tmux/grid-reader.c
nicm 81e5736510 Copy mode improvements from Anindya Mukherjee:
- Fix word and word-end for wrapped lines.
- Fix copying of selection end on wrapped lines.
- Fix wrapped word selection edge case.
- Update select-line to respect wrapped lines.
- Update window_copy_..._pos() functions to use grid_reader.

GitHub issue 2605.
2021-03-09 08:24:09 +00:00

366 lines
8.3 KiB
C

/* $OpenBSD$ */
/*
* Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "tmux.h"
#include <string.h>
/* Initialise virtual cursor. */
void
grid_reader_start(struct grid_reader *gr, struct grid *gd, u_int cx, u_int cy)
{
gr->gd = gd;
gr->cx = cx;
gr->cy = cy;
}
/* Get cursor position from reader. */
void
grid_reader_get_cursor(struct grid_reader *gr, u_int *cx, u_int *cy)
{
*cx = gr->cx;
*cy = gr->cy;
}
/* Get length of line containing the cursor. */
u_int
grid_reader_line_length(struct grid_reader *gr)
{
return (grid_line_length(gr->gd, gr->cy));
}
/* Move cursor forward one position. */
void
grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all)
{
u_int px;
struct grid_cell gc;
if (all)
px = gr->gd->sx;
else
px = grid_reader_line_length(gr);
if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) {
grid_reader_cursor_start_of_line(gr, 0);
grid_reader_cursor_down(gr);
} else if (gr->cx < px) {
gr->cx++;
while (gr->cx < px) {
grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
if (~gc.flags & GRID_FLAG_PADDING)
break;
gr->cx++;
}
}
}
/* Move cursor back one position. */
void
grid_reader_cursor_left(struct grid_reader *gr)
{
struct grid_cell gc;
while (gr->cx > 0) {
grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
if (~gc.flags & GRID_FLAG_PADDING)
break;
gr->cx--;
}
if (gr->cx == 0 && gr->cy > 0) {
grid_reader_cursor_up(gr);
grid_reader_cursor_end_of_line(gr, 0, 0);
} else if (gr->cx > 0)
gr->cx--;
}
/* Move cursor down one line. */
void
grid_reader_cursor_down(struct grid_reader *gr)
{
struct grid_cell gc;
if (gr->cy < gr->gd->hsize + gr->gd->sy - 1)
gr->cy++;
while (gr->cx > 0) {
grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
if (~gc.flags & GRID_FLAG_PADDING)
break;
gr->cx--;
}
}
/* Move cursor up one line. */
void
grid_reader_cursor_up(struct grid_reader *gr)
{
struct grid_cell gc;
if (gr->cy > 0)
gr->cy--;
while (gr->cx > 0) {
grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
if (~gc.flags & GRID_FLAG_PADDING)
break;
gr->cx--;
}
}
/* Move cursor to the start of the line. */
void
grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap)
{
if (wrap) {
while (gr->cy > 0 &&
grid_get_line(gr->gd, gr->cy - 1)->flags &
GRID_LINE_WRAPPED)
gr->cy--;
}
gr->cx = 0;
}
/* Move cursor to the end of the line. */
void
grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all)
{
u_int yy;
if (wrap) {
yy = gr->gd->hsize + gr->gd->sy - 1;
while (gr->cy < yy && grid_get_line(gr->gd, gr->cy)->flags &
GRID_LINE_WRAPPED)
gr->cy++;
}
if (all)
gr->cx = gr->gd->sx;
else
gr->cx = grid_reader_line_length(gr);
}
/* Check if character under cursor is in set. */
int
grid_reader_in_set(struct grid_reader *gr, const char *set)
{
struct grid_cell gc;
grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
if (gc.flags & GRID_FLAG_PADDING)
return (0);
return (utf8_cstrhas(set, &gc.data));
}
/* Move cursor to the start of the next word. */
void
grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
{
u_int xx, yy;
int expected = 0;
/* Do not break up wrapped words. */
if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
xx = gr->gd->sx - 1;
else
xx = grid_reader_line_length(gr);
yy = gr->gd->hsize + gr->gd->sy - 1;
/*
* If we started inside a word, skip over word characters. Then skip
* over separators till the next word.
*
* expected is initially set to 0 for the former and then 1 for the
* latter. It is finally set to 0 when the beginning of the next word is
* found.
*/
do {
while (gr->cx > xx ||
grid_reader_in_set(gr, separators) == expected) {
/* Move down if we are past the end of the line. */
if (gr->cx > xx) {
if (gr->cy == yy)
return;
grid_reader_cursor_start_of_line(gr, 0);
grid_reader_cursor_down(gr);
if (grid_get_line(gr->gd, gr->cy)->flags &
GRID_LINE_WRAPPED)
xx = gr->gd->sx - 1;
else
xx = grid_reader_line_length(gr);
} else
gr->cx++;
}
expected = !expected;
} while (expected == 1);
}
/* Move cursor to the end of the next word. */
void
grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
{
u_int xx, yy;
int expected = 1;
/* Do not break up wrapped words. */
if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
xx = gr->gd->sx - 1;
else
xx = grid_reader_line_length(gr);
yy = gr->gd->hsize + gr->gd->sy - 1;
/*
* If we started on a separator, skip over separators. Then skip over
* word characters till the next separator.
*
* expected is initially set to 1 for the former and then 1 for the
* latter. It is finally set to 1 when the end of the next word is
* found.
*/
do {
while (gr->cx > xx ||
grid_reader_in_set(gr, separators) == expected) {
/* Move down if we are past the end of the line. */
if (gr->cx > xx) {
if (gr->cy == yy)
return;
grid_reader_cursor_start_of_line(gr, 0);
grid_reader_cursor_down(gr);
if (grid_get_line(gr->gd, gr->cy)->flags &
GRID_LINE_WRAPPED)
xx = gr->gd->sx - 1;
else
xx = grid_reader_line_length(gr);
} else
gr->cx++;
}
expected = !expected;
} while (expected == 0);
}
/* Move to the previous place where a word begins. */
void
grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
int already)
{
int oldx, oldy, r;
/* Move back to the previous word character. */
if (already || grid_reader_in_set(gr, separators)) {
for (;;) {
if (gr->cx > 0) {
gr->cx--;
if (!grid_reader_in_set(gr, separators))
break;
} else {
if (gr->cy == 0)
return;
grid_reader_cursor_up(gr);
grid_reader_cursor_end_of_line(gr, 0, 0);
/* Stop if separator at EOL. */
if (gr->cx > 0) {
oldx = gr->cx;
gr->cx--;
r = grid_reader_in_set(gr, separators);
gr->cx = oldx;
if (r)
break;
}
}
}
}
/* Move back to the beginning of this word. */
do {
oldx = gr->cx;
oldy = gr->cy;
if (gr->cx == 0) {
if (gr->cy == 0 ||
~grid_get_line(gr->gd, gr->cy - 1)->flags &
GRID_LINE_WRAPPED)
break;
grid_reader_cursor_up(gr);
grid_reader_cursor_end_of_line(gr, 0, 1);
}
if (gr->cx > 0)
gr->cx--;
} while (!grid_reader_in_set(gr, separators));
gr->cx = oldx;
gr->cy = oldy;
}
/* Jump forward to character. */
int
grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc)
{
struct grid_cell gc;
u_int px, py, xx, yy;
px = gr->cx;
yy = gr->gd->hsize + gr->gd->sy - 1;
for (py = gr->cy; py <= yy; py++) {
xx = grid_line_length(gr->gd, py);
while (px < xx) {
grid_get_cell(gr->gd, px, py, &gc);
if (!(gc.flags & GRID_FLAG_PADDING) &&
gc.data.size == jc->size &&
memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
gr->cx = px;
gr->cy = py;
return 1;
}
px++;
}
if (py == yy ||
!(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED))
return 0;
px = 0;
}
return 0;
}
/* Jump back to character. */
int
grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc)
{
struct grid_cell gc;
u_int px, py, xx;
xx = gr->cx + 1;
for (py = gr->cy + 1; py > 0; py--) {
for (px = xx; px > 0; px--) {
grid_get_cell(gr->gd, px - 1, py - 1, &gc);
if (!(gc.flags & GRID_FLAG_PADDING) &&
gc.data.size == jc->size &&
memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
gr->cx = px - 1;
gr->cy = py - 1;
return 1;
}
}
if (py == 1 ||
!(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED))
return 0;
xx = grid_line_length(gr->gd, py - 2);
}
return 0;
}