diff --git a/config.c b/config.c index f4bf1da0..be911adb 100644 --- a/config.c +++ b/config.c @@ -199,8 +199,17 @@ static const char *const vimode_search_binding_action_map[] = { [BIND_ACTION_VIMODE_SEARCH_CANCEL] = "vimode-search-cancel", [BIND_ACTION_VIMODE_SEARCH_CONFIRM] = "vimode-search-confirm", [BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_CHAR] = "vimode-search-delete-prev", + [BIND_ACTION_VIMODE_SEARCH_DELETE_NEXT_CHAR] = "vimode-search-delete-next", + [BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_WORD] = "vimode-search-delete-prev-word", + [BIND_ACTION_VIMODE_SEARCH_DELETE_NEXT_WORD] = "vimode-search-delete-next-word", + [BIND_ACTION_VIMODE_SEARCH_DELETE_TO_START] = "vimode-search-delete-to-start", + [BIND_ACTION_VIMODE_SEARCH_DELETE_TO_END] = "vimode-search-delete-to-end", [BIND_ACTION_VIMODE_SEARCH_LEFT] = "vimode-search-left", [BIND_ACTION_VIMODE_SEARCH_RIGHT] = "vimode-search-right", + [BIND_ACTION_VIMODE_SEARCH_LEFT_WORD] = "vimode-search-left-word", + [BIND_ACTION_VIMODE_SEARCH_RIGHT_WORD] = "vimode-search-right-word", + [BIND_ACTION_VIMODE_SEARCH_LINE_START] = "vimode-search-line-start", + [BIND_ACTION_VIMODE_SEARCH_LINE_END] = "vimode-search-line-end", [BIND_ACTION_VIMODE_SEARCH_UNICODE_INPUT] = "vimode-search-unicode-input", [BIND_ACTION_VIMODE_SEARCH_CLIPBOARD_PASTE] = "vimode-search-clipboard-paste", [BIND_ACTION_VIMODE_SEARCH_PRIMARY_PASTE] = "vimode-search-primary-paste", @@ -3338,10 +3347,21 @@ add_default_vimode_search_bindings(struct config *conf) {BIND_ACTION_VIMODE_SEARCH_CANCEL, m("none"), {{XKB_KEY_Escape}}}, {BIND_ACTION_VIMODE_SEARCH_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_c}}}, {BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_CHAR, m("none"), {{XKB_KEY_BackSpace}}}, + {BIND_ACTION_VIMODE_SEARCH_DELETE_NEXT_CHAR, m("none"), {{XKB_KEY_Delete}}}, + {BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_BackSpace}}}, + {BIND_ACTION_VIMODE_SEARCH_DELETE_NEXT_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_Delete}}}, + {BIND_ACTION_VIMODE_SEARCH_DELETE_TO_START, m(XKB_MOD_NAME_ALT), {{XKB_KEY_BackSpace}}}, + {BIND_ACTION_VIMODE_SEARCH_DELETE_TO_END, m(XKB_MOD_NAME_ALT), {{XKB_KEY_Delete}}}, {BIND_ACTION_VIMODE_SEARCH_LEFT, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_h}}}, {BIND_ACTION_VIMODE_SEARCH_LEFT, m("none"), {{XKB_KEY_leftarrow}}}, {BIND_ACTION_VIMODE_SEARCH_RIGHT, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_l}}}, {BIND_ACTION_VIMODE_SEARCH_RIGHT, m("none"), {{XKB_KEY_rightarrow}}}, + {BIND_ACTION_VIMODE_SEARCH_LEFT_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_b}}}, + {BIND_ACTION_VIMODE_SEARCH_RIGHT_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_e}}}, + {BIND_ACTION_VIMODE_SEARCH_LINE_START, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_k}}}, + {BIND_ACTION_VIMODE_SEARCH_LINE_START, m("none"), {{XKB_KEY_Home}}}, + {BIND_ACTION_VIMODE_SEARCH_LINE_END, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_j}}}, + {BIND_ACTION_VIMODE_SEARCH_LINE_END, m("none"), {{XKB_KEY_End}}}, {BIND_ACTION_VIMODE_SEARCH_CLIPBOARD_PASTE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_p}}}, {BIND_ACTION_VIMODE_SEARCH_CLIPBOARD_PASTE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_v}}}, {BIND_ACTION_VIMODE_SEARCH_CLIPBOARD_PASTE, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_v}}}, diff --git a/key-binding.h b/key-binding.h index 85e0d627..ca573630 100644 --- a/key-binding.h +++ b/key-binding.h @@ -103,8 +103,17 @@ enum bind_action_vimode_search { BIND_ACTION_VIMODE_SEARCH_CONFIRM, BIND_ACTION_VIMODE_SEARCH_CANCEL, BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_CHAR, + BIND_ACTION_VIMODE_SEARCH_DELETE_NEXT_CHAR, + BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_WORD, + BIND_ACTION_VIMODE_SEARCH_DELETE_NEXT_WORD, + BIND_ACTION_VIMODE_SEARCH_DELETE_TO_START, + BIND_ACTION_VIMODE_SEARCH_DELETE_TO_END, BIND_ACTION_VIMODE_SEARCH_LEFT, BIND_ACTION_VIMODE_SEARCH_RIGHT, + BIND_ACTION_VIMODE_SEARCH_LEFT_WORD, + BIND_ACTION_VIMODE_SEARCH_RIGHT_WORD, + BIND_ACTION_VIMODE_SEARCH_LINE_START, + BIND_ACTION_VIMODE_SEARCH_LINE_END, BIND_ACTION_VIMODE_SEARCH_UNICODE_INPUT, BIND_ACTION_VIMODE_SEARCH_CLIPBOARD_PASTE, BIND_ACTION_VIMODE_SEARCH_PRIMARY_PASTE, diff --git a/vimode.c b/vimode.c index 46353b4d..3e4c1c54 100644 --- a/vimode.c +++ b/vimode.c @@ -1437,6 +1437,100 @@ static void from_clipboard_done(void *user) on_search_string_updated(term); } +// Deletes characters from the search string backward from the cursor +// position. Deletes at most cursor characters. +// Returns true when any characters have been deleted. +// +static bool +delete_chars_from_search_backward(struct vimode_search *const search, int count) +{ + if (search->cursor > 0) { + count = count < search->cursor ? count : search->cursor; + memmove(&search->buf[search->cursor - count], + &search->buf[search->cursor], + (search->len - search->cursor) * sizeof(char32_t)); + search->cursor -= count; + search->len -= count; + search->buf[search->len] = U'\0'; + return true; + } + return false; +} + +// Deletes characters from the search string forward from the cursor +// position. Deletes at most length-cursor characters. +// Returns true when any characters have been deleted. +// +static bool delete_chars_from_search_forward(struct vimode_search *const search, + int count) +{ + if (search->cursor < search->len) { + count = count < search->len - search->cursor + ? count + : search->len - search->cursor; + memmove(&search->buf[search->cursor], + &search->buf[search->cursor + count], + (search->len - search->cursor - count) * sizeof(char32_t)); + search->len -= count; + search->buf[search->len] = U'\0'; + return true; + } + return false; +} + +static int distance_back_word(struct vimode_search *const search) +{ + int cursor = search->cursor; + if (cursor == 0) { + return 0; + } + + cursor -= 1; + + // Skip whitespace. + while (cursor > 0 && get_class(search->buf[cursor]) == CLASS_BLANK) { + cursor -= 1; + } + + // Go back to the start of the word. + enum c32_class const current_class = get_class(search->buf[cursor]); + while (cursor >= 0 && get_class(search->buf[cursor]) == current_class) { + cursor -= 1; + } + + // We overshot. Move forward one character. + cursor += 1; + + return search->cursor - cursor; +} + +static int distance_fwd_word(struct vimode_search *const search) +{ + int cursor = search->cursor; + if (cursor == search->len) { + return 0; + } + + enum c32_class const starting_class = get_class(search->buf[cursor]); + cursor += 1; + + // Go forward to the end of the word. + if (starting_class != CLASS_BLANK) { + while (cursor < search->len && + get_class(search->buf[cursor]) == starting_class) { + cursor += 1; + } + } + + // Skip whitespace. + while (cursor < search->len && + get_class(search->buf[cursor]) == CLASS_BLANK) { + cursor += 1; + } + + return cursor - search->cursor; +} + static void execute_vimode_search_binding(struct seat *seat, struct terminal *const term, const struct key_binding *binding, @@ -1471,27 +1565,82 @@ static void execute_vimode_search_binding(struct seat *seat, cancel_search(term, true); break; - case BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_CHAR: - if (search->cursor > 0) { - memmove(&search->buf[search->cursor - 1], - &search->buf[search->cursor], - (search->len - search->cursor) * sizeof(char32_t)); - search->cursor -= 1; - search->len -= 1; - search->buf[search->len] = U'\0'; - *search_string_changed = true; - } - break; + case BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_CHAR: { + bool const deleted = delete_chars_from_search_backward(search, 1); + *search_string_changed = deleted; + } break; + + case BIND_ACTION_VIMODE_SEARCH_DELETE_NEXT_CHAR: { + bool const deleted = delete_chars_from_search_forward(search, 1); + *search_string_changed = deleted; + } break; + + case BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_WORD: { + int const distance = distance_back_word(search); + bool const deleted = + delete_chars_from_search_backward(search, distance); + *search_string_changed = deleted; + } break; + + case BIND_ACTION_VIMODE_SEARCH_DELETE_NEXT_WORD: { + int const distance = distance_fwd_word(search); + bool const deleted = delete_chars_from_search_forward(search, distance); + *search_string_changed = deleted; + } break; + + case BIND_ACTION_VIMODE_SEARCH_DELETE_TO_START: { + bool const deleted = + delete_chars_from_search_backward(search, search->len); + *search_string_changed = deleted; + } break; + + case BIND_ACTION_VIMODE_SEARCH_DELETE_TO_END: { + bool const deleted = + delete_chars_from_search_forward(search, search->len); + *search_string_changed = deleted; + } break; case BIND_ACTION_VIMODE_SEARCH_LEFT: if (search->cursor > 0) { search->cursor -= 1; + render_refresh_vimode_search_box(term); } break; case BIND_ACTION_VIMODE_SEARCH_RIGHT: if (search->cursor < search->len) { search->cursor += 1; + render_refresh_vimode_search_box(term); + } + break; + + case BIND_ACTION_VIMODE_SEARCH_LEFT_WORD: + if (search->cursor > 0) { + int const distance = distance_back_word(search); + search->cursor -= distance; + render_refresh_vimode_search_box(term); + } + break; + + case BIND_ACTION_VIMODE_SEARCH_RIGHT_WORD: + if (search->cursor < search->len) { + int const distance = distance_fwd_word(search); + search->cursor += distance; + render_refresh_vimode_search_box(term); + } + break; + + case BIND_ACTION_VIMODE_SEARCH_LINE_START: + if (search->cursor > 0) { + search->cursor = 0; + render_refresh_vimode_search_box(term); + } + break; + + case BIND_ACTION_VIMODE_SEARCH_LINE_END: + if (search->cursor < search->len) { + search->cursor = search->len; + render_refresh_vimode_search_box(term); } break;