Merge branch 'obsd-master'
[tmux.git] / grid-reader.c
blobbe374cd7ff010288b999c4fa7f5046b7f1bb26cb
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.com>
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.
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.
19 #include "tmux.h"
20 #include <string.h>
22 /* Initialise virtual cursor. */
23 void
24 grid_reader_start(struct grid_reader *gr, struct grid *gd, u_int cx, u_int cy)
26 gr->gd = gd;
27 gr->cx = cx;
28 gr->cy = cy;
31 /* Get cursor position from reader. */
32 void
33 grid_reader_get_cursor(struct grid_reader *gr, u_int *cx, u_int *cy)
35 *cx = gr->cx;
36 *cy = gr->cy;
39 /* Get length of line containing the cursor. */
40 u_int
41 grid_reader_line_length(struct grid_reader *gr)
43 return (grid_line_length(gr->gd, gr->cy));
46 /* Move cursor forward one position. */
47 void
48 grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all)
50 u_int px;
51 struct grid_cell gc;
53 if (all)
54 px = gr->gd->sx;
55 else
56 px = grid_reader_line_length(gr);
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++;
72 /* Move cursor back one position. */
73 void
74 grid_reader_cursor_left(struct grid_reader *gr, int wrap)
76 struct grid_cell gc;
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--;
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--;
93 /* Move cursor down one line. */
94 void
95 grid_reader_cursor_down(struct grid_reader *gr)
97 struct grid_cell gc;
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--;
109 /* Move cursor up one line. */
110 void
111 grid_reader_cursor_up(struct grid_reader *gr)
113 struct grid_cell gc;
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--;
125 /* Move cursor to the start of the line. */
126 void
127 grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap)
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--;
135 gr->cx = 0;
138 /* Move cursor to the end of the line. */
139 void
140 grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all)
142 u_int yy;
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++;
150 if (all)
151 gr->cx = gr->gd->sx;
152 else
153 gr->cx = grid_reader_line_length(gr);
156 /* Handle line wrapping while moving the cursor. */
157 static int
158 grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy)
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.
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);
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);
176 return (1);
179 /* Check if character under cursor is in set. */
181 grid_reader_in_set(struct grid_reader *gr, const char *set)
183 return (grid_in_set(gr->gd, gr->cx, gr->cy, set));
186 /* Move cursor to the start of the next word. */
187 void
188 grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
190 u_int xx, yy, width;
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;
200 * When navigating via spaces (for example with next-space) separators
201 * should be empty.
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.
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)) {
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 {
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)));
226 while (grid_reader_handle_wrap(gr, &xx, &yy) &&
227 (width = grid_reader_in_set(gr, WHITESPACE)))
228 gr->cx += width;
231 /* Move cursor to the end of the next word. */
232 void
233 grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
235 u_int xx, yy;
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;
245 * When navigating via spaces (for example with next-space), separators
246 * should be empty in both modes.
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.
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)) {
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 {
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;
276 /* Move to the previous place where a word begins. */
277 void
278 grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
279 int already, int stop_at_eol)
281 int oldx, oldy, at_eol, word_is_letters;
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;
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);
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;
313 } else
314 word_is_letters = !grid_reader_in_set(gr, separators);
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);
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;
336 /* Compare grid cell to UTF-8 data. Return 1 if equal, 0 if not. */
337 static int
338 grid_reader_cell_equals_data(const struct grid_cell *gc,
339 const struct utf8_data *ud)
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);
350 /* Jump forward to character. */
352 grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc)
354 struct grid_cell gc;
355 u_int px, py, xx, yy;
357 px = gr->cx;
358 yy = gr->gd->hsize + gr->gd->sy - 1;
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);
369 px++;
372 if (py == yy ||
373 !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED))
374 return (0);
375 px = 0;
377 return (0);
380 /* Jump back to character. */
382 grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc)
384 struct grid_cell gc;
385 u_int px, py, xx;
387 xx = gr->cx + 1;
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);
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);
404 return (0);
407 /* Jump back to the first non-blank character of the line. */
408 void
409 grid_reader_cursor_back_to_indentation(struct grid_reader *gr)
411 struct grid_cell gc;
412 u_int px, py, xx, yy, oldx, oldy;
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);
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;
431 if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)
432 break;
434 gr->cx = oldx;
435 gr->cy = oldy;