mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
motions
This commit is contained in:
parent
23c95f1dea
commit
26266dff3b
4 changed files with 341 additions and 62 deletions
4
char32.h
4
char32.h
|
|
@ -87,6 +87,10 @@ static inline bool hasc32upper(const char32_t *s) {
|
|||
return false;
|
||||
}
|
||||
|
||||
static inline bool isc32punct(char32_t c32) {
|
||||
return iswpunct((wint_t)c32);
|
||||
}
|
||||
|
||||
static inline int c32width(char32_t c) {
|
||||
#if defined(FOOT_GRAPHEME_CLUSTERING)
|
||||
return utf8proc_charwidth((utf8proc_int32_t)c);
|
||||
|
|
|
|||
15
config.c
15
config.c
|
|
@ -176,6 +176,13 @@ static const char *const vimode_binding_action_map[] = {
|
|||
[BIND_ACTION_VIMODE_DOWN_LINE] = "vimode-down-line",
|
||||
[BIND_ACTION_VIMODE_FIRST_LINE] = "vimode-first-line",
|
||||
[BIND_ACTION_VIMODE_LAST_LINE] = "vimode-last-line",
|
||||
[BIND_ACTION_VIMODE_LINE_BEGIN] = "vimode-line-begin",
|
||||
[BIND_ACTION_VIMODE_LINE_END] = "vimode-line-end",
|
||||
[BIND_ACTION_VIMODE_TEXT_BEGIN] = "vimode-text-begin",
|
||||
[BIND_ACTION_VIMODE_NEXT_WORD_BEGIN] = "vimode-next-word-begin",
|
||||
[BIND_ACTION_VIMODE_PREV_WORD_END] = "vimode-prev-word-end",
|
||||
[BIND_ACTION_VIMODE_WORD_BEGIN] = "vimode-word-begin",
|
||||
[BIND_ACTION_VIMODE_WORD_END] = "vimode-word-end",
|
||||
[BIND_ACTION_VIMODE_CANCEL] = "vimode-cancel",
|
||||
[BIND_ACTION_VIMODE_START_SEARCH_FORWARD] = "vimode-start-search-forward",
|
||||
[BIND_ACTION_VIMODE_START_SEARCH_BACKWARD] = "vimode-start-search-backward",
|
||||
|
|
@ -3282,6 +3289,14 @@ add_default_vimode_bindings(struct config *conf)
|
|||
{BIND_ACTION_VIMODE_DOWN_LINE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_e}}},
|
||||
{BIND_ACTION_VIMODE_FIRST_LINE, m("none"), {{XKB_KEY_g}}},
|
||||
{BIND_ACTION_VIMODE_LAST_LINE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_g}}},
|
||||
{BIND_ACTION_VIMODE_LINE_BEGIN, m("none"), {{XKB_KEY_0}}},
|
||||
{BIND_ACTION_VIMODE_LINE_END, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_4}}},
|
||||
{BIND_ACTION_VIMODE_TEXT_BEGIN, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_6}}},
|
||||
{BIND_ACTION_VIMODE_NEXT_WORD_BEGIN, m("none"), {{XKB_KEY_w}}},
|
||||
// TODO (kociap): PREV_WORD_END currently unbound. By default 'ge' in vim.
|
||||
// {BIND_ACTION_VIMODE_PREV_WORD_END, m("none"), {{XKB_KEY_}}},
|
||||
{BIND_ACTION_VIMODE_WORD_BEGIN, m("none"), {{XKB_KEY_b}}},
|
||||
{BIND_ACTION_VIMODE_WORD_END, m("none"), {{XKB_KEY_e}}},
|
||||
{BIND_ACTION_VIMODE_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_c}}},
|
||||
{BIND_ACTION_VIMODE_CANCEL, m("none"), {{XKB_KEY_Escape}}},
|
||||
{BIND_ACTION_VIMODE_START_SEARCH_FORWARD, m("none"), {{XKB_KEY_slash}}},
|
||||
|
|
|
|||
|
|
@ -78,6 +78,13 @@ enum bind_action_vimode {
|
|||
BIND_ACTION_VIMODE_DOWN_LINE,
|
||||
BIND_ACTION_VIMODE_FIRST_LINE,
|
||||
BIND_ACTION_VIMODE_LAST_LINE,
|
||||
BIND_ACTION_VIMODE_LINE_BEGIN,
|
||||
BIND_ACTION_VIMODE_LINE_END,
|
||||
BIND_ACTION_VIMODE_TEXT_BEGIN,
|
||||
BIND_ACTION_VIMODE_NEXT_WORD_BEGIN,
|
||||
BIND_ACTION_VIMODE_PREV_WORD_END,
|
||||
BIND_ACTION_VIMODE_WORD_BEGIN,
|
||||
BIND_ACTION_VIMODE_WORD_END,
|
||||
BIND_ACTION_VIMODE_CANCEL,
|
||||
BIND_ACTION_VIMODE_START_SEARCH_FORWARD,
|
||||
BIND_ACTION_VIMODE_START_SEARCH_BACKWARD,
|
||||
|
|
|
|||
377
vimode.c
377
vimode.c
|
|
@ -436,7 +436,7 @@ static ssize_t matches_cell(const struct terminal *term,
|
|||
if (composed == NULL && base == 0 && buf[search_ofs] == U' ')
|
||||
return 1;
|
||||
|
||||
if (c32ncasecmp(&base, &buf[search_ofs], 1) != 0)
|
||||
if (c32ncasecmp(&base, buf, 1) != 0)
|
||||
return -1;
|
||||
|
||||
if (composed != NULL) {
|
||||
|
|
@ -738,59 +738,6 @@ void search_add_chars(struct terminal *term, const char *src, size_t count) {
|
|||
add_wchars(term, c32s, chars);
|
||||
}
|
||||
|
||||
// static size_t distance_next_word(const struct terminal *term) {
|
||||
// size_t cursor = term->vimode.search.cursor;
|
||||
//
|
||||
// /* First eat non-whitespace. This is the word we're skipping past */
|
||||
// while (cursor < term->vimode.search.len) {
|
||||
// if (isc32space(term->vimode.search.buf[cursor++]))
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// xassert(cursor == term->vimode.search.len ||
|
||||
// isc32space(term->vimode.search.buf[cursor - 1]));
|
||||
//
|
||||
// /* Now skip past whitespace, so that we end up at the beginning of
|
||||
// * the next word */
|
||||
// while (cursor < term->vimode.search.len) {
|
||||
// if (!isc32space(term->vimode.search.buf[cursor++]))
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// xassert(cursor == term->vimode.search.len ||
|
||||
// !isc32space(term->vimode.search.buf[cursor - 1]));
|
||||
//
|
||||
// if (cursor < term->vimode.search.len &&
|
||||
// !isc32space(term->vimode.search.buf[cursor]))
|
||||
// cursor--;
|
||||
//
|
||||
// return cursor - term->vimode.search.cursor;
|
||||
// }
|
||||
|
||||
// static size_t distance_prev_word(const struct terminal *term) {
|
||||
// int cursor = term->vimode.search.cursor;
|
||||
//
|
||||
// /* First, eat whitespace prefix */
|
||||
// while (cursor > 0) {
|
||||
// if (!isc32space(term->vimode.search.buf[--cursor]))
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// xassert(cursor == 0 || !isc32space(term->vimode.search.buf[cursor]));
|
||||
//
|
||||
// /* Now eat non-whitespace. This is the word we're skipping past */
|
||||
// while (cursor > 0) {
|
||||
// if (isc32space(term->vimode.search.buf[--cursor]))
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// xassert(cursor == 0 || isc32space(term->vimode.search.buf[cursor]));
|
||||
// if (cursor > 0 && isc32space(term->vimode.search.buf[cursor]))
|
||||
// cursor++;
|
||||
//
|
||||
// return term->vimode.search.cursor - cursor;
|
||||
// }
|
||||
|
||||
void vimode_view_down(struct terminal *const term, int const delta) {
|
||||
if (!term->vimode.active) {
|
||||
return;
|
||||
|
|
@ -860,6 +807,254 @@ static void move_cursor_horizontal(struct terminal *const term,
|
|||
move_cursor_delta(term, (struct coord){.row = 0, .col = count});
|
||||
}
|
||||
|
||||
enum c32_class {
|
||||
CLASS_BLANK,
|
||||
CLASS_PUNCTUATION,
|
||||
CLASS_WORD,
|
||||
};
|
||||
|
||||
enum c32_class get_class(char32_t const c) {
|
||||
if (c == '\0') {
|
||||
return CLASS_BLANK;
|
||||
}
|
||||
|
||||
// We consider an underscore to be a word character instead of
|
||||
// punctuation.
|
||||
if (c == '_') {
|
||||
return CLASS_WORD;
|
||||
}
|
||||
|
||||
bool const whitespace = isc32space(c);
|
||||
if (whitespace) {
|
||||
return CLASS_BLANK;
|
||||
}
|
||||
|
||||
// TODO (kociap): unsure whether this handles all possible
|
||||
// punctuation, a subset of it or just the latin1 characters.
|
||||
bool const punctuation = isc32punct(c);
|
||||
if (punctuation) {
|
||||
return CLASS_PUNCTUATION;
|
||||
}
|
||||
|
||||
// Most other characters may be considered "word" characters.
|
||||
return CLASS_WORD;
|
||||
}
|
||||
|
||||
char32_t cursor_char(struct terminal *const term) {
|
||||
struct row *const row = grid_row(term->grid, term->vimode.cursor.row);
|
||||
return row->cells[term->vimode.cursor.col].wc;
|
||||
}
|
||||
|
||||
enum c32_class cursor_class(struct terminal *const term) {
|
||||
char32_t const c = cursor_char(term);
|
||||
return get_class(c);
|
||||
}
|
||||
|
||||
int row_length(struct terminal *const term, int const row_index) {
|
||||
struct row *const row = grid_row(term->grid, row_index);
|
||||
int length = 0;
|
||||
while (length < term->grid->num_cols) {
|
||||
if (row->cells[length].wc == '\0') {
|
||||
break;
|
||||
}
|
||||
length += 1;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
// increment_cursor
|
||||
//
|
||||
// Move the cursor to the next character, moving to the next row as
|
||||
// necessary.
|
||||
//
|
||||
// Returns:
|
||||
// false when at the end of the scrollback.
|
||||
// true otherwise
|
||||
//
|
||||
bool increment_cursor(struct terminal *const term) {
|
||||
char32_t const c = cursor_char(term);
|
||||
// Within a row, move to the next column.
|
||||
if (c != '\0') {
|
||||
term->vimode.cursor.col += 1;
|
||||
struct row const *const row = grid_row(term->grid, term->vimode.cursor.row);
|
||||
// If the row does not contain a linebreak, we want to move to the
|
||||
// next row immediately.
|
||||
if (row->linebreak || term->vimode.cursor.col < term->cols) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Not in the last row.
|
||||
if (term->vimode.cursor.row != term->rows - 1) {
|
||||
term->vimode.cursor.row += 1;
|
||||
term->vimode.cursor.col = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// decrement_cursor
|
||||
//
|
||||
// Move the cursor to the previous character, moving to the previous
|
||||
// row as necessary.
|
||||
//
|
||||
// Returns:
|
||||
// false when at the start of the scrollback.
|
||||
// true otherwise
|
||||
//
|
||||
bool decrement_cursor(struct terminal *const term) {
|
||||
// Within a row, move to the previous column.
|
||||
if (term->vimode.cursor.col > 0) {
|
||||
term->vimode.cursor.col -= 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO (kociap): this seems like a terrible way (performance-wise)
|
||||
// to figure out the current scrollback position. Could maybe store
|
||||
// it in the grid instead of recalculating it repeatedly?
|
||||
int const sb_start =
|
||||
grid_sb_start_ignore_uninitialized(term->grid, term->rows);
|
||||
int const sb_row = grid_row_abs_to_sb_precalc_sb_start(
|
||||
term->grid, sb_start,
|
||||
grid_row_absolute(term->grid, term->vimode.cursor.row));
|
||||
// Not in the first row.
|
||||
if (sb_row > 0) {
|
||||
term->vimode.cursor.row -= 1;
|
||||
term->vimode.cursor.col = row_length(term, term->vimode.cursor.row) - 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool skip_chars_forward(struct terminal *const term,
|
||||
enum c32_class const class) {
|
||||
while (cursor_class(term) == class) {
|
||||
if (increment_cursor(term) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool skip_chars_backward(struct terminal *const term,
|
||||
enum c32_class const class) {
|
||||
while (cursor_class(term) == class) {
|
||||
if (decrement_cursor(term) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// MOTIONS
|
||||
|
||||
void motion_begin_word(struct terminal *const term) {
|
||||
if (decrement_cursor(term) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip whitespace. If we encounter an empty row, stop.
|
||||
while (cursor_class(term) == CLASS_BLANK) {
|
||||
bool const row_empty = row_length(term, term->vimode.cursor.row) == 0;
|
||||
if (row_empty && term->vimode.cursor.col == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (decrement_cursor(term) == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Go to the start of the next word.
|
||||
enum c32_class const current_class = cursor_class(term);
|
||||
if (skip_chars_backward(term, current_class) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We overshot. Move forward one character.
|
||||
increment_cursor(term);
|
||||
}
|
||||
|
||||
void motion_end_word(struct terminal *const term) {
|
||||
if (increment_cursor(term) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip whitespace. If we encounter an empty row, stop.
|
||||
while (cursor_class(term) == CLASS_BLANK) {
|
||||
bool const row_empty = row_length(term, term->vimode.cursor.row) == 0;
|
||||
if (row_empty && term->vimode.cursor.col == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (increment_cursor(term) == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Go to the end of the next word.
|
||||
enum c32_class current_class = cursor_class(term);
|
||||
if (skip_chars_forward(term, current_class) == false) {
|
||||
return;
|
||||
}
|
||||
// We overshot. Go back one character.
|
||||
decrement_cursor(term);
|
||||
}
|
||||
|
||||
void motion_fwd_begin_word(struct terminal *const term) {
|
||||
enum c32_class const starting_class = cursor_class(term);
|
||||
if (increment_cursor(term) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to the end of this word.
|
||||
if (starting_class != CLASS_BLANK) {
|
||||
if (skip_chars_forward(term, starting_class) == false) {
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip whitespace. If we encounter an empty row, stop.
|
||||
while (cursor_class(term) == CLASS_BLANK) {
|
||||
bool const row_empty = row_length(term, term->vimode.cursor.row) == 0;
|
||||
if (row_empty && term->vimode.cursor.col == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (increment_cursor(term) == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void motion_back_end_word(struct terminal *const term) {
|
||||
enum c32_class const starting_class = cursor_class(term);
|
||||
if (decrement_cursor(term) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to before the start of this word.
|
||||
if (starting_class != CLASS_BLANK) {
|
||||
if (skip_chars_backward(term, starting_class) == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip whitespace. If we encounter an empty row, stop.
|
||||
while (cursor_class(term) == CLASS_BLANK) {
|
||||
bool const row_empty = row_length(term, term->vimode.cursor.row) == 0;
|
||||
if (row_empty && term->vimode.cursor.col == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (decrement_cursor(term) == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_vimode_binding(struct seat *seat, struct terminal *term,
|
||||
const struct key_binding *binding,
|
||||
uint32_t serial) {
|
||||
|
|
@ -946,8 +1141,7 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term,
|
|||
damage_cursor_cell(term);
|
||||
update_selection(term);
|
||||
update_highlights(term);
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case BIND_ACTION_VIMODE_LAST_LINE:
|
||||
cmd_scrollback_down(term, term->grid->num_rows);
|
||||
|
|
@ -958,6 +1152,68 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term,
|
|||
update_highlights(term);
|
||||
break;
|
||||
|
||||
case BIND_ACTION_VIMODE_LINE_BEGIN:
|
||||
damage_cursor_cell(term);
|
||||
term->vimode.cursor.col = 0;
|
||||
damage_cursor_cell(term);
|
||||
break;
|
||||
|
||||
case BIND_ACTION_VIMODE_LINE_END: {
|
||||
damage_cursor_cell(term);
|
||||
struct row const *const row = grid_row(term->grid, term->vimode.cursor.row);
|
||||
int col = term->cols - 1;
|
||||
while (col > 0) {
|
||||
if (row->cells[col].wc != '\0') {
|
||||
break;
|
||||
}
|
||||
col -= 1;
|
||||
}
|
||||
term->vimode.cursor.col = col;
|
||||
damage_cursor_cell(term);
|
||||
} break;
|
||||
|
||||
case BIND_ACTION_VIMODE_TEXT_BEGIN: {
|
||||
damage_cursor_cell(term);
|
||||
struct row const *const row = grid_row(term->grid, term->vimode.cursor.row);
|
||||
int col = 0;
|
||||
while (col < term->cols - 1) {
|
||||
if (isc32graph(row->cells[col].wc)) {
|
||||
break;
|
||||
}
|
||||
col += 1;
|
||||
}
|
||||
term->vimode.cursor.col = col;
|
||||
damage_cursor_cell(term);
|
||||
} break;
|
||||
|
||||
case BIND_ACTION_VIMODE_WORD_BEGIN:
|
||||
damage_cursor_cell(term);
|
||||
motion_begin_word(term);
|
||||
damage_cursor_cell(term);
|
||||
update_selection(term);
|
||||
break;
|
||||
|
||||
case BIND_ACTION_VIMODE_WORD_END:
|
||||
damage_cursor_cell(term);
|
||||
motion_end_word(term);
|
||||
damage_cursor_cell(term);
|
||||
update_selection(term);
|
||||
break;
|
||||
|
||||
case BIND_ACTION_VIMODE_NEXT_WORD_BEGIN:
|
||||
damage_cursor_cell(term);
|
||||
motion_fwd_begin_word(term);
|
||||
damage_cursor_cell(term);
|
||||
update_selection(term);
|
||||
break;
|
||||
|
||||
case BIND_ACTION_VIMODE_PREV_WORD_END:
|
||||
damage_cursor_cell(term);
|
||||
motion_back_end_word(term);
|
||||
damage_cursor_cell(term);
|
||||
update_selection(term);
|
||||
break;
|
||||
|
||||
case BIND_ACTION_VIMODE_CANCEL: {
|
||||
// We handle multiple actions here (in that exact order):
|
||||
// - clear search (handled by vimode-search bindings),
|
||||
|
|
@ -969,8 +1225,7 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term,
|
|||
} else {
|
||||
vimode_cancel(term);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case BIND_ACTION_VIMODE_START_SEARCH_FORWARD:
|
||||
start_search(term, SEARCH_FORWARD);
|
||||
|
|
@ -1004,8 +1259,7 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term,
|
|||
update_selection(term);
|
||||
}
|
||||
update_highlights(term);
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case BIND_ACTION_VIMODE_ENTER_VISUAL:
|
||||
case BIND_ACTION_VIMODE_ENTER_VLINE:
|
||||
|
|
@ -1041,8 +1295,7 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term,
|
|||
term->vimode.selection.start = cursor;
|
||||
term->vimode.mode = mode;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case BIND_ACTION_VIMODE_YANK:
|
||||
// TODO (kociap): Should yank executed in non-visual mode copy the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue