1 /* $OpenBSD: grid-reader.c,v 1.9 2024/11/20 20:54:02 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include "tmux.h" 20 #include <string.h> 21 22 /* Initialise virtual cursor. */ 23 void 24 grid_reader_start(struct grid_reader *gr, struct grid *gd, u_int cx, u_int cy) 25 { 26 gr->gd = gd; 27 gr->cx = cx; 28 gr->cy = cy; 29 } 30 31 /* Get cursor position from reader. */ 32 void 33 grid_reader_get_cursor(struct grid_reader *gr, u_int *cx, u_int *cy) 34 { 35 *cx = gr->cx; 36 *cy = gr->cy; 37 } 38 39 /* Get length of line containing the cursor. */ 40 u_int 41 grid_reader_line_length(struct grid_reader *gr) 42 { 43 return (grid_line_length(gr->gd, gr->cy)); 44 } 45 46 /* Move cursor forward one position. */ 47 void 48 grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all) 49 { 50 u_int px; 51 struct grid_cell gc; 52 53 if (all) 54 px = gr->gd->sx; 55 else 56 px = grid_reader_line_length(gr); 57 58 if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) { 59 grid_reader_cursor_start_of_line(gr, 0); 60 grid_reader_cursor_down(gr); 61 } else if (gr->cx < px) { 62 gr->cx++; 63 while (gr->cx < px) { 64 grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); 65 if (~gc.flags & GRID_FLAG_PADDING) 66 break; 67 gr->cx++; 68 } 69 } 70 } 71 72 /* Move cursor back one position. */ 73 void 74 grid_reader_cursor_left(struct grid_reader *gr, int wrap) 75 { 76 struct grid_cell gc; 77 78 while (gr->cx > 0) { 79 grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); 80 if (~gc.flags & GRID_FLAG_PADDING) 81 break; 82 gr->cx--; 83 } 84 if (gr->cx == 0 && gr->cy > 0 && 85 (wrap || 86 grid_get_line(gr->gd, gr->cy - 1)->flags & GRID_LINE_WRAPPED)) { 87 grid_reader_cursor_up(gr); 88 grid_reader_cursor_end_of_line(gr, 0, 0); 89 } else if (gr->cx > 0) 90 gr->cx--; 91 } 92 93 /* Move cursor down one line. */ 94 void 95 grid_reader_cursor_down(struct grid_reader *gr) 96 { 97 struct grid_cell gc; 98 99 if (gr->cy < gr->gd->hsize + gr->gd->sy - 1) 100 gr->cy++; 101 while (gr->cx > 0) { 102 grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); 103 if (~gc.flags & GRID_FLAG_PADDING) 104 break; 105 gr->cx--; 106 } 107 } 108 109 /* Move cursor up one line. */ 110 void 111 grid_reader_cursor_up(struct grid_reader *gr) 112 { 113 struct grid_cell gc; 114 115 if (gr->cy > 0) 116 gr->cy--; 117 while (gr->cx > 0) { 118 grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); 119 if (~gc.flags & GRID_FLAG_PADDING) 120 break; 121 gr->cx--; 122 } 123 } 124 125 /* Move cursor to the start of the line. */ 126 void 127 grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap) 128 { 129 if (wrap) { 130 while (gr->cy > 0 && 131 grid_get_line(gr->gd, gr->cy - 1)->flags & 132 GRID_LINE_WRAPPED) 133 gr->cy--; 134 } 135 gr->cx = 0; 136 } 137 138 /* Move cursor to the end of the line. */ 139 void 140 grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all) 141 { 142 u_int yy; 143 144 if (wrap) { 145 yy = gr->gd->hsize + gr->gd->sy - 1; 146 while (gr->cy < yy && grid_get_line(gr->gd, gr->cy)->flags & 147 GRID_LINE_WRAPPED) 148 gr->cy++; 149 } 150 if (all) 151 gr->cx = gr->gd->sx; 152 else 153 gr->cx = grid_reader_line_length(gr); 154 } 155 156 /* Handle line wrapping while moving the cursor. */ 157 static int 158 grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy) 159 { 160 /* 161 * Make sure the cursor lies within the grid reader's bounding area, 162 * wrapping to the next line as necessary. Return zero if the cursor 163 * would wrap past the bottom of the grid. 164 */ 165 while (gr->cx > *xx) { 166 if (gr->cy == *yy) 167 return (0); 168 grid_reader_cursor_start_of_line(gr, 0); 169 grid_reader_cursor_down(gr); 170 171 if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) 172 *xx = gr->gd->sx - 1; 173 else 174 *xx = grid_reader_line_length(gr); 175 } 176 return (1); 177 } 178 179 /* Check if character under cursor is in set. */ 180 int 181 grid_reader_in_set(struct grid_reader *gr, const char *set) 182 { 183 return (grid_in_set(gr->gd, gr->cx, gr->cy, set)); 184 } 185 186 /* Move cursor to the start of the next word. */ 187 void 188 grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators) 189 { 190 u_int xx, yy, width; 191 192 /* Do not break up wrapped words. */ 193 if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) 194 xx = gr->gd->sx - 1; 195 else 196 xx = grid_reader_line_length(gr); 197 yy = gr->gd->hsize + gr->gd->sy - 1; 198 199 /* 200 * When navigating via spaces (for example with next-space) separators 201 * should be empty. 202 * 203 * If we started on a separator that is not whitespace, skip over 204 * subsequent separators that are not whitespace. Otherwise, if we 205 * started on a non-whitespace character, skip over subsequent 206 * characters that are neither whitespace nor separators. Then, skip 207 * over whitespace (if any) until the next non-whitespace character. 208 */ 209 if (!grid_reader_handle_wrap(gr, &xx, &yy)) 210 return; 211 if (!grid_reader_in_set(gr, WHITESPACE)) { 212 if (grid_reader_in_set(gr, separators)) { 213 do 214 gr->cx++; 215 while (grid_reader_handle_wrap(gr, &xx, &yy) && 216 grid_reader_in_set(gr, separators) && 217 !grid_reader_in_set(gr, WHITESPACE)); 218 } else { 219 do 220 gr->cx++; 221 while (grid_reader_handle_wrap(gr, &xx, &yy) && 222 !(grid_reader_in_set(gr, separators) || 223 grid_reader_in_set(gr, WHITESPACE))); 224 } 225 } 226 while (grid_reader_handle_wrap(gr, &xx, &yy) && 227 (width = grid_reader_in_set(gr, WHITESPACE))) 228 gr->cx += width; 229 } 230 231 /* Move cursor to the end of the next word. */ 232 void 233 grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators) 234 { 235 u_int xx, yy; 236 237 /* Do not break up wrapped words. */ 238 if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) 239 xx = gr->gd->sx - 1; 240 else 241 xx = grid_reader_line_length(gr); 242 yy = gr->gd->hsize + gr->gd->sy - 1; 243 244 /* 245 * When navigating via spaces (for example with next-space), separators 246 * should be empty in both modes. 247 * 248 * If we started on a whitespace, move until reaching the first 249 * non-whitespace character. If that character is a separator, treat 250 * subsequent separators as a word, and continue moving until the first 251 * non-separator. Otherwise, continue moving until the first separator 252 * or whitespace. 253 */ 254 255 while (grid_reader_handle_wrap(gr, &xx, &yy)) { 256 if (grid_reader_in_set(gr, WHITESPACE)) 257 gr->cx++; 258 else if (grid_reader_in_set(gr, separators)) { 259 do 260 gr->cx++; 261 while (grid_reader_handle_wrap(gr, &xx, &yy) && 262 grid_reader_in_set(gr, separators) && 263 !grid_reader_in_set(gr, WHITESPACE)); 264 return; 265 } else { 266 do 267 gr->cx++; 268 while (grid_reader_handle_wrap(gr, &xx, &yy) && 269 !(grid_reader_in_set(gr, WHITESPACE) || 270 grid_reader_in_set(gr, separators))); 271 return; 272 } 273 } 274 } 275 276 /* Move to the previous place where a word begins. */ 277 void 278 grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, 279 int already, int stop_at_eol) 280 { 281 int oldx, oldy, at_eol, word_is_letters; 282 283 /* Move back to the previous word character. */ 284 if (already || grid_reader_in_set(gr, WHITESPACE)) { 285 for (;;) { 286 if (gr->cx > 0) { 287 gr->cx--; 288 if (!grid_reader_in_set(gr, WHITESPACE)) { 289 word_is_letters = 290 !grid_reader_in_set(gr, separators); 291 break; 292 } 293 } else { 294 if (gr->cy == 0) 295 return; 296 grid_reader_cursor_up(gr); 297 grid_reader_cursor_end_of_line(gr, 0, 0); 298 299 /* Stop if separator at EOL. */ 300 if (stop_at_eol && gr->cx > 0) { 301 oldx = gr->cx; 302 gr->cx--; 303 at_eol = grid_reader_in_set(gr, 304 WHITESPACE); 305 gr->cx = oldx; 306 if (at_eol) { 307 word_is_letters = 0; 308 break; 309 } 310 } 311 } 312 } 313 } else 314 word_is_letters = !grid_reader_in_set(gr, separators); 315 316 /* Move back to the beginning of this word. */ 317 do { 318 oldx = gr->cx; 319 oldy = gr->cy; 320 if (gr->cx == 0) { 321 if (gr->cy == 0 || 322 (~grid_get_line(gr->gd, gr->cy - 1)->flags & 323 GRID_LINE_WRAPPED)) 324 break; 325 grid_reader_cursor_up(gr); 326 grid_reader_cursor_end_of_line(gr, 0, 1); 327 } 328 if (gr->cx > 0) 329 gr->cx--; 330 } while (!grid_reader_in_set(gr, WHITESPACE) && 331 word_is_letters != grid_reader_in_set(gr, separators)); 332 gr->cx = oldx; 333 gr->cy = oldy; 334 } 335 336 /* Compare grid cell to UTF-8 data. Return 1 if equal, 0 if not. */ 337 static int 338 grid_reader_cell_equals_data(const struct grid_cell *gc, 339 const struct utf8_data *ud) 340 { 341 if (gc->flags & GRID_FLAG_PADDING) 342 return (0); 343 if (gc->flags & GRID_FLAG_TAB && ud->size == 1 && *ud->data == '\t') 344 return (1); 345 if (gc->data.size != ud->size) 346 return (0); 347 return (memcmp(gc->data.data, ud->data, gc->data.size) == 0); 348 } 349 350 /* Jump forward to character. */ 351 int 352 grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc) 353 { 354 struct grid_cell gc; 355 u_int px, py, xx, yy; 356 357 px = gr->cx; 358 yy = gr->gd->hsize + gr->gd->sy - 1; 359 360 for (py = gr->cy; py <= yy; py++) { 361 xx = grid_line_length(gr->gd, py); 362 while (px < xx) { 363 grid_get_cell(gr->gd, px, py, &gc); 364 if (grid_reader_cell_equals_data(&gc, jc)) { 365 gr->cx = px; 366 gr->cy = py; 367 return (1); 368 } 369 px++; 370 } 371 372 if (py == yy || 373 !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)) 374 return (0); 375 px = 0; 376 } 377 return (0); 378 } 379 380 /* Jump back to character. */ 381 int 382 grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc) 383 { 384 struct grid_cell gc; 385 u_int px, py, xx; 386 387 xx = gr->cx + 1; 388 389 for (py = gr->cy + 1; py > 0; py--) { 390 for (px = xx; px > 0; px--) { 391 grid_get_cell(gr->gd, px - 1, py - 1, &gc); 392 if (grid_reader_cell_equals_data(&gc, jc)) { 393 gr->cx = px - 1; 394 gr->cy = py - 1; 395 return (1); 396 } 397 } 398 399 if (py == 1 || 400 !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED)) 401 return (0); 402 xx = grid_line_length(gr->gd, py - 2); 403 } 404 return (0); 405 } 406 407 /* Jump back to the first non-blank character of the line. */ 408 void 409 grid_reader_cursor_back_to_indentation(struct grid_reader *gr) 410 { 411 struct grid_cell gc; 412 u_int px, py, xx, yy, oldx, oldy; 413 414 yy = gr->gd->hsize + gr->gd->sy - 1; 415 oldx = gr->cx; 416 oldy = gr->cy; 417 grid_reader_cursor_start_of_line(gr, 1); 418 419 for (py = gr->cy; py <= yy; py++) { 420 xx = grid_line_length(gr->gd, py); 421 for (px = 0; px < xx; px++) { 422 grid_get_cell(gr->gd, px, py, &gc); 423 if ((gc.data.size != 1 || *gc.data.data != ' ') && 424 ~gc.flags & GRID_FLAG_TAB && 425 ~gc.flags & GRID_FLAG_PADDING) { 426 gr->cx = px; 427 gr->cy = py; 428 return; 429 } 430 } 431 if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED) 432 break; 433 } 434 gr->cx = oldx; 435 gr->cy = oldy; 436 } 437