|  | /* $OpenBSD$ */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.com> | 
|  | * | 
|  | * Permission to use, copy, modify, and distribute this software for any | 
|  | * purpose with or without fee is hereby granted, provided that the above | 
|  | * copyright notice and this permission notice appear in all copies. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
|  | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
|  | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | 
|  | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
|  | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | 
|  | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | 
|  | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
|  | */ | 
|  |  | 
|  | #include "tmux.h" | 
|  | #include <string.h> | 
|  |  | 
|  | /* Initialise virtual cursor. */ | 
|  | void | 
|  | grid_reader_start(struct grid_reader *gr, struct grid *gd, u_int cx, u_int cy) | 
|  | { | 
|  | gr->gd = gd; | 
|  | gr->cx = cx; | 
|  | gr->cy = cy; | 
|  | } | 
|  |  | 
|  | /* Get cursor position from reader. */ | 
|  | void | 
|  | grid_reader_get_cursor(struct grid_reader *gr, u_int *cx, u_int *cy) | 
|  | { | 
|  | *cx = gr->cx; | 
|  | *cy = gr->cy; | 
|  | } | 
|  |  | 
|  | /* Get length of line containing the cursor. */ | 
|  | u_int | 
|  | grid_reader_line_length(struct grid_reader *gr) | 
|  | { | 
|  | return (grid_line_length(gr->gd, gr->cy)); | 
|  | } | 
|  |  | 
|  | /* Move cursor forward one position. */ | 
|  | void | 
|  | grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all) | 
|  | { | 
|  | u_int			px; | 
|  | struct grid_cell	gc; | 
|  |  | 
|  | if (all) | 
|  | px = gr->gd->sx; | 
|  | else | 
|  | px = grid_reader_line_length(gr); | 
|  |  | 
|  | if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) { | 
|  | grid_reader_cursor_start_of_line(gr, 0); | 
|  | grid_reader_cursor_down(gr); | 
|  | } else if (gr->cx < px) { | 
|  | gr->cx++; | 
|  | while (gr->cx < px) { | 
|  | grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); | 
|  | if (~gc.flags & GRID_FLAG_PADDING) | 
|  | break; | 
|  | gr->cx++; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Move cursor back one position. */ | 
|  | void | 
|  | grid_reader_cursor_left(struct grid_reader *gr, int wrap) | 
|  | { | 
|  | struct grid_cell	gc; | 
|  |  | 
|  | while (gr->cx > 0) { | 
|  | grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); | 
|  | if (~gc.flags & GRID_FLAG_PADDING) | 
|  | break; | 
|  | gr->cx--; | 
|  | } | 
|  | if (gr->cx == 0 && gr->cy > 0 && | 
|  | (wrap || | 
|  | grid_get_line(gr->gd, gr->cy - 1)->flags & GRID_LINE_WRAPPED)) { | 
|  | grid_reader_cursor_up(gr); | 
|  | grid_reader_cursor_end_of_line(gr, 0, 0); | 
|  | } else if (gr->cx > 0) | 
|  | gr->cx--; | 
|  | } | 
|  |  | 
|  | /* Move cursor down one line. */ | 
|  | void | 
|  | grid_reader_cursor_down(struct grid_reader *gr) | 
|  | { | 
|  | struct grid_cell	gc; | 
|  |  | 
|  | if (gr->cy < gr->gd->hsize + gr->gd->sy - 1) | 
|  | gr->cy++; | 
|  | while (gr->cx > 0) { | 
|  | grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); | 
|  | if (~gc.flags & GRID_FLAG_PADDING) | 
|  | break; | 
|  | gr->cx--; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Move cursor up one line. */ | 
|  | void | 
|  | grid_reader_cursor_up(struct grid_reader *gr) | 
|  | { | 
|  | struct grid_cell	gc; | 
|  |  | 
|  | if (gr->cy > 0) | 
|  | gr->cy--; | 
|  | while (gr->cx > 0) { | 
|  | grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); | 
|  | if (~gc.flags & GRID_FLAG_PADDING) | 
|  | break; | 
|  | gr->cx--; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Move cursor to the start of the line. */ | 
|  | void | 
|  | grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap) | 
|  | { | 
|  | if (wrap) { | 
|  | while (gr->cy > 0 && | 
|  | grid_get_line(gr->gd, gr->cy - 1)->flags & | 
|  | GRID_LINE_WRAPPED) | 
|  | gr->cy--; | 
|  | } | 
|  | gr->cx = 0; | 
|  | } | 
|  |  | 
|  | /* Move cursor to the end of the line. */ | 
|  | void | 
|  | grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all) | 
|  | { | 
|  | u_int	yy; | 
|  |  | 
|  | if (wrap) { | 
|  | yy = gr->gd->hsize + gr->gd->sy - 1; | 
|  | while (gr->cy < yy && grid_get_line(gr->gd, gr->cy)->flags & | 
|  | GRID_LINE_WRAPPED) | 
|  | gr->cy++; | 
|  | } | 
|  | if (all) | 
|  | gr->cx = gr->gd->sx; | 
|  | else | 
|  | gr->cx = grid_reader_line_length(gr); | 
|  | } | 
|  |  | 
|  | /* Handle line wrapping while moving the cursor. */ | 
|  | static int | 
|  | grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy) | 
|  | { | 
|  | /* | 
|  | * Make sure the cursor lies within the grid reader's bounding area, | 
|  | * wrapping to the next line as necessary. Return zero if the cursor | 
|  | * would wrap past the bottom of the grid. | 
|  | */ | 
|  | while (gr->cx > *xx) { | 
|  | if (gr->cy == *yy) | 
|  | return (0); | 
|  | grid_reader_cursor_start_of_line(gr, 0); | 
|  | grid_reader_cursor_down(gr); | 
|  |  | 
|  | if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) | 
|  | *xx = gr->gd->sx - 1; | 
|  | else | 
|  | *xx = grid_reader_line_length(gr); | 
|  | } | 
|  | return (1); | 
|  | } | 
|  |  | 
|  | /* Check if character under cursor is in set. */ | 
|  | int | 
|  | grid_reader_in_set(struct grid_reader *gr, const char *set) | 
|  | { | 
|  | struct grid_cell	gc; | 
|  |  | 
|  | grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); | 
|  | if (gc.flags & GRID_FLAG_PADDING) | 
|  | return (0); | 
|  | return (utf8_cstrhas(set, &gc.data)); | 
|  | } | 
|  |  | 
|  | /* Move cursor to the start of the next word. */ | 
|  | void | 
|  | grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators) | 
|  | { | 
|  | u_int	xx, yy; | 
|  |  | 
|  | /* Do not break up wrapped words. */ | 
|  | if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) | 
|  | xx = gr->gd->sx - 1; | 
|  | else | 
|  | xx = grid_reader_line_length(gr); | 
|  | yy = gr->gd->hsize + gr->gd->sy - 1; | 
|  |  | 
|  | /* | 
|  | * When navigating via spaces (for example with next-space) separators | 
|  | * should be empty. | 
|  | * | 
|  | * If we started on a separator that is not whitespace, skip over | 
|  | * subsequent separators that are not whitespace. Otherwise, if we | 
|  | * started on a non-whitespace character, skip over subsequent | 
|  | * characters that are neither whitespace nor separators. Then, skip | 
|  | * over whitespace (if any) until the next non-whitespace character. | 
|  | */ | 
|  | if (!grid_reader_handle_wrap(gr, &xx, &yy)) | 
|  | return; | 
|  | if (!grid_reader_in_set(gr, WHITESPACE)) { | 
|  | if (grid_reader_in_set(gr, separators)) { | 
|  | do | 
|  | gr->cx++; | 
|  | while (grid_reader_handle_wrap(gr, &xx, &yy) && | 
|  | grid_reader_in_set(gr, separators) && | 
|  | !grid_reader_in_set(gr, WHITESPACE)); | 
|  | } else { | 
|  | do | 
|  | gr->cx++; | 
|  | while (grid_reader_handle_wrap(gr, &xx, &yy) && | 
|  | !(grid_reader_in_set(gr, separators) || | 
|  | grid_reader_in_set(gr, WHITESPACE))); | 
|  | } | 
|  | } | 
|  | while (grid_reader_handle_wrap(gr, &xx, &yy) && | 
|  | grid_reader_in_set(gr, WHITESPACE)) | 
|  | gr->cx++; | 
|  | } | 
|  |  | 
|  | /* Move cursor to the end of the next word. */ | 
|  | void | 
|  | grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators) | 
|  | { | 
|  | u_int	xx, yy; | 
|  |  | 
|  | /* Do not break up wrapped words. */ | 
|  | if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) | 
|  | xx = gr->gd->sx - 1; | 
|  | else | 
|  | xx = grid_reader_line_length(gr); | 
|  | yy = gr->gd->hsize + gr->gd->sy - 1; | 
|  |  | 
|  | /* | 
|  | * When navigating via spaces (for example with next-space), separators | 
|  | * should be empty in both modes. | 
|  | * | 
|  | * If we started on a whitespace, move until reaching the first | 
|  | * non-whitespace character. If that character is a separator, treat | 
|  | * subsequent separators as a word, and continue moving until the first | 
|  | * non-separator. Otherwise, continue moving until the first separator | 
|  | * or whitespace. | 
|  | */ | 
|  |  | 
|  | while (grid_reader_handle_wrap(gr, &xx, &yy)) { | 
|  | if (grid_reader_in_set(gr, WHITESPACE)) | 
|  | gr->cx++; | 
|  | else if (grid_reader_in_set(gr, separators)) { | 
|  | do | 
|  | gr->cx++; | 
|  | while (grid_reader_handle_wrap(gr, &xx, &yy) && | 
|  | grid_reader_in_set(gr, separators) && | 
|  | !grid_reader_in_set(gr, WHITESPACE)); | 
|  | return; | 
|  | } else { | 
|  | do | 
|  | gr->cx++; | 
|  | while (grid_reader_handle_wrap(gr, &xx, &yy) && | 
|  | !(grid_reader_in_set(gr, WHITESPACE) || | 
|  | grid_reader_in_set(gr, separators))); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Move to the previous place where a word begins. */ | 
|  | void | 
|  | grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, | 
|  | int already, int stop_at_eol) | 
|  | { | 
|  | int	oldx, oldy, at_eol, word_is_letters; | 
|  |  | 
|  | /* Move back to the previous word character. */ | 
|  | if (already || grid_reader_in_set(gr, WHITESPACE)) { | 
|  | for (;;) { | 
|  | if (gr->cx > 0) { | 
|  | gr->cx--; | 
|  | if (!grid_reader_in_set(gr, WHITESPACE)) { | 
|  | word_is_letters = | 
|  | !grid_reader_in_set(gr, separators); | 
|  | break; | 
|  | } | 
|  | } else { | 
|  | if (gr->cy == 0) | 
|  | return; | 
|  | grid_reader_cursor_up(gr); | 
|  | grid_reader_cursor_end_of_line(gr, 0, 0); | 
|  |  | 
|  | /* Stop if separator at EOL. */ | 
|  | if (stop_at_eol && gr->cx > 0) { | 
|  | oldx = gr->cx; | 
|  | gr->cx--; | 
|  | at_eol = grid_reader_in_set(gr, | 
|  | WHITESPACE); | 
|  | gr->cx = oldx; | 
|  | if (at_eol) { | 
|  | word_is_letters = 0; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } else | 
|  | word_is_letters = !grid_reader_in_set(gr, separators); | 
|  |  | 
|  | /* Move back to the beginning of this word. */ | 
|  | do { | 
|  | oldx = gr->cx; | 
|  | oldy = gr->cy; | 
|  | if (gr->cx == 0) { | 
|  | if (gr->cy == 0 || | 
|  | (~grid_get_line(gr->gd, gr->cy - 1)->flags & | 
|  | GRID_LINE_WRAPPED)) | 
|  | break; | 
|  | grid_reader_cursor_up(gr); | 
|  | grid_reader_cursor_end_of_line(gr, 0, 1); | 
|  | } | 
|  | if (gr->cx > 0) | 
|  | gr->cx--; | 
|  | } while (!grid_reader_in_set(gr, WHITESPACE) && | 
|  | word_is_letters != grid_reader_in_set(gr, separators)); | 
|  | gr->cx = oldx; | 
|  | gr->cy = oldy; | 
|  | } | 
|  |  | 
|  | /* Jump forward to character. */ | 
|  | int | 
|  | grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc) | 
|  | { | 
|  | struct grid_cell	gc; | 
|  | u_int			px, py, xx, yy; | 
|  |  | 
|  | px = gr->cx; | 
|  | yy = gr->gd->hsize + gr->gd->sy - 1; | 
|  |  | 
|  | for (py = gr->cy; py <= yy; py++) { | 
|  | xx = grid_line_length(gr->gd, py); | 
|  | while (px < xx) { | 
|  | grid_get_cell(gr->gd, px, py, &gc); | 
|  | if (!(gc.flags & GRID_FLAG_PADDING) && | 
|  | gc.data.size == jc->size && | 
|  | memcmp(gc.data.data, jc->data, gc.data.size) == 0) { | 
|  | gr->cx = px; | 
|  | gr->cy = py; | 
|  | return (1); | 
|  | } | 
|  | px++; | 
|  | } | 
|  |  | 
|  | if (py == yy || | 
|  | !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)) | 
|  | return (0); | 
|  | px = 0; | 
|  | } | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* Jump back to character. */ | 
|  | int | 
|  | grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc) | 
|  | { | 
|  | struct grid_cell	gc; | 
|  | u_int			px, py, xx; | 
|  |  | 
|  | xx = gr->cx + 1; | 
|  |  | 
|  | for (py = gr->cy + 1; py > 0; py--) { | 
|  | for (px = xx; px > 0; px--) { | 
|  | grid_get_cell(gr->gd, px - 1, py - 1, &gc); | 
|  | if (!(gc.flags & GRID_FLAG_PADDING) && | 
|  | gc.data.size == jc->size && | 
|  | memcmp(gc.data.data, jc->data, gc.data.size) == 0) { | 
|  | gr->cx = px - 1; | 
|  | gr->cy = py - 1; | 
|  | return (1); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (py == 1 || | 
|  | !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED)) | 
|  | return (0); | 
|  | xx = grid_line_length(gr->gd, py - 2); | 
|  | } | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* Jump back to the first non-blank character of the line. */ | 
|  | void | 
|  | grid_reader_cursor_back_to_indentation(struct grid_reader *gr) | 
|  | { | 
|  | struct grid_cell	gc; | 
|  | u_int			px, py, xx, yy, oldx, oldy; | 
|  |  | 
|  | yy = gr->gd->hsize + gr->gd->sy - 1; | 
|  | oldx = gr->cx; | 
|  | oldy = gr->cy; | 
|  | grid_reader_cursor_start_of_line(gr, 1); | 
|  |  | 
|  | for (py = gr->cy; py <= yy; py++) { | 
|  | xx = grid_line_length(gr->gd, py); | 
|  | for (px = 0; px < xx; px++) { | 
|  | grid_get_cell(gr->gd, px, py, &gc); | 
|  | if (gc.data.size != 1 || *gc.data.data != ' ') { | 
|  | gr->cx = px; | 
|  | gr->cy = py; | 
|  | return; | 
|  | } | 
|  | } | 
|  | if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED) | 
|  | break; | 
|  | } | 
|  | gr->cx = oldx; | 
|  | gr->cy = oldy; | 
|  | } |