1*61e9d0deSnicm /* $OpenBSD: grid-reader.c,v 1.9 2024/11/20 20:54:02 nicm Exp $ */ 2704a71ceSnicm 3704a71ceSnicm /* 4704a71ceSnicm * Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.com> 5704a71ceSnicm * 6704a71ceSnicm * Permission to use, copy, modify, and distribute this software for any 7704a71ceSnicm * purpose with or without fee is hereby granted, provided that the above 8704a71ceSnicm * copyright notice and this permission notice appear in all copies. 9704a71ceSnicm * 10704a71ceSnicm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11704a71ceSnicm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12704a71ceSnicm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13704a71ceSnicm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14704a71ceSnicm * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15704a71ceSnicm * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16704a71ceSnicm * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17704a71ceSnicm */ 18704a71ceSnicm 19704a71ceSnicm #include "tmux.h" 20d70f1befSnicm #include <string.h> 21704a71ceSnicm 22704a71ceSnicm /* Initialise virtual cursor. */ 23704a71ceSnicm void 24704a71ceSnicm grid_reader_start(struct grid_reader *gr, struct grid *gd, u_int cx, u_int cy) 25704a71ceSnicm { 26704a71ceSnicm gr->gd = gd; 27704a71ceSnicm gr->cx = cx; 28704a71ceSnicm gr->cy = cy; 29704a71ceSnicm } 30704a71ceSnicm 31704a71ceSnicm /* Get cursor position from reader. */ 32704a71ceSnicm void 33704a71ceSnicm grid_reader_get_cursor(struct grid_reader *gr, u_int *cx, u_int *cy) 34704a71ceSnicm { 35704a71ceSnicm *cx = gr->cx; 36704a71ceSnicm *cy = gr->cy; 37704a71ceSnicm } 38704a71ceSnicm 39704a71ceSnicm /* Get length of line containing the cursor. */ 40704a71ceSnicm u_int 41704a71ceSnicm grid_reader_line_length(struct grid_reader *gr) 42704a71ceSnicm { 43704a71ceSnicm return (grid_line_length(gr->gd, gr->cy)); 44704a71ceSnicm } 45704a71ceSnicm 46704a71ceSnicm /* Move cursor forward one position. */ 47704a71ceSnicm void 48704a71ceSnicm grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all) 49704a71ceSnicm { 50704a71ceSnicm u_int px; 51704a71ceSnicm struct grid_cell gc; 52704a71ceSnicm 53704a71ceSnicm if (all) 54704a71ceSnicm px = gr->gd->sx; 55704a71ceSnicm else 56704a71ceSnicm px = grid_reader_line_length(gr); 57704a71ceSnicm 58704a71ceSnicm if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) { 59704a71ceSnicm grid_reader_cursor_start_of_line(gr, 0); 60704a71ceSnicm grid_reader_cursor_down(gr); 61704a71ceSnicm } else if (gr->cx < px) { 62704a71ceSnicm gr->cx++; 63704a71ceSnicm while (gr->cx < px) { 64704a71ceSnicm grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); 65704a71ceSnicm if (~gc.flags & GRID_FLAG_PADDING) 66704a71ceSnicm break; 67704a71ceSnicm gr->cx++; 68704a71ceSnicm } 69704a71ceSnicm } 70704a71ceSnicm } 71704a71ceSnicm 72704a71ceSnicm /* Move cursor back one position. */ 73704a71ceSnicm void 742870e7b8Snicm grid_reader_cursor_left(struct grid_reader *gr, int wrap) 75704a71ceSnicm { 76704a71ceSnicm struct grid_cell gc; 77704a71ceSnicm 78704a71ceSnicm while (gr->cx > 0) { 79704a71ceSnicm grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); 80704a71ceSnicm if (~gc.flags & GRID_FLAG_PADDING) 81704a71ceSnicm break; 82704a71ceSnicm gr->cx--; 83704a71ceSnicm } 842870e7b8Snicm if (gr->cx == 0 && gr->cy > 0 && 852870e7b8Snicm (wrap || 862870e7b8Snicm grid_get_line(gr->gd, gr->cy - 1)->flags & GRID_LINE_WRAPPED)) { 87704a71ceSnicm grid_reader_cursor_up(gr); 88704a71ceSnicm grid_reader_cursor_end_of_line(gr, 0, 0); 89704a71ceSnicm } else if (gr->cx > 0) 90704a71ceSnicm gr->cx--; 91704a71ceSnicm } 92704a71ceSnicm 93704a71ceSnicm /* Move cursor down one line. */ 94704a71ceSnicm void 95704a71ceSnicm grid_reader_cursor_down(struct grid_reader *gr) 96704a71ceSnicm { 97704a71ceSnicm struct grid_cell gc; 98704a71ceSnicm 99704a71ceSnicm if (gr->cy < gr->gd->hsize + gr->gd->sy - 1) 100704a71ceSnicm gr->cy++; 101704a71ceSnicm while (gr->cx > 0) { 102704a71ceSnicm grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); 103704a71ceSnicm if (~gc.flags & GRID_FLAG_PADDING) 104704a71ceSnicm break; 105704a71ceSnicm gr->cx--; 106704a71ceSnicm } 107704a71ceSnicm } 108704a71ceSnicm 109704a71ceSnicm /* Move cursor up one line. */ 110704a71ceSnicm void 111704a71ceSnicm grid_reader_cursor_up(struct grid_reader *gr) 112704a71ceSnicm { 113704a71ceSnicm struct grid_cell gc; 114704a71ceSnicm 115704a71ceSnicm if (gr->cy > 0) 116704a71ceSnicm gr->cy--; 117704a71ceSnicm while (gr->cx > 0) { 118704a71ceSnicm grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); 119704a71ceSnicm if (~gc.flags & GRID_FLAG_PADDING) 120704a71ceSnicm break; 121704a71ceSnicm gr->cx--; 122704a71ceSnicm } 123704a71ceSnicm } 124704a71ceSnicm 125704a71ceSnicm /* Move cursor to the start of the line. */ 126704a71ceSnicm void 127704a71ceSnicm grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap) 128704a71ceSnicm { 129704a71ceSnicm if (wrap) { 130704a71ceSnicm while (gr->cy > 0 && 131704a71ceSnicm grid_get_line(gr->gd, gr->cy - 1)->flags & 132704a71ceSnicm GRID_LINE_WRAPPED) 133704a71ceSnicm gr->cy--; 134704a71ceSnicm } 135704a71ceSnicm gr->cx = 0; 136704a71ceSnicm } 137704a71ceSnicm 138704a71ceSnicm /* Move cursor to the end of the line. */ 139704a71ceSnicm void 140704a71ceSnicm grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all) 141704a71ceSnicm { 142704a71ceSnicm u_int yy; 143704a71ceSnicm 144704a71ceSnicm if (wrap) { 145704a71ceSnicm yy = gr->gd->hsize + gr->gd->sy - 1; 146704a71ceSnicm while (gr->cy < yy && grid_get_line(gr->gd, gr->cy)->flags & 147704a71ceSnicm GRID_LINE_WRAPPED) 148704a71ceSnicm gr->cy++; 149704a71ceSnicm } 150704a71ceSnicm if (all) 151704a71ceSnicm gr->cx = gr->gd->sx; 152704a71ceSnicm else 153704a71ceSnicm gr->cx = grid_reader_line_length(gr); 154704a71ceSnicm } 155704a71ceSnicm 1568f36458cSnicm /* Handle line wrapping while moving the cursor. */ 1578f36458cSnicm static int 1588f36458cSnicm grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy) 1598f36458cSnicm { 1608f36458cSnicm /* 1618f36458cSnicm * Make sure the cursor lies within the grid reader's bounding area, 1628f36458cSnicm * wrapping to the next line as necessary. Return zero if the cursor 1638f36458cSnicm * would wrap past the bottom of the grid. 1648f36458cSnicm */ 1658f36458cSnicm while (gr->cx > *xx) { 1668f36458cSnicm if (gr->cy == *yy) 1678f36458cSnicm return (0); 1688f36458cSnicm grid_reader_cursor_start_of_line(gr, 0); 1698f36458cSnicm grid_reader_cursor_down(gr); 1708f36458cSnicm 1718f36458cSnicm if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) 1728f36458cSnicm *xx = gr->gd->sx - 1; 1738f36458cSnicm else 1748f36458cSnicm *xx = grid_reader_line_length(gr); 1758f36458cSnicm } 1768f36458cSnicm return (1); 1778f36458cSnicm } 1788f36458cSnicm 179704a71ceSnicm /* Check if character under cursor is in set. */ 180704a71ceSnicm int 181704a71ceSnicm grid_reader_in_set(struct grid_reader *gr, const char *set) 182704a71ceSnicm { 183*61e9d0deSnicm return (grid_in_set(gr->gd, gr->cx, gr->cy, set)); 184704a71ceSnicm } 185704a71ceSnicm 186704a71ceSnicm /* Move cursor to the start of the next word. */ 187704a71ceSnicm void 188704a71ceSnicm grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators) 189704a71ceSnicm { 190*61e9d0deSnicm u_int xx, yy, width; 191704a71ceSnicm 192704a71ceSnicm /* Do not break up wrapped words. */ 193704a71ceSnicm if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) 194f4e980ccSnicm xx = gr->gd->sx - 1; 195704a71ceSnicm else 196704a71ceSnicm xx = grid_reader_line_length(gr); 197704a71ceSnicm yy = gr->gd->hsize + gr->gd->sy - 1; 198704a71ceSnicm 199704a71ceSnicm /* 2008f36458cSnicm * When navigating via spaces (for example with next-space) separators 2018f36458cSnicm * should be empty. 202704a71ceSnicm * 2038f36458cSnicm * If we started on a separator that is not whitespace, skip over 2048f36458cSnicm * subsequent separators that are not whitespace. Otherwise, if we 2058f36458cSnicm * started on a non-whitespace character, skip over subsequent 2068f36458cSnicm * characters that are neither whitespace nor separators. Then, skip 2078f36458cSnicm * over whitespace (if any) until the next non-whitespace character. 208704a71ceSnicm */ 2098f36458cSnicm if (!grid_reader_handle_wrap(gr, &xx, &yy)) 210704a71ceSnicm return; 2118f36458cSnicm if (!grid_reader_in_set(gr, WHITESPACE)) { 2128f36458cSnicm if (grid_reader_in_set(gr, separators)) { 2138f36458cSnicm do 214704a71ceSnicm gr->cx++; 2158f36458cSnicm while (grid_reader_handle_wrap(gr, &xx, &yy) && 2168f36458cSnicm grid_reader_in_set(gr, separators) && 2178f36458cSnicm !grid_reader_in_set(gr, WHITESPACE)); 2188f36458cSnicm } else { 2198f36458cSnicm do 2208f36458cSnicm gr->cx++; 2218f36458cSnicm while (grid_reader_handle_wrap(gr, &xx, &yy) && 2228f36458cSnicm !(grid_reader_in_set(gr, separators) || 2238f36458cSnicm grid_reader_in_set(gr, WHITESPACE))); 224704a71ceSnicm } 2258f36458cSnicm } 2268f36458cSnicm while (grid_reader_handle_wrap(gr, &xx, &yy) && 227*61e9d0deSnicm (width = grid_reader_in_set(gr, WHITESPACE))) 228*61e9d0deSnicm gr->cx += width; 229704a71ceSnicm } 230704a71ceSnicm 231704a71ceSnicm /* Move cursor to the end of the next word. */ 232704a71ceSnicm void 233704a71ceSnicm grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators) 234704a71ceSnicm { 235704a71ceSnicm u_int xx, yy; 236704a71ceSnicm 237704a71ceSnicm /* Do not break up wrapped words. */ 238704a71ceSnicm if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) 239f4e980ccSnicm xx = gr->gd->sx - 1; 240704a71ceSnicm else 241704a71ceSnicm xx = grid_reader_line_length(gr); 242704a71ceSnicm yy = gr->gd->hsize + gr->gd->sy - 1; 243704a71ceSnicm 244704a71ceSnicm /* 2458f36458cSnicm * When navigating via spaces (for example with next-space), separators 2468f36458cSnicm * should be empty in both modes. 247704a71ceSnicm * 2488f36458cSnicm * If we started on a whitespace, move until reaching the first 2498f36458cSnicm * non-whitespace character. If that character is a separator, treat 2508f36458cSnicm * subsequent separators as a word, and continue moving until the first 2518f36458cSnicm * non-separator. Otherwise, continue moving until the first separator 2528f36458cSnicm * or whitespace. 253704a71ceSnicm */ 254704a71ceSnicm 2558f36458cSnicm while (grid_reader_handle_wrap(gr, &xx, &yy)) { 2568f36458cSnicm if (grid_reader_in_set(gr, WHITESPACE)) 257704a71ceSnicm gr->cx++; 2588f36458cSnicm else if (grid_reader_in_set(gr, separators)) { 2598f36458cSnicm do 2608f36458cSnicm gr->cx++; 2618f36458cSnicm while (grid_reader_handle_wrap(gr, &xx, &yy) && 2628f36458cSnicm grid_reader_in_set(gr, separators) && 2638f36458cSnicm !grid_reader_in_set(gr, WHITESPACE)); 2648f36458cSnicm return; 2658f36458cSnicm } else { 2668f36458cSnicm do 2678f36458cSnicm gr->cx++; 2688f36458cSnicm while (grid_reader_handle_wrap(gr, &xx, &yy) && 2698f36458cSnicm !(grid_reader_in_set(gr, WHITESPACE) || 2708f36458cSnicm grid_reader_in_set(gr, separators))); 2718f36458cSnicm return; 272704a71ceSnicm } 2738f36458cSnicm } 274704a71ceSnicm } 275704a71ceSnicm 276704a71ceSnicm /* Move to the previous place where a word begins. */ 277704a71ceSnicm void 278704a71ceSnicm grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, 2798f36458cSnicm int already, int stop_at_eol) 280704a71ceSnicm { 2818f36458cSnicm int oldx, oldy, at_eol, word_is_letters; 282704a71ceSnicm 283704a71ceSnicm /* Move back to the previous word character. */ 2848f36458cSnicm if (already || grid_reader_in_set(gr, WHITESPACE)) { 285704a71ceSnicm for (;;) { 286704a71ceSnicm if (gr->cx > 0) { 287704a71ceSnicm gr->cx--; 2888f36458cSnicm if (!grid_reader_in_set(gr, WHITESPACE)) { 2898f36458cSnicm word_is_letters = 2908f36458cSnicm !grid_reader_in_set(gr, separators); 291704a71ceSnicm break; 2928f36458cSnicm } 293704a71ceSnicm } else { 294704a71ceSnicm if (gr->cy == 0) 295704a71ceSnicm return; 296704a71ceSnicm grid_reader_cursor_up(gr); 297704a71ceSnicm grid_reader_cursor_end_of_line(gr, 0, 0); 298704a71ceSnicm 299704a71ceSnicm /* Stop if separator at EOL. */ 3008f36458cSnicm if (stop_at_eol && gr->cx > 0) { 301704a71ceSnicm oldx = gr->cx; 302704a71ceSnicm gr->cx--; 3038f36458cSnicm at_eol = grid_reader_in_set(gr, 3048f36458cSnicm WHITESPACE); 305704a71ceSnicm gr->cx = oldx; 3068f36458cSnicm if (at_eol) { 3078f36458cSnicm word_is_letters = 0; 308704a71ceSnicm break; 309704a71ceSnicm } 310704a71ceSnicm } 311704a71ceSnicm } 312704a71ceSnicm } 3138f36458cSnicm } else 3148f36458cSnicm word_is_letters = !grid_reader_in_set(gr, separators); 315704a71ceSnicm 316704a71ceSnicm /* Move back to the beginning of this word. */ 317704a71ceSnicm do { 318704a71ceSnicm oldx = gr->cx; 319704a71ceSnicm oldy = gr->cy; 320704a71ceSnicm if (gr->cx == 0) { 321704a71ceSnicm if (gr->cy == 0 || 3228f36458cSnicm (~grid_get_line(gr->gd, gr->cy - 1)->flags & 3238f36458cSnicm GRID_LINE_WRAPPED)) 324704a71ceSnicm break; 325704a71ceSnicm grid_reader_cursor_up(gr); 326f4e980ccSnicm grid_reader_cursor_end_of_line(gr, 0, 1); 327704a71ceSnicm } 328704a71ceSnicm if (gr->cx > 0) 329704a71ceSnicm gr->cx--; 3308f36458cSnicm } while (!grid_reader_in_set(gr, WHITESPACE) && 3318f36458cSnicm word_is_letters != grid_reader_in_set(gr, separators)); 332704a71ceSnicm gr->cx = oldx; 333704a71ceSnicm gr->cy = oldy; 334704a71ceSnicm } 335d70f1befSnicm 33626b00f59Snicm /* Compare grid cell to UTF-8 data. Return 1 if equal, 0 if not. */ 33726b00f59Snicm static int 33826b00f59Snicm grid_reader_cell_equals_data(const struct grid_cell *gc, 33926b00f59Snicm const struct utf8_data *ud) 34026b00f59Snicm { 34126b00f59Snicm if (gc->flags & GRID_FLAG_PADDING) 34226b00f59Snicm return (0); 34302d75531Snicm if (gc->flags & GRID_FLAG_TAB && ud->size == 1 && *ud->data == '\t') 34402d75531Snicm return (1); 34526b00f59Snicm if (gc->data.size != ud->size) 34626b00f59Snicm return (0); 34726b00f59Snicm return (memcmp(gc->data.data, ud->data, gc->data.size) == 0); 34826b00f59Snicm } 34926b00f59Snicm 350d70f1befSnicm /* Jump forward to character. */ 351d70f1befSnicm int 352d70f1befSnicm grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc) 353d70f1befSnicm { 354d70f1befSnicm struct grid_cell gc; 355d70f1befSnicm u_int px, py, xx, yy; 356d70f1befSnicm 357d70f1befSnicm px = gr->cx; 358d70f1befSnicm yy = gr->gd->hsize + gr->gd->sy - 1; 359d70f1befSnicm 360d70f1befSnicm for (py = gr->cy; py <= yy; py++) { 361d70f1befSnicm xx = grid_line_length(gr->gd, py); 362d70f1befSnicm while (px < xx) { 363d70f1befSnicm grid_get_cell(gr->gd, px, py, &gc); 36426b00f59Snicm if (grid_reader_cell_equals_data(&gc, jc)) { 365d70f1befSnicm gr->cx = px; 366d70f1befSnicm gr->cy = py; 3678f36458cSnicm return (1); 368d70f1befSnicm } 369d70f1befSnicm px++; 370d70f1befSnicm } 371d70f1befSnicm 372d70f1befSnicm if (py == yy || 373d70f1befSnicm !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)) 3748f36458cSnicm return (0); 375d70f1befSnicm px = 0; 376d70f1befSnicm } 3778f36458cSnicm return (0); 378d70f1befSnicm } 379d70f1befSnicm 380d70f1befSnicm /* Jump back to character. */ 381d70f1befSnicm int 382d70f1befSnicm grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc) 383d70f1befSnicm { 384d70f1befSnicm struct grid_cell gc; 385d70f1befSnicm u_int px, py, xx; 386d70f1befSnicm 387d70f1befSnicm xx = gr->cx + 1; 388d70f1befSnicm 389d70f1befSnicm for (py = gr->cy + 1; py > 0; py--) { 390d70f1befSnicm for (px = xx; px > 0; px--) { 391d70f1befSnicm grid_get_cell(gr->gd, px - 1, py - 1, &gc); 39226b00f59Snicm if (grid_reader_cell_equals_data(&gc, jc)) { 393d70f1befSnicm gr->cx = px - 1; 394d70f1befSnicm gr->cy = py - 1; 3958f36458cSnicm return (1); 396d70f1befSnicm } 397d70f1befSnicm } 398d70f1befSnicm 399d70f1befSnicm if (py == 1 || 400d70f1befSnicm !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED)) 4018f36458cSnicm return (0); 402d70f1befSnicm xx = grid_line_length(gr->gd, py - 2); 403d70f1befSnicm } 4048f36458cSnicm return (0); 405d70f1befSnicm } 4062870e7b8Snicm 4072870e7b8Snicm /* Jump back to the first non-blank character of the line. */ 4082870e7b8Snicm void 4092870e7b8Snicm grid_reader_cursor_back_to_indentation(struct grid_reader *gr) 4102870e7b8Snicm { 4112870e7b8Snicm struct grid_cell gc; 4126d68d2a2Snicm u_int px, py, xx, yy, oldx, oldy; 4132870e7b8Snicm 4142870e7b8Snicm yy = gr->gd->hsize + gr->gd->sy - 1; 4156d68d2a2Snicm oldx = gr->cx; 4166d68d2a2Snicm oldy = gr->cy; 4172870e7b8Snicm grid_reader_cursor_start_of_line(gr, 1); 4182870e7b8Snicm 4192870e7b8Snicm for (py = gr->cy; py <= yy; py++) { 4202870e7b8Snicm xx = grid_line_length(gr->gd, py); 4212870e7b8Snicm for (px = 0; px < xx; px++) { 4222870e7b8Snicm grid_get_cell(gr->gd, px, py, &gc); 423*61e9d0deSnicm if ((gc.data.size != 1 || *gc.data.data != ' ') && 424*61e9d0deSnicm ~gc.flags & GRID_FLAG_TAB && 425*61e9d0deSnicm ~gc.flags & GRID_FLAG_PADDING) { 4266d68d2a2Snicm gr->cx = px; 4276d68d2a2Snicm gr->cy = py; 4286d68d2a2Snicm return; 4296d68d2a2Snicm } 4302870e7b8Snicm } 4312870e7b8Snicm if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED) 4322870e7b8Snicm break; 4332870e7b8Snicm } 4346d68d2a2Snicm gr->cx = oldx; 4356d68d2a2Snicm gr->cy = oldy; 4362870e7b8Snicm } 437