1 /* $OpenBSD: grid-reader.c,v 1.6 2021/06/10 07:56:47 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 struct grid_cell gc; 184 185 grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); 186 if (gc.flags & GRID_FLAG_PADDING) 187 return (0); 188 return (utf8_cstrhas(set, &gc.data)); 189 } 190 191 /* Move cursor to the start of the next word. */ 192 void 193 grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators) 194 { 195 u_int xx, yy; 196 197 /* Do not break up wrapped words. */ 198 if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) 199 xx = gr->gd->sx - 1; 200 else 201 xx = grid_reader_line_length(gr); 202 yy = gr->gd->hsize + gr->gd->sy - 1; 203 204 /* 205 * When navigating via spaces (for example with next-space) separators 206 * should be empty. 207 * 208 * If we started on a separator that is not whitespace, skip over 209 * subsequent separators that are not whitespace. Otherwise, if we 210 * started on a non-whitespace character, skip over subsequent 211 * characters that are neither whitespace nor separators. Then, skip 212 * over whitespace (if any) until the next non-whitespace character. 213 */ 214 if (!grid_reader_handle_wrap(gr, &xx, &yy)) 215 return; 216 if (!grid_reader_in_set(gr, WHITESPACE)) { 217 if (grid_reader_in_set(gr, separators)) { 218 do 219 gr->cx++; 220 while (grid_reader_handle_wrap(gr, &xx, &yy) && 221 grid_reader_in_set(gr, separators) && 222 !grid_reader_in_set(gr, WHITESPACE)); 223 } else { 224 do 225 gr->cx++; 226 while (grid_reader_handle_wrap(gr, &xx, &yy) && 227 !(grid_reader_in_set(gr, separators) || 228 grid_reader_in_set(gr, WHITESPACE))); 229 } 230 } 231 while (grid_reader_handle_wrap(gr, &xx, &yy) && 232 grid_reader_in_set(gr, WHITESPACE)) 233 gr->cx++; 234 } 235 236 /* Move cursor to the end of the next word. */ 237 void 238 grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators) 239 { 240 u_int xx, yy; 241 242 /* Do not break up wrapped words. */ 243 if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) 244 xx = gr->gd->sx - 1; 245 else 246 xx = grid_reader_line_length(gr); 247 yy = gr->gd->hsize + gr->gd->sy - 1; 248 249 /* 250 * When navigating via spaces (for example with next-space), separators 251 * should be empty in both modes. 252 * 253 * If we started on a whitespace, move until reaching the first 254 * non-whitespace character. If that character is a separator, treat 255 * subsequent separators as a word, and continue moving until the first 256 * non-separator. Otherwise, continue moving until the first separator 257 * or whitespace. 258 */ 259 260 while (grid_reader_handle_wrap(gr, &xx, &yy)) { 261 if (grid_reader_in_set(gr, WHITESPACE)) 262 gr->cx++; 263 else if (grid_reader_in_set(gr, separators)) { 264 do 265 gr->cx++; 266 while (grid_reader_handle_wrap(gr, &xx, &yy) && 267 grid_reader_in_set(gr, separators) && 268 !grid_reader_in_set(gr, WHITESPACE)); 269 return; 270 } else { 271 do 272 gr->cx++; 273 while (grid_reader_handle_wrap(gr, &xx, &yy) && 274 !(grid_reader_in_set(gr, WHITESPACE) || 275 grid_reader_in_set(gr, separators))); 276 return; 277 } 278 } 279 } 280 281 /* Move to the previous place where a word begins. */ 282 void 283 grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, 284 int already, int stop_at_eol) 285 { 286 int oldx, oldy, at_eol, word_is_letters; 287 288 /* Move back to the previous word character. */ 289 if (already || grid_reader_in_set(gr, WHITESPACE)) { 290 for (;;) { 291 if (gr->cx > 0) { 292 gr->cx--; 293 if (!grid_reader_in_set(gr, WHITESPACE)) { 294 word_is_letters = 295 !grid_reader_in_set(gr, separators); 296 break; 297 } 298 } else { 299 if (gr->cy == 0) 300 return; 301 grid_reader_cursor_up(gr); 302 grid_reader_cursor_end_of_line(gr, 0, 0); 303 304 /* Stop if separator at EOL. */ 305 if (stop_at_eol && gr->cx > 0) { 306 oldx = gr->cx; 307 gr->cx--; 308 at_eol = grid_reader_in_set(gr, 309 WHITESPACE); 310 gr->cx = oldx; 311 if (at_eol) { 312 word_is_letters = 0; 313 break; 314 } 315 } 316 } 317 } 318 } else 319 word_is_letters = !grid_reader_in_set(gr, separators); 320 321 /* Move back to the beginning of this word. */ 322 do { 323 oldx = gr->cx; 324 oldy = gr->cy; 325 if (gr->cx == 0) { 326 if (gr->cy == 0 || 327 (~grid_get_line(gr->gd, gr->cy - 1)->flags & 328 GRID_LINE_WRAPPED)) 329 break; 330 grid_reader_cursor_up(gr); 331 grid_reader_cursor_end_of_line(gr, 0, 1); 332 } 333 if (gr->cx > 0) 334 gr->cx--; 335 } while (!grid_reader_in_set(gr, WHITESPACE) && 336 word_is_letters != grid_reader_in_set(gr, separators)); 337 gr->cx = oldx; 338 gr->cy = oldy; 339 } 340 341 /* Jump forward to character. */ 342 int 343 grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc) 344 { 345 struct grid_cell gc; 346 u_int px, py, xx, yy; 347 348 px = gr->cx; 349 yy = gr->gd->hsize + gr->gd->sy - 1; 350 351 for (py = gr->cy; py <= yy; py++) { 352 xx = grid_line_length(gr->gd, py); 353 while (px < xx) { 354 grid_get_cell(gr->gd, px, py, &gc); 355 if (!(gc.flags & GRID_FLAG_PADDING) && 356 gc.data.size == jc->size && 357 memcmp(gc.data.data, jc->data, gc.data.size) == 0) { 358 gr->cx = px; 359 gr->cy = py; 360 return (1); 361 } 362 px++; 363 } 364 365 if (py == yy || 366 !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)) 367 return (0); 368 px = 0; 369 } 370 return (0); 371 } 372 373 /* Jump back to character. */ 374 int 375 grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc) 376 { 377 struct grid_cell gc; 378 u_int px, py, xx; 379 380 xx = gr->cx + 1; 381 382 for (py = gr->cy + 1; py > 0; py--) { 383 for (px = xx; px > 0; px--) { 384 grid_get_cell(gr->gd, px - 1, py - 1, &gc); 385 if (!(gc.flags & GRID_FLAG_PADDING) && 386 gc.data.size == jc->size && 387 memcmp(gc.data.data, jc->data, gc.data.size) == 0) { 388 gr->cx = px - 1; 389 gr->cy = py - 1; 390 return (1); 391 } 392 } 393 394 if (py == 1 || 395 !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED)) 396 return (0); 397 xx = grid_line_length(gr->gd, py - 2); 398 } 399 return (0); 400 } 401 402 /* Jump back to the first non-blank character of the line. */ 403 void 404 grid_reader_cursor_back_to_indentation(struct grid_reader *gr) 405 { 406 struct grid_cell gc; 407 u_int px, py, xx, yy, oldx, oldy; 408 409 yy = gr->gd->hsize + gr->gd->sy - 1; 410 oldx = gr->cx; 411 oldy = gr->cy; 412 grid_reader_cursor_start_of_line(gr, 1); 413 414 for (py = gr->cy; py <= yy; py++) { 415 xx = grid_line_length(gr->gd, py); 416 for (px = 0; px < xx; px++) { 417 grid_get_cell(gr->gd, px, py, &gc); 418 if (gc.data.size != 1 || *gc.data.data != ' ') { 419 gr->cx = px; 420 gr->cy = py; 421 return; 422 } 423 } 424 if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED) 425 break; 426 } 427 gr->cx = oldx; 428 gr->cy = oldy; 429 } 430