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. */
23void
24grid_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. */
32void
33grid_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. */
40u_int
41grid_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. */
47void
48grid_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. */
73void
74grid_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. */
94void
95grid_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. */
110void
111grid_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. */
126void
127grid_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. */
139void
140grid_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. */
157static int
158grid_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. */
180int
181grid_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. */
192void
193grid_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. */
237void
238grid_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. */
282void
283grid_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. */
342int
343grid_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. */
374int
375grid_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. */
403void
404grid_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