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 /* 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           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
grid_reader_cursor_next_word(struct grid_reader * gr,const char * separators)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
grid_reader_cursor_next_word_end(struct grid_reader * gr,const char * separators)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
grid_reader_cursor_previous_word(struct grid_reader * gr,const char * separators,int already,int stop_at_eol)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
grid_reader_cursor_jump(struct grid_reader * gr,const struct utf8_data * jc)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
grid_reader_cursor_jump_back(struct grid_reader * gr,const struct utf8_data * jc)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
grid_reader_cursor_back_to_indentation(struct grid_reader * gr)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