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