1 /* $OpenBSD$ */
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 /* Check if character under cursor is in set. */
157 int
grid_reader_in_set(struct grid_reader * gr,const char * set)158 grid_reader_in_set(struct grid_reader *gr, const char *set)
159 {
160 	struct grid_cell	gc;
161 
162 	grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
163 	if (gc.flags & GRID_FLAG_PADDING)
164 		return (0);
165 	return (utf8_cstrhas(set, &gc.data));
166 }
167 
168 /* Move cursor to the start of the next word. */
169 void
grid_reader_cursor_next_word(struct grid_reader * gr,const char * separators)170 grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
171 {
172 	u_int	xx, yy;
173 	int expected = 0;
174 
175 	/* Do not break up wrapped words. */
176 	if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
177 		xx = gr->gd->sx - 1;
178 	else
179 		xx = grid_reader_line_length(gr);
180 	yy = gr->gd->hsize + gr->gd->sy - 1;
181 
182 	/*
183 	 * If we started inside a word, skip over word characters. Then skip
184 	 * over separators till the next word.
185 	 *
186 	 * expected is initially set to 0 for the former and then 1 for the
187 	 * latter. It is finally set to 0 when the beginning of the next word is
188 	 * found.
189 	 */
190 	do {
191 		while (gr->cx > xx ||
192 		    grid_reader_in_set(gr, separators) == expected) {
193 			/* Move down if we are past the end of the line. */
194 			if (gr->cx > xx) {
195 				if (gr->cy == yy)
196 					return;
197 				grid_reader_cursor_start_of_line(gr, 0);
198 				grid_reader_cursor_down(gr);
199 
200 				if (grid_get_line(gr->gd, gr->cy)->flags &
201 				    GRID_LINE_WRAPPED)
202 					xx = gr->gd->sx - 1;
203 				else
204 					xx = grid_reader_line_length(gr);
205 			} else
206 				gr->cx++;
207 		}
208 		expected = !expected;
209 	} while (expected == 1);
210 }
211 
212 /* Move cursor to the end of the next word. */
213 void
grid_reader_cursor_next_word_end(struct grid_reader * gr,const char * separators)214 grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
215 {
216 	u_int	xx, yy;
217 	int	expected = 1;
218 
219 	/* Do not break up wrapped words. */
220 	if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
221 		xx = gr->gd->sx - 1;
222 	else
223 		xx = grid_reader_line_length(gr);
224 	yy = gr->gd->hsize + gr->gd->sy - 1;
225 
226 	/*
227 	 * If we started on a separator, skip over separators. Then skip over
228 	 * word characters till the next separator.
229 	 *
230 	 * expected is initially set to 1 for the former and then 1 for the
231 	 * latter. It is finally set to 1 when the end of the next word is
232 	 * found.
233 	 */
234 	do {
235 		while (gr->cx > xx ||
236 		    grid_reader_in_set(gr, separators) == expected) {
237 			/* Move down if we are past the end of the line. */
238 			if (gr->cx > xx) {
239 				if (gr->cy == yy)
240 					return;
241 				grid_reader_cursor_start_of_line(gr, 0);
242 				grid_reader_cursor_down(gr);
243 
244 				if (grid_get_line(gr->gd, gr->cy)->flags &
245 				    GRID_LINE_WRAPPED)
246 					xx = gr->gd->sx - 1;
247 				else
248 					xx = grid_reader_line_length(gr);
249 			} else
250 				gr->cx++;
251 		}
252 		expected = !expected;
253 	} while (expected == 0);
254 }
255 
256 /* Move to the previous place where a word begins. */
257 void
grid_reader_cursor_previous_word(struct grid_reader * gr,const char * separators,int already)258 grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
259     int already)
260 {
261 	int	oldx, oldy, r;
262 
263 	/* Move back to the previous word character. */
264 	if (already || grid_reader_in_set(gr, separators)) {
265 		for (;;) {
266 			if (gr->cx > 0) {
267 				gr->cx--;
268 				if (!grid_reader_in_set(gr, separators))
269 					break;
270 			} else {
271 				if (gr->cy == 0)
272 					return;
273 				grid_reader_cursor_up(gr);
274 				grid_reader_cursor_end_of_line(gr, 0, 0);
275 
276 				/* Stop if separator at EOL. */
277 				if (gr->cx > 0) {
278 					oldx = gr->cx;
279 					gr->cx--;
280 					r = grid_reader_in_set(gr, separators);
281 					gr->cx = oldx;
282 					if (r)
283 						break;
284 				}
285 			}
286 		}
287 	}
288 
289 	/* Move back to the beginning of this word. */
290 	do {
291 		oldx = gr->cx;
292 		oldy = gr->cy;
293 		if (gr->cx == 0) {
294 			if (gr->cy == 0 ||
295 			  ~grid_get_line(gr->gd, gr->cy - 1)->flags &
296 			  GRID_LINE_WRAPPED)
297 				break;
298 			grid_reader_cursor_up(gr);
299 			grid_reader_cursor_end_of_line(gr, 0, 1);
300 		}
301 		if (gr->cx > 0)
302 			gr->cx--;
303 	} while (!grid_reader_in_set(gr, separators));
304 	gr->cx = oldx;
305 	gr->cy = oldy;
306 }
307 
308 /* Jump forward to character. */
309 int
grid_reader_cursor_jump(struct grid_reader * gr,const struct utf8_data * jc)310 grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc)
311 {
312 	struct grid_cell	gc;
313 	u_int			px, py, xx, yy;
314 
315 	px = gr->cx;
316 	yy = gr->gd->hsize + gr->gd->sy - 1;
317 
318 	for (py = gr->cy; py <= yy; py++) {
319 		xx = grid_line_length(gr->gd, py);
320 		while (px < xx) {
321 			grid_get_cell(gr->gd, px, py, &gc);
322 			if (!(gc.flags & GRID_FLAG_PADDING) &&
323 			    gc.data.size == jc->size &&
324 			    memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
325 				gr->cx = px;
326 				gr->cy = py;
327 				return 1;
328 			}
329 			px++;
330 		}
331 
332 		if (py == yy ||
333 		    !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED))
334 			return 0;
335 		px = 0;
336 	}
337 	return 0;
338 }
339 
340 /* Jump back to character. */
341 int
grid_reader_cursor_jump_back(struct grid_reader * gr,const struct utf8_data * jc)342 grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc)
343 {
344 	struct grid_cell	gc;
345 	u_int			px, py, xx;
346 
347 	xx = gr->cx + 1;
348 
349 	for (py = gr->cy + 1; py > 0; py--) {
350 		for (px = xx; px > 0; px--) {
351 			grid_get_cell(gr->gd, px - 1, py - 1, &gc);
352 			if (!(gc.flags & GRID_FLAG_PADDING) &&
353 			    gc.data.size == jc->size &&
354 			    memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
355 				gr->cx = px - 1;
356 				gr->cy = py - 1;
357 				return 1;
358 			}
359 		}
360 
361 		if (py == 1 ||
362 		    !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED))
363 			return 0;
364 		xx = grid_line_length(gr->gd, py - 2);
365 	}
366 	return 0;
367 }
368 
369 /* Jump back to the first non-blank character of the line. */
370 void
grid_reader_cursor_back_to_indentation(struct grid_reader * gr)371 grid_reader_cursor_back_to_indentation(struct grid_reader *gr)
372 {
373 	struct grid_cell	gc;
374 	u_int			px, py, xx, yy, oldx, oldy;
375 
376 	yy = gr->gd->hsize + gr->gd->sy - 1;
377 	oldx = gr->cx;
378 	oldy = gr->cy;
379 	grid_reader_cursor_start_of_line(gr, 1);
380 
381 	for (py = gr->cy; py <= yy; py++) {
382 		xx = grid_line_length(gr->gd, py);
383 		for (px = 0; px < xx; px++) {
384 			grid_get_cell(gr->gd, px, py, &gc);
385 			if (gc.data.size != 1 || *gc.data.data != ' ') {
386 				gr->cx = px;
387 				gr->cy = py;
388 				return;
389 			}
390 		}
391 		if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)
392 			break;
393 	}
394 	gr->cx = oldx;
395 	gr->cy = oldy;
396 }
397