| /* $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; |
| } |