Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD$ */
      2 
      3 /*
      4  * Copyright (c) 2020 Anindya Mukherjee <anindya49 (at) 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
     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
     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
     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
     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
     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
     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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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