xref: /openbsd/usr.bin/tmux/grid-reader.c (revision 61e9d0de)
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
grid_reader_start(struct grid_reader * gr,struct grid * gd,u_int cx,u_int cy)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
grid_reader_get_cursor(struct grid_reader * gr,u_int * cx,u_int * cy)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
grid_reader_line_length(struct grid_reader * gr)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
grid_reader_cursor_right(struct grid_reader * gr,int wrap,int all)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
grid_reader_cursor_left(struct grid_reader * gr,int wrap)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
grid_reader_cursor_down(struct grid_reader * gr)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
grid_reader_cursor_up(struct grid_reader * gr)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
grid_reader_cursor_start_of_line(struct grid_reader * gr,int wrap)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
grid_reader_cursor_end_of_line(struct grid_reader * gr,int wrap,int all)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
grid_reader_handle_wrap(struct grid_reader * gr,u_int * xx,u_int * yy)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
grid_reader_in_set(struct grid_reader * gr,const char * set)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
grid_reader_cursor_next_word(struct grid_reader * gr,const char * separators)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
grid_reader_cursor_next_word_end(struct grid_reader * gr,const char * separators)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
grid_reader_cursor_previous_word(struct grid_reader * gr,const char * separators,int already,int stop_at_eol)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
grid_reader_cell_equals_data(const struct grid_cell * gc,const struct utf8_data * ud)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
grid_reader_cursor_jump(struct grid_reader * gr,const struct utf8_data * jc)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
grid_reader_cursor_jump_back(struct grid_reader * gr,const struct utf8_data * jc)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
grid_reader_cursor_back_to_indentation(struct grid_reader * gr)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