diff --git a/config.c b/config.c index 515b088c..5ea26454 100644 --- a/config.c +++ b/config.c @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include @@ -120,7 +122,8 @@ static const char *const binding_action_map[] = { [BIND_ACTION_CLIPBOARD_COPY] = "clipboard-copy", [BIND_ACTION_CLIPBOARD_PASTE] = "clipboard-paste", [BIND_ACTION_PRIMARY_PASTE] = "primary-paste", - [BIND_ACTION_SEARCH_START] = "search-start", + [BIND_ACTION_START_VIMODE] = "start-vimode", + [BIND_ACTION_START_VIMODE_SEARCH] = "start-vimode-search", [BIND_ACTION_FONT_SIZE_UP] = "font-increase", [BIND_ACTION_FONT_SIZE_DOWN] = "font-decrease", [BIND_ACTION_FONT_SIZE_RESET] = "font-reset", @@ -159,43 +162,37 @@ static const char *const binding_action_map[] = { [BIND_ACTION_SELECT_ROW] = "select-row", }; -static const char *const search_binding_action_map[] = { - [BIND_ACTION_SEARCH_NONE] = NULL, - [BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE] = "scrollback-up-page", - [BIND_ACTION_SEARCH_SCROLLBACK_UP_HALF_PAGE] = "scrollback-up-half-page", - [BIND_ACTION_SEARCH_SCROLLBACK_UP_LINE] = "scrollback-up-line", - [BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE] = "scrollback-down-page", - [BIND_ACTION_SEARCH_SCROLLBACK_DOWN_HALF_PAGE] = "scrollback-down-half-page", - [BIND_ACTION_SEARCH_SCROLLBACK_DOWN_LINE] = "scrollback-down-line", - [BIND_ACTION_SEARCH_SCROLLBACK_HOME] = "scrollback-home", - [BIND_ACTION_SEARCH_SCROLLBACK_END] = "scrollback-end", - [BIND_ACTION_SEARCH_CANCEL] = "cancel", - [BIND_ACTION_SEARCH_COMMIT] = "commit", - [BIND_ACTION_SEARCH_FIND_PREV] = "find-prev", - [BIND_ACTION_SEARCH_FIND_NEXT] = "find-next", - [BIND_ACTION_SEARCH_EDIT_LEFT] = "cursor-left", - [BIND_ACTION_SEARCH_EDIT_LEFT_WORD] = "cursor-left-word", - [BIND_ACTION_SEARCH_EDIT_RIGHT] = "cursor-right", - [BIND_ACTION_SEARCH_EDIT_RIGHT_WORD] = "cursor-right-word", - [BIND_ACTION_SEARCH_EDIT_HOME] = "cursor-home", - [BIND_ACTION_SEARCH_EDIT_END] = "cursor-end", - [BIND_ACTION_SEARCH_DELETE_PREV] = "delete-prev", - [BIND_ACTION_SEARCH_DELETE_PREV_WORD] = "delete-prev-word", - [BIND_ACTION_SEARCH_DELETE_NEXT] = "delete-next", - [BIND_ACTION_SEARCH_DELETE_NEXT_WORD] = "delete-next-word", - [BIND_ACTION_SEARCH_DELETE_TO_START] = "delete-to-start", - [BIND_ACTION_SEARCH_DELETE_TO_END] = "delete-to-end", - [BIND_ACTION_SEARCH_EXTEND_CHAR] = "extend-char", - [BIND_ACTION_SEARCH_EXTEND_WORD] = "extend-to-word-boundary", - [BIND_ACTION_SEARCH_EXTEND_WORD_WS] = "extend-to-next-whitespace", - [BIND_ACTION_SEARCH_EXTEND_LINE_DOWN] = "extend-line-down", - [BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR] = "extend-backward-char", - [BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD] = "extend-backward-to-word-boundary", - [BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS] = "extend-backward-to-next-whitespace", - [BIND_ACTION_SEARCH_EXTEND_LINE_UP] = "extend-line-up", - [BIND_ACTION_SEARCH_CLIPBOARD_PASTE] = "clipboard-paste", - [BIND_ACTION_SEARCH_PRIMARY_PASTE] = "primary-paste", - [BIND_ACTION_SEARCH_UNICODE_INPUT] = "unicode-input", +static const char *const vimode_binding_action_map[] = { + [BIND_ACTION_VIMODE_NONE] = NULL, + [BIND_ACTION_VIMODE_UP] = "vimode-up", + [BIND_ACTION_VIMODE_DOWN] = "vimode-down", + [BIND_ACTION_VIMODE_LEFT] = "vimode-left", + [BIND_ACTION_VIMODE_RIGHT] = "vimode-right", + [BIND_ACTION_VIMODE_UP_PAGE] = "vimode-up-page", + [BIND_ACTION_VIMODE_DOWN_PAGE] = "vimode-down-page", + [BIND_ACTION_VIMODE_UP_HALF_PAGE] = "vimode-up-half-page", + [BIND_ACTION_VIMODE_DOWN_HALF_PAGE] = "vimode-down-half-page", + [BIND_ACTION_VIMODE_UP_LINE] = "vimode-up-line", + [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_CANCEL] = "vimode-cancel", + [BIND_ACTION_VIMODE_START_SEARCH] = "vimode-start-search", + [BIND_ACTION_VIMODE_FIND_NEXT] = "vimode-find-next", + [BIND_ACTION_VIMODE_FIND_PREV] = "vimode-find-prev", + [BIND_ACTION_VIMODE_ENTER_VISUAL] = "vimode-enter-visual", + [BIND_ACTION_VIMODE_ENTER_VLINE] = "vimode-enter-visual-line", + [BIND_ACTION_VIMODE_ENTER_VBLOCK] = "vimode-enter-visual-block", + [BIND_ACTION_VIMODE_YANK] = "vimode-yank", +}; + +static const char *const vimode_search_binding_action_map[] = { + [BIND_ACTION_VIMODE_NONE] = NULL, + [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_LEFT] = "vimode-search-left", + [BIND_ACTION_VIMODE_SEARCH_RIGHT] = "vimode-search-right", }; static const char *const url_binding_action_map[] = { @@ -206,8 +203,10 @@ static const char *const url_binding_action_map[] = { static_assert(ALEN(binding_action_map) == BIND_ACTION_COUNT, "binding action map size mismatch"); -static_assert(ALEN(search_binding_action_map) == BIND_ACTION_SEARCH_COUNT, - "search binding action map size mismatch"); +static_assert(ALEN(vimode_binding_action_map) == BIND_ACTION_VIMODE_COUNT, + "vimode binding action map size mismatch"); +static_assert(ALEN(vimode_search_binding_action_map) == BIND_ACTION_VIMODE_SEARCH_COUNT, + "vimode-search binding action map size mismatch"); static_assert(ALEN(url_binding_action_map) == BIND_ACTION_URL_COUNT, "URL binding action map size mismatch"); @@ -2388,12 +2387,21 @@ parse_section_key_bindings(struct context *ctx) } static bool -parse_section_search_bindings(struct context *ctx) +parse_section_vimode_bindings(struct context *ctx) { return parse_key_binding_section( ctx, - BIND_ACTION_SEARCH_COUNT, search_binding_action_map, - &ctx->conf->bindings.search); + BIND_ACTION_VIMODE_COUNT, vimode_binding_action_map, + &ctx->conf->bindings.vimode); +} + +static bool +parse_section_vimode_search_bindings(struct context *ctx) +{ + return parse_key_binding_section( + ctx, + BIND_ACTION_VIMODE_SEARCH_COUNT, vimode_search_binding_action_map, + &ctx->conf->bindings.vimode_search); } static bool @@ -2964,7 +2972,8 @@ enum section { SECTION_MOUSE, SECTION_CSD, SECTION_KEY_BINDINGS, - SECTION_SEARCH_BINDINGS, + SECTION_VIMODE_BINDINGS, + SECTION_VIMODE_SEARCH_BINDINGS, SECTION_URL_BINDINGS, SECTION_MOUSE_BINDINGS, SECTION_TEXT_BINDINGS, @@ -2995,7 +3004,8 @@ static const struct { [SECTION_MOUSE] = {&parse_section_mouse, "mouse"}, [SECTION_CSD] = {&parse_section_csd, "csd"}, [SECTION_KEY_BINDINGS] = {&parse_section_key_bindings, "key-bindings"}, - [SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"}, + [SECTION_VIMODE_BINDINGS] = {&parse_section_vimode_bindings, "vimode-bindings"}, + [SECTION_VIMODE_SEARCH_BINDINGS] = {&parse_section_vimode_search_bindings, "vimode-search-bindings"}, [SECTION_URL_BINDINGS] = {&parse_section_url_bindings, "url-bindings"}, [SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"}, [SECTION_TEXT_BINDINGS] = {&parse_section_text_bindings, "text-bindings"}, @@ -3234,7 +3244,8 @@ add_default_key_bindings(struct config *conf) {BIND_ACTION_CLIPBOARD_PASTE, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_v}}}, {BIND_ACTION_CLIPBOARD_PASTE, m("none"), {{XKB_KEY_XF86Paste}}}, {BIND_ACTION_PRIMARY_PASTE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Insert}}}, - {BIND_ACTION_SEARCH_START, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_r}}}, + {BIND_ACTION_START_VIMODE, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_r}}}, + {BIND_ACTION_START_VIMODE_SEARCH, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_slash}}}, {BIND_ACTION_FONT_SIZE_UP, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_plus}}}, {BIND_ACTION_FONT_SIZE_UP, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_equal}}}, {BIND_ACTION_FONT_SIZE_UP, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_KP_Add}}}, @@ -3255,57 +3266,51 @@ add_default_key_bindings(struct config *conf) static void -add_default_search_bindings(struct config *conf) +add_default_vimode_bindings(struct config *conf) { const struct config_key_binding bindings[] = { - {BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Prior}}}, - {BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_KP_Prior}}}, - {BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Next}}}, - {BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_KP_Next}}}, - {BIND_ACTION_SEARCH_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_c}}}, - {BIND_ACTION_SEARCH_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_g}}}, - {BIND_ACTION_SEARCH_CANCEL, m("none"), {{XKB_KEY_Escape}}}, - {BIND_ACTION_SEARCH_COMMIT, m("none"), {{XKB_KEY_Return}}}, - {BIND_ACTION_SEARCH_COMMIT, m("none"), {{XKB_KEY_KP_Enter}}}, - {BIND_ACTION_SEARCH_FIND_PREV, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_r}}}, - {BIND_ACTION_SEARCH_FIND_NEXT, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_s}}}, - {BIND_ACTION_SEARCH_EDIT_LEFT, m("none"), {{XKB_KEY_Left}}}, - {BIND_ACTION_SEARCH_EDIT_LEFT, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_b}}}, - {BIND_ACTION_SEARCH_EDIT_LEFT_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_Left}}}, - {BIND_ACTION_SEARCH_EDIT_LEFT_WORD, m(XKB_MOD_NAME_ALT), {{XKB_KEY_b}}}, - {BIND_ACTION_SEARCH_EDIT_RIGHT, m("none"), {{XKB_KEY_Right}}}, - {BIND_ACTION_SEARCH_EDIT_RIGHT, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_f}}}, - {BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_Right}}}, - {BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, m(XKB_MOD_NAME_ALT), {{XKB_KEY_f}}}, - {BIND_ACTION_SEARCH_EDIT_HOME, m("none"), {{XKB_KEY_Home}}}, - {BIND_ACTION_SEARCH_EDIT_HOME, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_a}}}, - {BIND_ACTION_SEARCH_EDIT_END, m("none"), {{XKB_KEY_End}}}, - {BIND_ACTION_SEARCH_EDIT_END, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_e}}}, - {BIND_ACTION_SEARCH_DELETE_PREV, m("none"), {{XKB_KEY_BackSpace}}}, - {BIND_ACTION_SEARCH_DELETE_PREV_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_BackSpace}}}, - {BIND_ACTION_SEARCH_DELETE_PREV_WORD, m(XKB_MOD_NAME_ALT), {{XKB_KEY_BackSpace}}}, - {BIND_ACTION_SEARCH_DELETE_NEXT, m("none"), {{XKB_KEY_Delete}}}, - {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_Delete}}}, - {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m(XKB_MOD_NAME_ALT), {{XKB_KEY_d}}}, - {BIND_ACTION_SEARCH_DELETE_TO_START, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_u}}}, - {BIND_ACTION_SEARCH_DELETE_TO_END, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_k}}}, - {BIND_ACTION_SEARCH_EXTEND_CHAR, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Right}}}, - {BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_Right}}}, - {BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_w}}}, - {BIND_ACTION_SEARCH_EXTEND_WORD_WS, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_w}}}, - {BIND_ACTION_SEARCH_EXTEND_LINE_DOWN, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Down}}}, - {BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Left}}}, - {BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_Left}}}, - {BIND_ACTION_SEARCH_EXTEND_LINE_UP, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Up}}}, - {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_v}}}, - {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_v}}}, - {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_y}}}, - {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m("none"), {{XKB_KEY_XF86Paste}}}, - {BIND_ACTION_SEARCH_PRIMARY_PASTE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Insert}}}, + {BIND_ACTION_VIMODE_UP, m("none"), {{XKB_KEY_k}}}, + {BIND_ACTION_VIMODE_DOWN, m("none"), {{XKB_KEY_j}}}, + {BIND_ACTION_VIMODE_LEFT, m("none"), {{XKB_KEY_h}}}, + {BIND_ACTION_VIMODE_RIGHT, m("none"), {{XKB_KEY_l}}}, + {BIND_ACTION_VIMODE_UP_PAGE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_b}}}, + {BIND_ACTION_VIMODE_DOWN_PAGE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_f}}}, + {BIND_ACTION_VIMODE_UP_HALF_PAGE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_u}}}, + {BIND_ACTION_VIMODE_DOWN_HALF_PAGE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_d}}}, + {BIND_ACTION_VIMODE_UP_LINE, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_y}}}, + {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_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_c}}}, + {BIND_ACTION_VIMODE_CANCEL, m("none"), {{XKB_KEY_Escape}}}, + {BIND_ACTION_VIMODE_START_SEARCH, m("none"), {{XKB_KEY_slash}}}, + {BIND_ACTION_VIMODE_FIND_NEXT, m("none"), {{XKB_KEY_n}}}, + {BIND_ACTION_VIMODE_FIND_PREV, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_n}}}, + {BIND_ACTION_VIMODE_ENTER_VISUAL, m("none"), {{XKB_KEY_v}}}, + {BIND_ACTION_VIMODE_ENTER_VLINE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_v}}}, + {BIND_ACTION_VIMODE_ENTER_VBLOCK, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_v}}}, + {BIND_ACTION_VIMODE_YANK, m("none"), {{XKB_KEY_y}}}, }; - conf->bindings.search.count = ALEN(bindings); - conf->bindings.search.arr = xmemdup(bindings, sizeof(bindings)); + conf->bindings.vimode.count = ALEN(bindings); + conf->bindings.vimode.arr = xmemdup(bindings, sizeof(bindings)); +} + +static void +add_default_vimode_search_bindings(struct config *conf) +{ + const struct config_key_binding bindings[] = { + {BIND_ACTION_VIMODE_SEARCH_CONFIRM, m("none"), {{XKB_KEY_Return}}}, + {BIND_ACTION_VIMODE_SEARCH_CANCEL, m("none"), {{XKB_KEY_Escape}}}, + {BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_CHAR, m("none"), {{XKB_KEY_BackSpace}}}, + {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}}}, + }; + + conf->bindings.vimode_search.count = ALEN(bindings); + conf->bindings.vimode_search.arr = xmemdup(bindings, sizeof(bindings)); } static void @@ -3602,7 +3607,8 @@ config_load(struct config *conf, const char *conf_path, } add_default_key_bindings(conf); - add_default_search_bindings(conf); + add_default_vimode_bindings(conf); + add_default_vimode_search_bindings(conf); add_default_url_bindings(conf); add_default_mouse_bindings(conf); @@ -3661,8 +3667,10 @@ config_load(struct config *conf, const char *conf_path, #if defined(_DEBUG) for (size_t i = 0; i < conf->bindings.key.count; i++) xassert(conf->bindings.key.arr[i].action != BIND_ACTION_NONE); - for (size_t i = 0; i < conf->bindings.search.count; i++) - xassert(conf->bindings.search.arr[i].action != BIND_ACTION_SEARCH_NONE); + for (size_t i = 0; i < conf->bindings.vimode.count; i++) + xassert(conf->bindings.vimode.arr[i].action != BIND_ACTION_VIMODE_NONE); + for (size_t i = 0; i < conf->bindings.vimode_search.count; i++) + xassert(conf->bindings.vimode_search.arr[i].action != BIND_ACTION_VIMODE_SEARCH_NONE); for (size_t i = 0; i < conf->bindings.url.count; i++) xassert(conf->bindings.url.arr[i].action != BIND_ACTION_URL_NONE); #endif @@ -3738,8 +3746,11 @@ config_override_apply(struct config *conf, config_override_t *overrides, conf, section_info[SECTION_KEY_BINDINGS].name, binding_action_map, &conf->bindings.key, KEY_BINDING) && resolve_key_binding_collisions( - conf, section_info[SECTION_SEARCH_BINDINGS].name, - search_binding_action_map, &conf->bindings.search, KEY_BINDING) && + conf, section_info[SECTION_VIMODE_BINDINGS].name, + vimode_binding_action_map, &conf->bindings.vimode, KEY_BINDING) && + resolve_key_binding_collisions( + conf, section_info[SECTION_VIMODE_SEARCH_BINDINGS].name, + vimode_search_binding_action_map, &conf->bindings.vimode_search, KEY_BINDING) && resolve_key_binding_collisions( conf, section_info[SECTION_URL_BINDINGS].name, url_binding_action_map, &conf->bindings.url, KEY_BINDING) && @@ -3863,7 +3874,8 @@ config_clone(const struct config *old) } key_binding_list_clone(&conf->bindings.key, &old->bindings.key); - key_binding_list_clone(&conf->bindings.search, &old->bindings.search); + key_binding_list_clone(&conf->bindings.vimode, &old->bindings.vimode); + key_binding_list_clone(&conf->bindings.vimode_search, &old->bindings.vimode_search); key_binding_list_clone(&conf->bindings.url, &old->bindings.url); key_binding_list_clone(&conf->bindings.mouse, &old->bindings.mouse); @@ -3955,7 +3967,8 @@ config_free(struct config *conf) } free_key_binding_list(&conf->bindings.key); - free_key_binding_list(&conf->bindings.search); + free_key_binding_list(&conf->bindings.vimode); + free_key_binding_list(&conf->bindings.vimode_search); free_key_binding_list(&conf->bindings.url); free_key_binding_list(&conf->bindings.mouse); tll_free_and_free(conf->mouse.selection_override_modifiers, free); diff --git a/config.h b/config.h index fc5e290e..017cda4c 100644 --- a/config.h +++ b/config.h @@ -88,6 +88,7 @@ enum key_binding_type { typedef tll(char *) config_modifier_list_t; struct config_key_binding { + // TODO (kociap): from wayland.h? int action; /* One of the various bind_action_* enums from wayland.h */ //struct config_key_modifiers modifiers; config_modifier_list_t modifiers; @@ -358,9 +359,18 @@ struct config { * Special modes */ - /* While searching (not - action to *start* a search is in the - * 'key' bindings above */ - struct config_key_binding_list search; + /* + * Bindings for vimode. + * Note: action to enter vimode is in the 'key' bindings + * above. + */ + struct config_key_binding_list vimode; + /* + * Bindings for the search mode within vimode. + * Actions to enter the search mode are in the 'key' and + * 'vimode' bindings. + */ + struct config_key_binding_list vimode_search; /* While showing URL jump labels */ struct config_key_binding_list url; diff --git a/grid.c b/grid.c index df7ef61c..5d313e0c 100644 --- a/grid.c +++ b/grid.c @@ -819,6 +819,7 @@ grid_resize_and_reflow( size_t tracking_points_count, struct coord *const _tracking_points[static tracking_points_count]) { + printf("GRID REFLOW\n"); #if defined(TIME_REFLOW) && TIME_REFLOW struct timespec start; clock_gettime(CLOCK_MONOTONIC, &start); @@ -859,6 +860,7 @@ grid_resize_and_reflow( saved_cursor.row += grid->offset; saved_cursor.row &= old_rows - 1; + // TODO (kociap): add the vimode cursor and selection start. size_t tp_count = tracking_points_count + 1 + /* cursor */ diff --git a/ime.c b/ime.c index c6ccb479..e1a64d61 100644 --- a/ime.c +++ b/ime.c @@ -177,8 +177,10 @@ done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, ime_reset_preedit(seat); if (term != NULL) { - if (term->is_searching) - render_refresh_search(term); + if (term->is_vimming) + // TODO (kociap): refresh + // render_refresh_search(term); + (void)0; else render_refresh(term); } @@ -198,9 +200,11 @@ done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, size_t len = strlen(text); if (term != NULL) { - if (term->is_searching) { - search_add_chars(term, text, len); - render_refresh_search(term); + if (term->is_vimming) { + // TODO (kociap): input and refresh + // search_add_chars(term, text, len); + // render_refresh_search(term); + (void)0; } else term_to_slave(term, text, len); } @@ -367,8 +371,10 @@ done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, ime_reset_pending_preedit(seat); if (term != NULL) { - if (term->is_searching) - render_refresh_search(term); + if (term->is_vimming) + // TODO (kociap): refresh + // render_refresh_search(term); + (void)0; else render_refresh(term); } @@ -474,8 +480,9 @@ ime_update_cursor_rect(struct seat *seat) goto update; /* Set in render_search_box() */ - if (term->is_searching) - goto update; + // TODO (kociap): in vimode this most likely is not necessary. + // if (term->is_searching) + // goto update; int x, y, width, height; int col = term->grid->cursor.point.col; diff --git a/input.c b/input.c index 44a99e3b..e8c3ed27 100644 --- a/input.c +++ b/input.c @@ -31,7 +31,7 @@ #include "macros.h" #include "quirks.h" #include "render.h" -#include "search.h" +#include "vimode.h" #include "selection.h" #include "spawn.h" #include "terminal.h" @@ -183,8 +183,12 @@ execute_binding(struct seat *seat, struct terminal *term, term_reset_view(term); return true; - case BIND_ACTION_SEARCH_START: - search_begin(term); + case BIND_ACTION_START_VIMODE: + vimode_begin(term); + return true; + + case BIND_ACTION_START_VIMODE_SEARCH: + vimode_search_begin(term); return true; case BIND_ACTION_FONT_SIZE_UP: @@ -1579,7 +1583,6 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, uint32_t key, uint32_t state) { xassert(serial != 0); - seat->kbd.serial = serial; if (seat->kbd.xkb == NULL || seat->kbd.xkb_keymap == NULL || @@ -1632,15 +1635,17 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, if (pressed) { if (term->unicode_mode.active) { + printf("UNICODE INPUT\n"); unicode_mode_input(seat, term, sym); return; } - else if (term->is_searching) { + else if (term->is_vimming) { + printf("VIMODE INPUT\n"); if (should_repeat) start_repeater(seat, key); - search_input( + vimode_input( seat, term, bindings, key, sym, mods, consumed, raw_syms, raw_count, serial); return; @@ -2744,7 +2749,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, selection_stop_scroll_timer(term); /* Update selection */ - if (!term->is_searching) { + if (!term->is_vimming) { if (auto_scroll_direction != SELECTION_SCROLL_NOT) { /* * Start 'selection auto-scrolling' @@ -3207,7 +3212,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, break; case TERM_SURF_GRID: { - search_cancel(term); + vimode_cancel(term); urls_reset(term); bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0; diff --git a/key-binding.c b/key-binding.c index a2883ed5..877d5ec4 100644 --- a/key-binding.c +++ b/key-binding.c @@ -111,7 +111,7 @@ key_binding_new_for_seat(struct key_binding_manager *mgr, struct key_set set = { .public = { .key = tll_init(), - .search = tll_init(), + .vimode = tll_init(), .url = tll_init(), .mouse = tll_init(), }, @@ -152,7 +152,7 @@ key_binding_new_for_conf(struct key_binding_manager *mgr, struct key_set set = { .public = { .key = tll_init(), - .search = tll_init(), + .vimode = tll_init(), .url = tll_init(), .mouse = tll_init(), }, @@ -532,13 +532,25 @@ convert_key_bindings(struct key_set *set) } static void -convert_search_bindings(struct key_set *set) +convert_vimode_bindings(struct key_set *set) { const struct config *conf = set->conf; - for (size_t i = 0; i < conf->bindings.search.count; i++) { - const struct config_key_binding *binding = &conf->bindings.search.arr[i]; - convert_key_binding(set, binding, &set->public.search); + for (size_t i = 0; i < conf->bindings.vimode.count; i++) { + const struct config_key_binding *binding = &conf->bindings.vimode.arr[i]; + convert_key_binding(set, binding, &set->public.vimode); + } +} + +static void +convert_vimode_search_bindings(struct key_set *set) +{ + const struct config *conf = set->conf; + + for (size_t i = 0; i < conf->bindings.vimode_search.count; i++) { + const struct config_key_binding *binding = + &conf->bindings.vimode_search.arr[i]; + convert_key_binding(set, binding, &set->public.vimode_search); } } @@ -597,7 +609,8 @@ load_keymap(struct key_set *set) } convert_key_bindings(set); - convert_search_bindings(set); + convert_vimode_bindings(set); + convert_vimode_search_bindings(set); convert_url_bindings(set); convert_mouse_bindings(set); @@ -638,7 +651,8 @@ static void NOINLINE unload_keymap(struct key_set *set) { key_bindings_destroy(&set->public.key); - key_bindings_destroy(&set->public.search); + key_bindings_destroy(&set->public.vimode); + key_bindings_destroy(&set->public.vimode_search); key_bindings_destroy(&set->public.url); key_bindings_destroy(&set->public.mouse); set->public.selection_overrides = 0; diff --git a/key-binding.h b/key-binding.h index 5f0c1f1e..4c30db99 100644 --- a/key-binding.h +++ b/key-binding.h @@ -21,7 +21,8 @@ enum bind_action_normal { BIND_ACTION_CLIPBOARD_COPY, BIND_ACTION_CLIPBOARD_PASTE, BIND_ACTION_PRIMARY_PASTE, - BIND_ACTION_SEARCH_START, + BIND_ACTION_START_VIMODE, + BIND_ACTION_START_VIMODE_SEARCH, BIND_ACTION_FONT_SIZE_UP, BIND_ACTION_FONT_SIZE_DOWN, BIND_ACTION_FONT_SIZE_RESET, @@ -63,44 +64,41 @@ enum bind_action_normal { BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1, }; -enum bind_action_search { - BIND_ACTION_SEARCH_NONE, - BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE, - BIND_ACTION_SEARCH_SCROLLBACK_UP_HALF_PAGE, - BIND_ACTION_SEARCH_SCROLLBACK_UP_LINE, - BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE, - BIND_ACTION_SEARCH_SCROLLBACK_DOWN_HALF_PAGE, - BIND_ACTION_SEARCH_SCROLLBACK_DOWN_LINE, - BIND_ACTION_SEARCH_SCROLLBACK_HOME, - BIND_ACTION_SEARCH_SCROLLBACK_END, - BIND_ACTION_SEARCH_CANCEL, - BIND_ACTION_SEARCH_COMMIT, - BIND_ACTION_SEARCH_FIND_PREV, - BIND_ACTION_SEARCH_FIND_NEXT, - BIND_ACTION_SEARCH_EDIT_LEFT, - BIND_ACTION_SEARCH_EDIT_LEFT_WORD, - BIND_ACTION_SEARCH_EDIT_RIGHT, - BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, - BIND_ACTION_SEARCH_EDIT_HOME, - BIND_ACTION_SEARCH_EDIT_END, - BIND_ACTION_SEARCH_DELETE_PREV, - BIND_ACTION_SEARCH_DELETE_PREV_WORD, - BIND_ACTION_SEARCH_DELETE_NEXT, - BIND_ACTION_SEARCH_DELETE_NEXT_WORD, - BIND_ACTION_SEARCH_DELETE_TO_START, - BIND_ACTION_SEARCH_DELETE_TO_END, - BIND_ACTION_SEARCH_EXTEND_CHAR, - BIND_ACTION_SEARCH_EXTEND_WORD, - BIND_ACTION_SEARCH_EXTEND_WORD_WS, - BIND_ACTION_SEARCH_EXTEND_LINE_DOWN, - BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR, - BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD, - BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS, - BIND_ACTION_SEARCH_EXTEND_LINE_UP, - BIND_ACTION_SEARCH_CLIPBOARD_PASTE, - BIND_ACTION_SEARCH_PRIMARY_PASTE, - BIND_ACTION_SEARCH_UNICODE_INPUT, - BIND_ACTION_SEARCH_COUNT, +enum bind_action_vimode { + BIND_ACTION_VIMODE_NONE, + BIND_ACTION_VIMODE_UP, + BIND_ACTION_VIMODE_DOWN, + BIND_ACTION_VIMODE_LEFT, + BIND_ACTION_VIMODE_RIGHT, + BIND_ACTION_VIMODE_UP_PAGE, + BIND_ACTION_VIMODE_DOWN_PAGE, + BIND_ACTION_VIMODE_UP_HALF_PAGE, + BIND_ACTION_VIMODE_DOWN_HALF_PAGE, + BIND_ACTION_VIMODE_UP_LINE, + BIND_ACTION_VIMODE_DOWN_LINE, + BIND_ACTION_VIMODE_FIRST_LINE, + BIND_ACTION_VIMODE_LAST_LINE, + BIND_ACTION_VIMODE_CANCEL, + BIND_ACTION_VIMODE_START_SEARCH, + BIND_ACTION_VIMODE_FIND_NEXT, + BIND_ACTION_VIMODE_FIND_PREV, + BIND_ACTION_VIMODE_ENTER_VISUAL, + BIND_ACTION_VIMODE_ENTER_VLINE, + BIND_ACTION_VIMODE_ENTER_VBLOCK, + BIND_ACTION_VIMODE_YANK, + + BIND_ACTION_VIMODE_COUNT, +}; + +enum bind_action_vimode_search { + BIND_ACTION_VIMODE_SEARCH_NONE, + BIND_ACTION_VIMODE_SEARCH_CONFIRM, + BIND_ACTION_VIMODE_SEARCH_CANCEL, + BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_CHAR, + BIND_ACTION_VIMODE_SEARCH_LEFT, + BIND_ACTION_VIMODE_SEARCH_RIGHT, + + BIND_ACTION_VIMODE_SEARCH_COUNT, }; enum bind_action_url { @@ -140,7 +138,8 @@ struct wayland; struct key_binding_set { key_binding_list_t key; - key_binding_list_t search; + key_binding_list_t vimode; + key_binding_list_t vimode_search; key_binding_list_t url; key_binding_list_t mouse; xkb_mod_mask_t selection_overrides; diff --git a/meson.build b/meson.build index aa8342ab..2d92ff8d 100644 --- a/meson.build +++ b/meson.build @@ -320,7 +320,7 @@ executable( 'quirks.c', 'quirks.h', 'reaper.c', 'reaper.h', 'render.c', 'render.h', - 'search.c', 'search.h', + 'vimode.c', 'vimode.h', 'server.c', 'server.h', 'client-protocol.h', 'shm.c', 'shm.h', 'slave.c', 'slave.h', diff --git a/render.c b/render.c index 1d0f08af..da791b69 100644 --- a/render.c +++ b/render.c @@ -36,7 +36,7 @@ #include "grid.h" #include "ime.h" #include "quirks.h" -#include "search.h" +#include "vimode.h" #include "selection.h" #include "shm.h" #include "sixel.h" @@ -598,6 +598,10 @@ draw_strikeout(const struct terminal *term, pixman_image_t *pix, cols * term->cell_width, thickness}); } +/* + * TODO (kociap): The cell parameter is not used? We could make this + * function more generic to be reusable in the search box. + */ static void cursor_colors_for_cell(const struct terminal *term, const struct cell *cell, const pixman_color_t *fg, const pixman_color_t *bg, @@ -701,11 +705,12 @@ render_cell(struct terminal *term, pixman_image_t *pix, const int x = term->margins.left + col * width; const int y = term->margins.top + row_no * height; + const bool is_selected = cell->attrs.selected; + uint32_t _fg = 0; uint32_t _bg = 0; uint16_t alpha = 0xffff; - const bool is_selected = cell->attrs.selected; /* Use cell specific color, if set, otherwise the default colors (possible reversed) */ switch (cell->attrs.fg_src) { @@ -762,19 +767,29 @@ render_cell(struct terminal *term, pixman_image_t *pix, _fg = cell_bg; } + // Cursor is always inverted, thus we want to invert it when + // the vi mode is visual or visual block for visibility + // (otherwise it is difficult to tell its position within the + // row). + const bool mode_not_vline = term->vimode.mode == VI_MODE_VISUAL || + term->vimode.mode == VI_MODE_VBLOCK; + if (has_cursor && mode_not_vline) { + uint32_t swap = _fg; + _fg = _bg; + _bg = swap; + } + if (unlikely(_fg == _bg)) { /* Invert bg when selected/highlighted text has same fg/bg */ _bg = ~_bg; alpha = 0xffff; } - } else { if (unlikely(cell->attrs.reverse)) { uint32_t swap = _fg; _fg = _bg; _bg = swap; } - else if (!term->window->is_fullscreen && term->colors.alpha != 0xffff) { switch (term->conf->colors.alpha_mode) { case ALPHA_MODE_DEFAULT: { @@ -1722,7 +1737,7 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, if (likely(seat->ime.preedit.cells == NULL)) return; - if (unlikely(term->is_searching)) + if (unlikely(term->is_vimming)) return; const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); @@ -1965,7 +1980,7 @@ render_overlay(struct terminal *term) const bool unicode_mode_active = term->unicode_mode.active; const enum overlay_style style = - term->is_searching ? OVERLAY_SEARCH : + term->is_vimming ? OVERLAY_SEARCH : term->flash.active ? OVERLAY_FLASH : unicode_mode_active ? OVERLAY_UNICODE_MODE : OVERLAY_NONE; @@ -2065,33 +2080,34 @@ render_overlay(struct terminal *term) pixman_region32_clear(see_through); /* Build region consisting of all current search matches */ - struct search_match_iterator iter = search_matches_new_iter(term); - for (struct range match = search_matches_next(&iter); - match.start.row >= 0; - match = search_matches_next(&iter)) - { - int r = match.start.row; - int start_col = match.start.col; - const int end_row = match.end.row; - - while (true) { - const int end_col = - r == end_row ? match.end.col : term->cols - 1; - - int x = term->margins.left + start_col * term->cell_width; - int y = term->margins.top + r * term->cell_height; - int width = (end_col + 1 - start_col) * term->cell_width; - int height = 1 * term->cell_height; - - pixman_region32_union_rect( - see_through, see_through, x, y, width, height); - - if (++r > end_row) - break; - - start_col = 0; - } - } + // TODO (kociap): commented this because no search atm. + // struct search_match_iterator iter = search_matches_new_iter(term); + // for (struct range match = search_matches_next(&iter); + // match.start.row >= 0; + // match = search_matches_next(&iter)) + // { + // int r = match.start.row; + // int start_col = match.start.col; + // const int end_row = match.end.row; + // + // while (true) { + // const int end_col = + // r == end_row ? match.end.col : term->cols - 1; + // + // int x = term->margins.left + start_col * term->cell_width; + // int y = term->margins.top + r * term->cell_height; + // int width = (end_col + 1 - start_col) * term->cell_width; + // int height = 1 * term->cell_height; + // + // pixman_region32_union_rect( + // see_through, see_through, x, y, width, height); + // + // if (++r > end_row) + // break; + // + // start_col = 0; + // } + // } /* Areas that need to be cleared: cells that were dimmed in * the last frame but is now see-through */ @@ -2198,9 +2214,17 @@ render_worker_thread(void *_ctx) bool frame_done = false; - /* Translate offset-relative cursor row to view-relative */ + /* + * We always show the vimode cursor when in vimode as that is + * not dependent on the terminal state. + */ struct coord cursor = {-1, -1}; - if (!term->hide_cursor) { + if (term->is_vimming) { + cursor = term->vimode.cursor; + cursor.row += term->grid->offset; + cursor.row -= term->grid->view; + cursor.row &= term->grid->num_rows - 1; + } else if (!term->hide_cursor) { cursor = term->grid->cursor.point; cursor.row += term->grid->offset; cursor.row -= term->grid->view; @@ -3084,10 +3108,11 @@ render_scrollback_position(struct terminal *term) case SCROLLBACK_INDICATOR_POSITION_RELATIVE: { int lines = term->rows - 2; /* Avoid using first and last rows */ - if (term->is_searching) { - /* Make sure we don't collide with the scrollback search box */ - lines--; - } + // TODO (kociap): whatever this does + // if (term->is_searching) { + // /* Make sure we don't collide with the scrollback search box */ + // lines--; + // } lines = max(lines, 0); @@ -3303,7 +3328,6 @@ dirty_cursor(struct terminal *term) return; const struct coord *cursor = &term->grid->cursor.point; - struct row *row = grid_row(term->grid, cursor->row); struct cell *cell = &row->cells[cursor->col]; cell->attrs.clean = 0; @@ -3603,7 +3627,8 @@ grid_render(struct terminal *term) pixman_region32_fini(&damage); - render_overlay(term); + // TODO (kociap): we want to eventually render the overlays. + // render_overlay(term); render_ime_preedit(term, buf); render_scrollback_position(term); @@ -3709,9 +3734,20 @@ grid_render(struct terminal *term) wl_surface_commit(term->window->surface.surf); } +static void +render_search_box_cursor( + struct terminal* const term, pixman_image_t* const pix, pixman_color_t fg, + int x, int y) +{ + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, pix, &fg, 1, + &(pixman_rectangle16_t){x, y, term->cell_width, term->cell_height}); +} + static void render_search_box(struct terminal *term) { + printf("REDRAWING SEARCH BOX\n"); xassert(term->window->search.sub != NULL); /* @@ -3735,7 +3771,7 @@ render_search_box(struct terminal *term) } } - size_t text_len = term->search.len; + size_t text_len = term->vimode.search.len; if (ime_seat != NULL && ime_seat->ime.preedit.text != NULL) text_len += c32len(ime_seat->ime.preedit.text); @@ -3743,19 +3779,19 @@ render_search_box(struct terminal *term) text[0] = U'\0'; /* Copy everything up to the cursor */ - c32ncpy(text, term->search.buf, term->search.cursor); - text[term->search.cursor] = U'\0'; + c32ncpy(text, term->vimode.search.buf, term->vimode.search.cursor); + text[term->vimode.search.cursor] = U'\0'; /* Insert pre-edit text at cursor */ if (ime_seat != NULL && ime_seat->ime.preedit.text != NULL) c32cat(text, ime_seat->ime.preedit.text); /* And finally everything after the cursor */ - c32ncat(text, &term->search.buf[term->search.cursor], - term->search.len - term->search.cursor); + c32ncat(text, &term->vimode.search.buf[term->vimode.search.cursor], + term->vimode.search.len - term->vimode.search.cursor); #else - const char32_t *text = term->search.buf; - const size_t text_len = term->search.len; + const char32_t *text = term->vimode.search.buf; + const size_t text_len = term->vimode.search.len; #endif /* Calculate the width of each character */ @@ -3769,21 +3805,15 @@ render_search_box(struct terminal *term) const float scale = term->scale; xassert(scale >= 1.); - const size_t margin = (size_t)roundf(3 * scale); - size_t width = term->width - 2 * margin; - size_t height = min( - term->height - 2 * margin, - margin + 1 * term->cell_height + margin); + const size_t width = roundf(scale * ceilf(term->width / scale)); + const size_t height = + roundf(scale * ceilf(min(term->height, term->cell_height) / scale)); - width = roundf(scale * ceilf((term->width - 2 * margin) / scale)); - height = roundf(scale * ceilf(height / scale)); + const size_t visible_width = + min(term->width, wanted_visible_cells * term->cell_width); - size_t visible_width = min( - term->width - 2 * margin, - margin + wanted_visible_cells * term->cell_width + margin); - - const size_t visible_cells = (visible_width - 2 * margin) / term->cell_width; + const size_t visible_cells = (visible_width) / term->cell_width; size_t glyph_offset = term->render.search_glyph_offset; struct buffer_chain *chain = term->render.chains.search; @@ -3794,52 +3824,46 @@ render_search_box(struct terminal *term) pixman_image_set_clip_region32(buf->pix[0], &clip); pixman_region32_fini(&clip); -#define WINDOW_X(x) (margin + x) -#define WINDOW_Y(y) (term->height - margin - height + y) +#define WINDOW_X(x) (x) +#define WINDOW_Y(y) (term->height - height + y) - const bool is_match = term->search.match_len == text_len; + const bool is_match = term->vimode.search.match_len == text_len; const bool custom_colors = is_match ? term->conf->colors.use_custom.search_box_match : term->conf->colors.use_custom.search_box_no_match; /* Background - yellow on empty/match, red on mismatch (default) */ const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); - const pixman_color_t color = color_hex_to_pixman( + const pixman_color_t bg = color_hex_to_pixman( is_match ? (custom_colors ? term->conf->colors.search_box.match.bg - : term->colors.table[3]) + : term->colors.bg) : (custom_colors ? term->conf->colors.search_box.no_match.bg - : term->colors.table[1]), + : term->colors.bg), gamma_correct); pixman_image_fill_rectangles( - PIXMAN_OP_SRC, buf->pix[0], &color, - 1, &(pixman_rectangle16_t){width - visible_width, 0, visible_width, height}); - - pixman_color_t transparent = color_hex_to_pixman_with_alpha(0, 0, gamma_correct); - pixman_image_fill_rectangles( - PIXMAN_OP_SRC, buf->pix[0], &transparent, - 1, &(pixman_rectangle16_t){0, 0, width - visible_width, height}); + PIXMAN_OP_SRC, buf->pix[0], &bg, 1, + &(pixman_rectangle16_t){0, 0, width, height}); struct fcft_font *font = term->fonts[0]; - const int x_left = width - visible_width + margin; const int x_ofs = term->font_x_ofs; - int x = x_left; - int y = margin; + int x = 0; + int y = 0; pixman_color_t fg = color_hex_to_pixman( custom_colors ? (is_match ? term->conf->colors.search_box.match.fg : term->conf->colors.search_box.no_match.fg) - : term->colors.table[0], + : term->colors.table[244], gamma_correct); /* Move offset we start rendering at, to ensure the cursor is visible */ - for (size_t i = 0, cell_idx = 0; i <= term->search.cursor; cell_idx += widths[i], i++) { - if (i != term->search.cursor) + for (size_t i = 0, cell_idx = 0; i <= term->vimode.search.cursor; cell_idx += widths[i], i++) { + if (i != term->vimode.search.cursor) continue; #if (FOOT_IME_ENABLED) && FOOT_IME_ENABLED @@ -3905,7 +3929,8 @@ render_search_box(struct terminal *term) { /* Convert subsurface coordinates to window coordinates*/ /* Render cursor */ - if (i == term->search.cursor) { + bool const cursor_cell = i == term->vimode.search.cursor; + if (cursor_cell) { #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED bool have_preedit = ime_seat != NULL && ime_seat->ime.preedit.cells != NULL; @@ -3933,8 +3958,7 @@ render_search_box(struct terminal *term) /* Bar-styled cursor, if in the visible area */ if (start >= 0 && start <= visible_cells) { - draw_beam_cursor( - term, buf->pix[0], font, &fg, + render_search_box_cursor(term, buf->pix[0], fg, x + start * term->cell_width, y); } @@ -3966,7 +3990,7 @@ render_search_box(struct terminal *term) /* Cursor *should* be in the visible area */ xassert(cell_idx >= glyph_offset); xassert(cell_idx <= glyph_offset + visible_cells); - draw_beam_cursor(term, buf->pix[0], font, &fg, x, y); + render_search_box_cursor(term, buf->pix[0], fg, x, y); term_ime_set_cursor_rect( term, WINDOW_X(x), WINDOW_Y(y), 1, term->cell_height); } @@ -4002,7 +4026,8 @@ render_search_box(struct terminal *term) ? width * term->cell_width : (width - 1) * term->cell_width) : 0; /* Not a zero-width character - no additional offset */ - pixman_image_t *src = pixman_image_create_solid_fill(&fg); + pixman_color_t color = cursor_cell ? bg : fg; + pixman_image_t *src = pixman_image_create_solid_fill(&color); pixman_image_composite32( PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0, x + x_ofs + combining_ofs + glyph->x, @@ -4020,8 +4045,8 @@ render_search_box(struct terminal *term) /* Already rendered */; else #endif - if (term->search.cursor >= term->search.len) { - draw_beam_cursor(term, buf->pix[0], font, &fg, x, y); + if (term->vimode.search.cursor >= term->vimode.search.len) { + render_search_box_cursor(term, buf->pix[0], fg, x, y); term_ime_set_cursor_rect( term, WINDOW_X(x), WINDOW_Y(y), 1, term->cell_height); } @@ -4031,8 +4056,8 @@ render_search_box(struct terminal *term) /* TODO: this is only necessary on a window resize */ wl_subsurface_set_position( term->window->search.sub, - roundf(margin / scale), - roundf(max(0, (int32_t)term->height - height - margin) / scale)); + 0, + roundf(max(0, (int32_t)term->height - height) / scale)); wayl_surface_scale(term->window, &term->window->search.surface, buf, scale); wl_surface_attach(term->window->search.surface.surf, buf->wl_buf, 0, 0); @@ -4040,7 +4065,7 @@ render_search_box(struct terminal *term) struct wl_region *region = wl_compositor_create_region(term->wl->compositor); if (region != NULL) { - wl_region_add(region, width - visible_width, 0, visible_width, height); + wl_region_add(region, 0, 0, width, height); wl_surface_set_opaque_region(term->window->search.surface.surf, region); wl_region_destroy(region); } @@ -4310,14 +4335,14 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da wl_callback_destroy(wl_callback); term->window->frame_callback = NULL; - bool grid = term->render.pending.grid; - bool csd = term->render.pending.csd; - bool search = term->is_searching && term->render.pending.search; - bool urls = urls_mode_is_active(term) > 0 && term->render.pending.urls; + bool const grid = term->render.pending.grid; + bool const csd = term->render.pending.csd; + bool const search_box = term->render.pending.vimode_search_box; + bool const urls = urls_mode_is_active(term) > 0 && term->render.pending.urls; term->render.pending.grid = false; term->render.pending.csd = false; - term->render.pending.search = false; + term->render.pending.vimode_search_box = false; term->render.pending.urls = false; struct grid *original_grid = term->grid; @@ -4332,13 +4357,13 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da quirk_weston_csd_off(term); } - if (search) + if (search_box) render_search_box(term); if (urls) render_urls(term); - if ((grid && !term->delayed_render_timer.is_armed) || (csd | search | urls)) + if ((grid && !term->delayed_render_timer.is_armed) || (csd | search_box | urls)) grid_render(term); tll_foreach(term->wl->seats, it) { @@ -4984,7 +5009,7 @@ damage_view: term->render.last_buf = NULL; term_damage_view(term); render_refresh_csd(term); - render_refresh_search(term); + render_refresh_vimode_search_box(term); render_refresh(term); return true; @@ -5129,20 +5154,20 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data) if (unlikely(term->shutdown.in_progress || !term->window->is_configured)) continue; - bool grid = term->render.refresh.grid; - bool csd = term->render.refresh.csd; - bool search = term->is_searching && term->render.refresh.search; - bool urls = urls_mode_is_active(term) && term->render.refresh.urls; + bool const grid = term->render.refresh.grid; + bool const csd = term->render.refresh.csd; + bool const search_box = term->render.refresh.vimode_search_box; + bool const urls = urls_mode_is_active(term) && term->render.refresh.urls; - if (!(grid | csd | search | urls)) + if (!(grid | csd | search_box | urls)) continue; - if (term->render.app_sync_updates.enabled && !(csd | search | urls)) + if (term->render.app_sync_updates.enabled && !(csd | search_box | urls)) continue; term->render.refresh.grid = false; term->render.refresh.csd = false; - term->render.refresh.search = false; + term->render.refresh.vimode_search_box = false; term->render.refresh.urls = false; if (term->window->frame_callback == NULL) { @@ -5157,11 +5182,11 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data) render_csd(term); quirk_weston_csd_off(term); } - if (search) + if (search_box) render_search_box(term); if (urls) render_urls(term); - if (grid | csd | search | urls) + if (grid | csd | search_box | urls) grid_render(term); tll_foreach(term->wl->seats, it) { @@ -5174,7 +5199,7 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data) /* Tells the frame callback to render again */ term->render.pending.grid |= grid; term->render.pending.csd |= csd; - term->render.pending.search |= search; + term->render.pending.vimode_search_box |= search_box; term->render.pending.urls |= urls; } } @@ -5292,11 +5317,13 @@ render_refresh_csd(struct terminal *term) term->render.refresh.csd = true; } +// TODO (kociap): Rename to something more indicative, e.g. +// render_refresh_vimode_search_box_search_box void -render_refresh_search(struct terminal *term) +render_refresh_vimode_search_box(struct terminal *term) { - if (term->is_searching) - term->render.refresh.search = true; + if (term->is_vimming && term->vimode.is_searching) + term->render.refresh.vimode_search_box = true; } void diff --git a/render.h b/render.h index e21eaca8..4cc4b169 100644 --- a/render.h +++ b/render.h @@ -24,7 +24,7 @@ void render_refresh(struct terminal *term); void render_refresh_app_id(struct terminal *term); void render_refresh_icon(struct terminal *term); void render_refresh_csd(struct terminal *term); -void render_refresh_search(struct terminal *term); +void render_refresh_vimode_search_box(struct terminal *term); void render_refresh_title(struct terminal *term); void render_refresh_urls(struct terminal *term); bool render_xcursor_set( diff --git a/selection.c b/selection.c index f07396a5..55f03ecd 100644 --- a/selection.c +++ b/selection.c @@ -24,7 +24,7 @@ #include "grid.h" #include "misc.h" #include "render.h" -#include "search.h" +#include "vimode.h" #include "uri.h" #include "util.h" #include "vt.h" @@ -1615,8 +1615,6 @@ selection_cancel(struct terminal *term) term->selection.pivot.end = (struct coord){-1, -1}; term->selection.direction = SELECTION_UNDIR; term->selection.ongoing = false; - - search_selection_cancelled(term); } bool diff --git a/terminal.c b/terminal.c index 36f8513b..a9fa5f9e 100644 --- a/terminal.c +++ b/terminal.c @@ -46,6 +46,7 @@ #include "vt.h" #include "xmalloc.h" #include "xsnprintf.h" +#include "vimode.h" #define PTMX_TIMING 0 @@ -1886,8 +1887,9 @@ term_destroy(struct terminal *term) free_custom_glyphs( &term->custom_glyphs.octants, GLYPH_OCTANTS_COUNT); - free(term->search.buf); - free(term->search.last.buf); + // TODO (kociap): Free the vimode search buffers. + // free(term->search.buf); + // free(term->search.last.buf); if (term->render.workers.threads != NULL) { for (size_t i = 0; i < term->render.workers.count; i++) { @@ -2477,6 +2479,18 @@ term_font_baseline(const struct terminal *term) return term->font_y_ofs + line_height - glyph_top_y - font->descent; } +void +term_damage_cell_in_view(struct terminal* const term, int const row, int const col) +{ + if(col >= term->grid->num_cols || col < 0) { + return; + } + + struct row* const r = grid_row_in_view(term->grid, row); + r->dirty = true; + r->cells[col].attrs.clean = 0; +} + void term_damage_rows(struct terminal *term, int start, int end) { @@ -3056,6 +3070,7 @@ selection_on_bottom_region(const struct terminal *term, void term_scroll_partial(struct terminal *term, struct scroll_region region, int rows) { + printf("SCROLL PARTIAL [rows=%d, region.start=%d, region.end=%d]\n", rows, region.start, region.end); LOG_DBG("scroll: rows=%d, region.start=%d, region.end=%d", rows, region.start, region.end); @@ -3113,6 +3128,7 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows } term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); + vimode_view_down(term, rows); #if defined(_DEBUG) for (int r = 0; r < term->rows; r++) @@ -3798,8 +3814,8 @@ term_enable_app_sync_updates(struct terminal *term) /* Disable pending refresh *iff* the grid is the *only* thing * scheduled to be re-rendered */ - if (!term->render.refresh.csd && !term->render.refresh.search && - !term->render.pending.csd && !term->render.pending.search) + if (!term->render.refresh.csd && !term->render.refresh.vimode_search_box && + !term->render.pending.csd && !term->render.pending.vimode_search_box) { term->render.refresh.grid = false; term->render.pending.grid = false; @@ -3960,6 +3976,7 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count, void term_print(struct terminal *term, char32_t wc, int width, bool insert_mode_disable) { + printf("TERM PRINT\n"); xassert(width > 0); struct grid *grid = term->grid; diff --git a/terminal.h b/terminal.h index 364d57b3..61292b6f 100644 --- a/terminal.h +++ b/terminal.h @@ -374,11 +374,19 @@ enum term_surface { enum overlay_style { OVERLAY_NONE, + // TODO (kociap): rename to OVERLAY_VIMODE OVERLAY_SEARCH, OVERLAY_FLASH, OVERLAY_UNICODE_MODE, }; +enum vi_mode { + VI_MODE_NORMAL, + VI_MODE_VISUAL, + VI_MODE_VLINE, + VI_MODE_VBLOCK, +}; + typedef tll(struct ptmx_buffer) ptmx_buffer_list_t; enum url_action { URL_ACTION_COPY, URL_ACTION_LAUNCH, URL_ACTION_PERSISTENT }; @@ -621,23 +629,34 @@ struct terminal { } auto_scroll; } selection; - bool is_searching; + bool is_vimming; struct { - char32_t *buf; - size_t len; - size_t sz; - size_t cursor; + enum vi_mode mode; + struct coord cursor; + bool is_searching; - int original_view; - bool view_followed_offset; - struct coord match; - size_t match_len; + struct { + struct coord start; + } selection; + + struct vimode_search { + char32_t *buf; + size_t len; + size_t sz; + size_t cursor; + + enum search_direction direction; + int original_view; + struct coord match; + size_t match_len; + } search; struct { char32_t *buf; size_t len; - } last; - } search; + enum search_direction direction; + } confirmed_search; + } vimode; struct wayland *wl; struct wl_window *window; @@ -660,7 +679,7 @@ struct terminal { struct { bool grid; bool csd; - bool search; + bool vimode_search_box; bool urls; } refresh; @@ -668,7 +687,7 @@ struct terminal { struct { bool grid; bool csd; - bool search; + bool vimode_search_box; bool urls; } pending; @@ -878,6 +897,7 @@ int term_pt_or_px_as_pixels( void term_window_configured(struct terminal *term); +void term_damage_cell_in_view(struct terminal* term, int row, int col); void term_damage_rows(struct terminal *term, int start, int end); void term_damage_rows_in_view(struct terminal *term, int start, int end); diff --git a/tests/test-config.c b/tests/test-config.c index 268733db..0d2f0a83 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -1165,22 +1165,22 @@ test_section_key_bindings_collisions(void) } static void -test_section_search_bindings(void) +test_section_vimode_bindings(void) { struct config conf = {0}; struct context ctx = { - .conf = &conf, .section = "search-bindings", .path = "unittest"}; + .conf = &conf, .section = "vimode-bindings", .path = "unittest"}; - test_invalid_key(&ctx, &parse_section_search_bindings, "invalid-key"); + test_invalid_key(&ctx, &parse_section_vimode_bindings, "invalid-key"); - for (int action = 0; action < BIND_ACTION_SEARCH_COUNT; action++) { - if (search_binding_action_map[action] == NULL) + for (int action = 0; action < BIND_ACTION_VIMODE_COUNT; action++) { + if (vimode_binding_action_map[action] == NULL) continue; test_key_binding( - &ctx, &parse_section_search_bindings, - action, BIND_ACTION_SEARCH_COUNT - 1, - search_binding_action_map, &conf.bindings.search, KEY_BINDING, + &ctx, &parse_section_vimode_bindings, + action, BIND_ACTION_VIMODE_COUNT - 1, + vimode_binding_action_map, &conf.bindings.vimode, KEY_BINDING, false, false); } @@ -1188,15 +1188,52 @@ test_section_search_bindings(void) } static void -test_section_search_bindings_collisions(void) +test_section_vimode_search_bindings(void) { struct config conf = {0}; struct context ctx = { - .conf = &conf, .section = "search-bindings", .path = "unittest"}; + .conf = &conf, .section = "vimode-search-bindings", .path = "unittest"}; + + test_invalid_key(&ctx, &parse_section_vimode_search_bindings, "invalid-key"); + + for (int action = 0; action < BIND_ACTION_VIMODE_COUNT; action++) { + if (vimode_search_binding_action_map[action] == NULL) + continue; + + test_key_binding( + &ctx, &parse_section_vimode_search_bindings, + action, BIND_ACTION_VIMODE_COUNT - 1, + vimode_search_binding_action_map, &conf.bindings.vimode_search, KEY_BINDING, + false, false); + } + + config_free(&conf); +} + +static void +test_section_vimode_bindings_collisions(void) +{ + struct config conf = {0}; + struct context ctx = { + .conf = &conf, .section = "vimode-bindings", .path = "unittest"}; test_binding_collisions( &ctx, - BIND_ACTION_SEARCH_COUNT - 1, search_binding_action_map, KEY_BINDING); + BIND_ACTION_VIMODE_COUNT - 1, vimode_binding_action_map, KEY_BINDING); + + config_free(&conf); +} + +static void +test_section_vimode_search_bindings_collisions(void) +{ + struct config conf = {0}; + struct context ctx = { + .conf = &conf, .section = "vimode-search-bindings", .path = "unittest"}; + + test_binding_collisions( + &ctx, + BIND_ACTION_VIMODE_COUNT - 1, vimode_search_binding_action_map, KEY_BINDING); config_free(&conf); } @@ -1448,8 +1485,10 @@ main(int argc, const char *const *argv) test_section_csd(); test_section_key_bindings(); test_section_key_bindings_collisions(); - test_section_search_bindings(); - test_section_search_bindings_collisions(); + test_section_vimode_bindings(); + test_section_vimode_search_bindings(); + test_section_vimode_bindings_collisions(); + test_section_vimode_search_bindings_collisions(); test_section_url_bindings(); test_section_url_bindings_collisions(); test_section_mouse_bindings(); diff --git a/unicode-mode.c b/unicode-mode.c index 1acdc664..b7d23e36 100644 --- a/unicode-mode.c +++ b/unicode-mode.c @@ -33,9 +33,11 @@ unicode_mode_updated(struct terminal *term) { if (term == NULL) return; - - if (term->is_searching) - render_refresh_search(term); + printf("UNICODE UPDATE\n"); + if (term->is_vimming) + // TODO (kociap): refresh + // render_refresh_search(term); + (void)0; else render_refresh(term); } @@ -44,6 +46,7 @@ void unicode_mode_input(struct seat *seat, struct terminal *term, xkb_keysym_t sym) { + printf("UNICODE INPUT\n"); if (sym == XKB_KEY_Return || sym == XKB_KEY_space || sym == XKB_KEY_KP_Enter || @@ -57,8 +60,10 @@ unicode_mode_input(struct seat *seat, struct terminal *term, term->unicode_mode.character, (int)chars, utf8); if (chars != (size_t)-1) { - if (term->is_searching) - search_add_chars(term, utf8, chars); + if (term->is_vimming) + // TODO (kociap): input + // search_add_chars(term, utf8, chars); + (void)0; else term_to_slave(term, utf8, chars); } diff --git a/vimode.c b/vimode.c new file mode 100644 index 00000000..c49efe8d --- /dev/null +++ b/vimode.c @@ -0,0 +1,1072 @@ +#include + +#include +#include + +#define LOG_MODULE "vimode" +#define LOG_ENABLE_DBG 0 +#include "char32.h" +#include "commands.h" +#include "grid.h" +#include "key-binding.h" +#include "log.h" +#include "render.h" +#include "selection.h" +#include "util.h" +#include "vimode.h" +#include "xmalloc.h" + +static bool is_mode_visual(enum vi_mode const mode) { + return mode == VI_MODE_VISUAL || mode == VI_MODE_VLINE || + mode == VI_MODE_VBLOCK; +} + +static enum selection_kind +selection_kind_from_vi_mode(enum vi_mode const mode) { + switch (mode) { + case VI_MODE_VISUAL: + return SELECTION_CHAR_WISE; + case VI_MODE_VLINE: + return SELECTION_LINE_WISE; + case VI_MODE_VBLOCK: + return SELECTION_BLOCK; + default: + BUG("invalid vi mode"); + return SELECTION_NONE; + } +} + +static struct coord offset_to_view_relative(struct terminal *const term, + struct coord coord) { + coord.row += term->grid->offset; + coord.row -= term->grid->view; + return coord; +} + +static struct coord view_to_offset_relative(struct terminal *const term, + struct coord coord) { + coord.row += term->grid->view; + coord.row -= term->grid->offset; + return coord; +} + +static void damage_cursor_cell(struct terminal *const term) { + struct coord const cursor = + offset_to_view_relative(term, term->vimode.cursor); + term_damage_cell_in_view(term, cursor.row, cursor.col); +} + +static void clip_cursor_to_view(struct terminal *const term) { + damage_cursor_cell(term); + printf("CLIP CURSOR BEFORE (%d, %d) [view=%d; offset=%d]\n", + term->vimode.cursor.row, term->vimode.cursor.col, term->grid->view, + term->grid->offset); + struct coord cursor = offset_to_view_relative(term, term->vimode.cursor); + printf("CLIP CURSOR VIEW RELATIVE BEFORE (%d, %d)\n", cursor.row, cursor.col); + if (cursor.row < 0) { + cursor.row = 0; + } else if (cursor.row >= term->rows) { + cursor.row = term->rows - 1; + } + printf("CLIP CURSOR VIEW RELATIVE AFTER (%d, %d)\n", cursor.row, cursor.col); + term->vimode.cursor = view_to_offset_relative(term, cursor); + printf("CLIP CURSOR AFTER (%d, %d)\n", term->vimode.cursor.row, + term->vimode.cursor.col); + damage_cursor_cell(term); + render_refresh(term); +} + +static void update_selection(struct seat *const seat, + struct terminal *const term) { + enum vi_mode const mode = term->vimode.mode; + if (is_mode_visual(mode)) { + struct coord const cursor = term->grid->cursor.point; + printf("UPDATING SELECTION [row=%d; col=%d]\n", cursor.row, cursor.col); + selection_update(term, cursor.col, cursor.row); + } +} + +/* + * Ensures a "new" viewport doesn't contain any unallocated rows. + * + * This is done by first checking if the *first* row is NULL. If so, + * we move the viewport *forward*, until the first row is non-NULL. At + * this point, the entire viewport should be allocated rows only. + * + * If the first row already was non-NULL, we instead check the *last* + * row, and if it is NULL, we move the viewport *backward* until the + * last row is non-NULL. + */ +static int ensure_view_is_allocated(struct terminal *term, int new_view) { + struct grid *grid = term->grid; + int view_end = (new_view + term->rows - 1) & (grid->num_rows - 1); + + if (grid->rows[new_view] == NULL) { + while (grid->rows[new_view] == NULL) + new_view = (new_view + 1) & (grid->num_rows - 1); + } + + else if (grid->rows[view_end] == NULL) { + while (grid->rows[view_end] == NULL) { + new_view--; + if (new_view < 0) + new_view += grid->num_rows; + view_end = (new_view + term->rows - 1) & (grid->num_rows - 1); + } + } + +#if defined(_DEBUG) + for (size_t r = 0; r < term->rows; r++) + xassert(grid->rows[(new_view + r) & (grid->num_rows - 1)] != NULL); +#endif + + return new_view; +} + +static bool search_ensure_size(struct terminal *term, size_t wanted_size) { + struct vimode_search *const search = &term->vimode.search; + while (wanted_size >= search->sz) { + size_t new_sz = search->sz == 0 ? 64 : search->sz * 2; + char32_t *new_buf = realloc(search->buf, new_sz * sizeof(search->buf[0])); + + if (new_buf == NULL) { + LOG_ERRNO("failed to resize search buffer"); + return false; + } + + search->buf = new_buf; + search->sz = new_sz; + } + + return true; +} + +static void start_search(struct terminal *term) { + if (term->vimode.is_searching) { + return; + } + + LOG_DBG("vimode-search: begin"); + + const struct grid *grid = term->grid; + term->vimode.search.original_view = grid->view; + term->vimode.search.len = 0; + term->vimode.search.sz = 64; + term->vimode.search.buf = + xmalloc(term->vimode.search.sz * sizeof(term->vimode.search.buf[0])); + term->vimode.search.buf[0] = U'\0'; + term->vimode.search.direction = SEARCH_FORWARD; + term->vimode.is_searching = true; + + /* On-demand instantiate wayland surface */ + bool ret = + wayl_win_subsurface_new(term->window, &term->window->search, false); + xassert(ret); + + render_refresh_vimode_search_box(term); +} + +static void cancel_search(struct terminal *const term) { + if (!term->vimode.is_searching) { + return; + } + + wayl_win_subsurface_destroy(&term->window->search); + term->vimode.is_searching = false; + struct vimode_search *const search = &term->vimode.search; + if (search->buf != NULL) { + free(search->buf); + search->buf = NULL; + } + search->len = search->sz = 0; + search->cursor = 0; + search->original_view = 0; + search->match = (struct coord){-1, -1}; + search->match_len = 0; + term->render.search_glyph_offset = 0; + + term->grid->view = ensure_view_is_allocated(term, search->original_view); + term_damage_view(term); + render_refresh(term); +} + +void vimode_search_begin(struct terminal *term) { + vimode_begin(term); + start_search(term); + term_xcursor_update(term); +} + +void vimode_begin(struct terminal *term) { + LOG_DBG("vimode: begin"); + printf("VIMODE BEGIN [grid rows=%d]\n", term->grid->num_rows); + + vimode_cancel(term); + + term->vimode.cursor = term->grid->cursor.point; + // From a user's perspective, it is reasonable to expect that the + // mode will launch at the exact position in the scrollback they are + // currently viewing, thus we move the cursor into the view. + clip_cursor_to_view(term); + + /* Reset IME state */ + if (term_ime_is_enabled(term)) { + term_ime_disable(term); + term_ime_enable(term); + } + + term->is_vimming = true; + + term_xcursor_update(term); +} + +void vimode_cancel(struct terminal *term) { + if (!term->is_vimming) { + return; + } + + printf("VIMODE CANCEL\n"); + + cancel_search(term); + + term->is_vimming = false; + + /* Reset IME state */ + if (term_ime_is_enabled(term)) { + term_ime_disable(term); + term_ime_enable(term); + } + + selection_cancel(term); + struct grid *const grid = term->grid; + grid->view = grid->offset; + term_damage_view(term); + term_xcursor_update(term); + render_refresh(term); +} + +static ssize_t matches_cell(const struct terminal *term, + const struct cell *cell, size_t search_ofs) { + assert(search_ofs < term->vimode.search.len); + + char32_t base = cell->wc; + const struct composed *composed = NULL; + + if (base >= CELL_COMB_CHARS_LO && base <= CELL_COMB_CHARS_HI) { + composed = composed_lookup(term->composed, base - CELL_COMB_CHARS_LO); + base = composed->chars[0]; + } + + if (composed == NULL && base == 0 && + term->vimode.search.buf[search_ofs] == U' ') + return 1; + + if (c32ncasecmp(&base, &term->vimode.search.buf[search_ofs], 1) != 0) + return -1; + + if (composed != NULL) { + if (search_ofs + composed->count > term->vimode.search.len) + return -1; + + for (size_t j = 1; j < composed->count; j++) { + if (composed->chars[j] != term->vimode.search.buf[search_ofs + j]) + return -1; + } + } + + return composed != NULL ? composed->count : 1; +} + +static bool find_next(struct terminal *term, enum search_direction direction, + struct coord abs_start, struct coord abs_end, + struct range *match) { +#define ROW_DEC(_r) ((_r) = ((_r) - 1 + grid->num_rows) & (grid->num_rows - 1)) +#define ROW_INC(_r) ((_r) = ((_r) + 1) & (grid->num_rows - 1)) + + struct grid *grid = term->grid; + const bool backward = direction != SEARCH_FORWARD; + + LOG_DBG("%s: start: %dx%d, end: %dx%d", backward ? "backward" : "forward", + abs_start.row, abs_start.col, abs_end.row, abs_end.col); + + xassert(abs_start.row >= 0); + xassert(abs_start.row < grid->num_rows); + xassert(abs_start.col >= 0); + xassert(abs_start.col < term->cols); + + xassert(abs_end.row >= 0); + xassert(abs_end.row < grid->num_rows); + xassert(abs_end.col >= 0); + xassert(abs_end.col < term->cols); + + for (int match_start_row = abs_start.row, match_start_col = abs_start.col;; + backward ? ROW_DEC(match_start_row) : ROW_INC(match_start_row)) { + + const struct row *row = grid->rows[match_start_row]; + if (row == NULL) { + if (match_start_row == abs_end.row) + break; + continue; + } + + for (; backward ? match_start_col >= 0 : match_start_col < term->cols; + backward ? match_start_col-- : match_start_col++) { + if (matches_cell(term, &row->cells[match_start_col], 0) < 0) { + if (match_start_row == abs_end.row && match_start_col == abs_end.col) { + break; + } + continue; + } + + /* + * Got a match on the first letter. Now we'll see if the + * rest of the search buffer matches. + */ + + LOG_DBG("search: initial match at row=%d, col=%d", match_start_row, + match_start_col); + + int match_end_row = match_start_row; + int match_end_col = match_start_col; + const struct row *match_row = row; + size_t match_len = 0; + + for (size_t i = 0; i < term->vimode.search.len;) { + if (match_end_col >= term->cols) { + ROW_INC(match_end_row); + match_end_col = 0; + + match_row = grid->rows[match_end_row]; + if (match_row == NULL) + break; + } + + if (match_row->cells[match_end_col].wc >= CELL_SPACER) { + match_end_col++; + continue; + } + + ssize_t additional_chars = + matches_cell(term, &match_row->cells[match_end_col], i); + if (additional_chars < 0) + break; + + i += additional_chars; + match_len += additional_chars; + match_end_col++; + + while (match_end_col < term->cols && + match_row->cells[match_end_col].wc > CELL_SPACER) { + match_end_col++; + } + } + + if (match_len != term->vimode.search.len) { + /* Didn't match (completely) */ + + if (match_start_row == abs_end.row && match_start_col == abs_end.col) { + break; + } + + continue; + } + + *match = (struct range){ + .start = {match_start_col, match_start_row}, + .end = {match_end_col - 1, match_end_row}, + }; + + return true; + } + + if (match_start_row == abs_end.row && match_start_col == abs_end.col) + break; + + match_start_col = backward ? term->cols - 1 : 0; + } + + return false; +} + +static void search_find_next(struct terminal *term, + enum search_direction direction) { + struct grid *grid = term->grid; + + if (term->vimode.search.len == 0) { + term->vimode.search.match = (struct coord){-1, -1}; + return; + } + + struct coord start = term->vimode.search.match; + size_t len = term->vimode.search.match_len; + + xassert((len == 0 && start.row == -1 && start.col == -1) || + (len > 0 && start.row >= 0 && start.col >= 0)); + + if (len == 0) { + /* No previous match, start from the top, or bottom, of the scrollback */ + switch (direction) { + case SEARCH_FORWARD: + start.row = grid_row_absolute_in_view(grid, 0); + start.col = 0; + break; + + case SEARCH_BACKWARD: + case SEARCH_BACKWARD_SAME_POSITION: + start.row = grid_row_absolute_in_view(grid, term->rows - 1); + start.col = term->cols - 1; + break; + } + } else { + /* Continue from last match */ + xassert(start.row >= 0); + xassert(start.col >= 0); + + switch (direction) { + case SEARCH_BACKWARD_SAME_POSITION: + break; + + case SEARCH_BACKWARD: + if (--start.col < 0) { + start.col = term->cols - 1; + start.row += grid->num_rows - 1; + start.row &= grid->num_rows - 1; + } + break; + + case SEARCH_FORWARD: + if (++start.col >= term->cols) { + start.col = 0; + start.row++; + start.row &= grid->num_rows - 1; + } + break; + } + + xassert(start.row >= 0); + xassert(start.row < grid->num_rows); + xassert(start.col >= 0); + xassert(start.col < term->cols); + } + + LOG_DBG("update: %s: starting at row=%d col=%d " + "(offset = %d, view = %d)", + direction != SEARCH_FORWARD ? "backward" : "forward", start.row, + start.col, grid->offset, grid->view); + + struct coord end = start; + switch (direction) { + case SEARCH_FORWARD: + /* Search forward, until we reach the cell *before* current start */ + if (--end.col < 0) { + end.col = term->cols - 1; + end.row += grid->num_rows - 1; + end.row &= grid->num_rows - 1; + } + break; + + case SEARCH_BACKWARD: + case SEARCH_BACKWARD_SAME_POSITION: + /* Search backwards, until we reach the cell *after* current start */ + if (++end.col >= term->cols) { + end.col = 0; + end.row++; + end.row &= grid->num_rows - 1; + } + break; + } + + struct range match; + bool found = find_next(term, direction, start, end, &match); + if (found) { + LOG_DBG("primary match found at %dx%d", match.start.row, match.start.col); + term->vimode.search.match = match.start; + term->vimode.search.match_len = term->vimode.search.len; + } else { + LOG_DBG("no match"); + term->vimode.search.match = (struct coord){-1, -1}; + term->vimode.search.match_len = 0; + } +#undef ROW_DEC +} + +struct search_match_iterator search_matches_new_iter(struct terminal *term) { + return (struct search_match_iterator){ + .term = term, + .start = {0, 0}, + }; +} + +struct range search_matches_next(struct search_match_iterator *iter) { + struct terminal *term = iter->term; + struct grid *grid = term->grid; + + if (term->vimode.search.match_len == 0) + goto no_match; + + if (iter->start.row >= term->rows) + goto no_match; + + xassert(iter->start.row >= 0); + xassert(iter->start.row < term->rows); + xassert(iter->start.col >= 0); + xassert(iter->start.col < term->cols); + + struct coord abs_start = iter->start; + abs_start.row = grid_row_absolute_in_view(grid, abs_start.row); + + struct coord abs_end = {term->cols - 1, + grid_row_absolute_in_view(grid, term->rows - 1)}; + + /* BUG: matches *starting* outside the view, but ending *inside*, aren't + * matched */ + struct range match; + bool found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match); + if (!found) + goto no_match; + + LOG_DBG("match at (absolute coordinates) %dx%d-%dx%d", match.start.row, + match.start.col, match.end.row, match.end.col); + + /* Convert absolute row numbers back to view relative */ + match.start.row = match.start.row - grid->view + grid->num_rows; + match.start.row &= grid->num_rows - 1; + match.end.row = match.end.row - grid->view + grid->num_rows; + match.end.row &= grid->num_rows - 1; + + LOG_DBG("match at (view-local coordinates) %dx%d-%dx%d, view=%d", + match.start.row, match.start.col, match.end.row, match.end.col, + grid->view); + + /* Assert match end comes *after* the match start */ + xassert( + match.end.row > match.start.row || + (match.end.row == match.start.row && match.end.col >= match.start.col)); + + /* Assert the match starts at, or after, the iterator position */ + xassert(match.start.row > iter->start.row || + (match.start.row == iter->start.row && + match.start.col >= iter->start.col)); + + /* Continue at next column, next time */ + iter->start.row = match.start.row; + iter->start.col = match.start.col + 1; + + if (iter->start.col >= term->cols) { + iter->start.col = 0; + iter->start.row++; /* Overflow is caught in next iteration */ + } + + xassert(iter->start.row >= 0); + xassert(iter->start.row <= term->rows); + xassert(iter->start.col >= 0); + xassert(iter->start.col < term->cols); + return match; + +no_match: + iter->start.row = -1; + iter->start.col = -1; + return (struct range){{-1, -1}, {-1, -1}}; +} + +static void add_wchars(struct terminal *term, char32_t *src, size_t count) { + /* Strip non-printable characters */ + for (size_t i = 0, j = 0, orig_count = count; i < orig_count; i++) { + if (isc32print(src[i])) + src[j++] = src[i]; + else + count--; + } + + if (!search_ensure_size(term, term->vimode.search.len + count)) + return; + + xassert(term->vimode.search.len + count < term->vimode.search.sz); + + memmove(&term->vimode.search.buf[term->vimode.search.cursor + count], + &term->vimode.search.buf[term->vimode.search.cursor], + (term->vimode.search.len - term->vimode.search.cursor) * + sizeof(char32_t)); + + memcpy(&term->vimode.search.buf[term->vimode.search.cursor], src, + count * sizeof(char32_t)); + + term->vimode.search.len += count; + term->vimode.search.cursor += count; + term->vimode.search.buf[term->vimode.search.len] = U'\0'; +} + +void search_add_chars(struct terminal *term, const char *src, size_t count) { + size_t chars = mbsntoc32(NULL, src, count, 0); + if (chars == (size_t)-1) { + LOG_ERRNO("failed to convert %.*s to Unicode", (int)count, src); + return; + } + + char32_t c32s[chars + 1]; + mbsntoc32(c32s, src, count, chars); + 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->is_vimming) { + return; + } + + damage_cursor_cell(term); + term->vimode.cursor.row -= delta; + printf("VIMODE VIEW DOWN [delta=%d]\n", delta); + clip_cursor_to_view(term); +} + +// move_cursor_vertical +// +// Moves the cursor up or down within the scrollback. If the cursor goes outside +// the view bounds, the view is scrolled. +// +// Parameters: +// count - the number of rows to move by. Negative values move up, positive +// down. +// +static void move_cursor_vertical(struct terminal *const term, int const count) { + damage_cursor_cell(term); + struct coord cursor = offset_to_view_relative(term, term->vimode.cursor); + cursor.row += count; + if (cursor.row < 0) { + int const overflow = -cursor.row; + cmd_scrollback_up(term, overflow); + cursor.row = 0; + } else if (cursor.row >= term->rows) { + int const overflow = cursor.row - term->rows + 1; + cmd_scrollback_down(term, overflow); + cursor.row = term->rows - 1; + } + + term->vimode.cursor = view_to_offset_relative(term, cursor); + printf("DIRTYING CELL (%d, %d) [offset=%d; view=%d]\n", + term->vimode.cursor.row, term->vimode.cursor.col, term->grid->offset, + term->grid->view); + damage_cursor_cell(term); + render_refresh(term); +} + +// move_cursor_horizontal +// +// Moves the cursor left or right within the scrollback. The cursor is +// clipped to the view bounds. +// +// Parameters: +// count - the number of columns to move by. Negative values move left, positive +// right. +// +static void move_cursor_horizontal(struct terminal *const term, + int const count) { + damage_cursor_cell(term); + struct coord cursor = term->vimode.cursor; + cursor.col += count; + if (cursor.col < 0) { + cursor.col = 0; + } else if (cursor.col >= term->cols) { + cursor.col = term->cols - 1; + } + + term->vimode.cursor = cursor; + damage_cursor_cell(term); + render_refresh(term); +} + +static void execute_vimode_binding(struct seat *seat, struct terminal *term, + const struct key_binding *binding, + uint32_t serial) { + const enum bind_action_vimode action = binding->action; + + if (term->grid != &term->normal) { + return; + } + + switch (action) { + case BIND_ACTION_VIMODE_NONE: + break; + + case BIND_ACTION_VIMODE_UP: + move_cursor_vertical(term, -1); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_DOWN: + move_cursor_vertical(term, 1); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_LEFT: + move_cursor_horizontal(term, -1); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_RIGHT: + move_cursor_horizontal(term, 1); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_UP_PAGE: + cmd_scrollback_up(term, term->rows); + clip_cursor_to_view(term); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_DOWN_PAGE: + cmd_scrollback_down(term, term->rows); + clip_cursor_to_view(term); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_UP_HALF_PAGE: + cmd_scrollback_up(term, max(term->rows / 2, 1)); + clip_cursor_to_view(term); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_DOWN_HALF_PAGE: + cmd_scrollback_down(term, max(term->rows / 2, 1)); + clip_cursor_to_view(term); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_UP_LINE: + cmd_scrollback_up(term, 1); + clip_cursor_to_view(term); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_DOWN_LINE: + cmd_scrollback_down(term, 1); + clip_cursor_to_view(term); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_FIRST_LINE: + cmd_scrollback_up(term, term->grid->num_rows); + term->vimode.cursor.row = -term->grid->num_rows; + clip_cursor_to_view(term); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_LAST_LINE: + cmd_scrollback_down(term, term->grid->num_rows); + term->vimode.cursor.row = term->grid->num_rows; + clip_cursor_to_view(term); + update_selection(seat, term); + break; + + case BIND_ACTION_VIMODE_CANCEL: { + // We handle multiple actions here (in that exact order): + // - clear search (handled by vimode-search bindings), + // - return to the normal mode, + // - exit vimode. + if (is_mode_visual(term->vimode.mode)) { + selection_cancel(term); + term->vimode.mode = VI_MODE_NORMAL; + } else { + vimode_cancel(term); + } + break; + } + + case BIND_ACTION_VIMODE_START_SEARCH: + start_search(term); + break; + + // TODO (kociap): Implement. + case BIND_ACTION_VIMODE_FIND_NEXT: + case BIND_ACTION_VIMODE_FIND_PREV: + break; + + case BIND_ACTION_VIMODE_ENTER_VISUAL: + case BIND_ACTION_VIMODE_ENTER_VLINE: + case BIND_ACTION_VIMODE_ENTER_VBLOCK: { + enum vi_mode mode = VI_MODE_VISUAL; + if (action == BIND_ACTION_VIMODE_ENTER_VLINE) { + mode = VI_MODE_VLINE; + } else if (action == BIND_ACTION_VIMODE_ENTER_VBLOCK) { + mode = VI_MODE_VBLOCK; + } + + enum selection_kind const selection = selection_kind_from_vi_mode(mode); + if (is_mode_visual(term->vimode.mode)) { + // "Entering" the same mode exits it. Otherwise, we switch from + // another visual mode. + if (term->vimode.mode == mode) { + selection_cancel(term); + term->vimode.mode = VI_MODE_NORMAL; + } else { + selection_cancel(term); + struct coord const start = term->vimode.selection.start; + selection_start(term, start.col, start.row, selection, false); + struct coord const cursor = term->grid->cursor.point; + selection_update(term, cursor.col, cursor.row); + term->vimode.mode = mode; + } + } else if (term->vimode.mode == VI_MODE_NORMAL) { + struct coord const cursor = term->grid->cursor.point; + selection_start(term, cursor.col, cursor.row, selection, false); + selection_update(term, cursor.col, cursor.row); + term->vimode.selection.start = cursor; + term->vimode.mode = mode; + } + // render_refresh(term); + break; + } + + case BIND_ACTION_VIMODE_YANK: + // TODO (kociap): Should yank executed in non-visual mode copy the + // current line? + if (is_mode_visual(term->vimode.mode)) { + selection_finalize(seat, term, serial); + // finalize only copies, but we also want to clear the selection + selection_cancel(term); + term->vimode.mode = VI_MODE_NORMAL; + } + break; + + // case BIND_ACTION_SEARCH_COMMIT: + // selection_finalize(seat, term, serial); + // search_cancel_keep_selection(term); + // return true; + + // case BIND_ACTION_SEARCH_CLIPBOARD_PASTE: + // text_from_clipboard(seat, term, &from_clipboard_cb, + // &from_clipboard_done, + // term); + // *update_search_result = *redraw = true; + // return true; + // + // case BIND_ACTION_SEARCH_PRIMARY_PASTE: + // text_from_primary(seat, term, &from_clipboard_cb, &from_clipboard_done, + // term); + // *update_search_result = *redraw = true; + // return true; + // + // case BIND_ACTION_SEARCH_UNICODE_INPUT: + // unicode_mode_activate(term); + // return true; + + case BIND_ACTION_VIMODE_COUNT: + BUG("Invalid action type"); + break; + + default: + BUG("Unhandled action type"); + break; + } +} + +static void execute_vimode_search_binding(struct seat *seat, + struct terminal *term, + const struct key_binding *binding, + uint32_t serial) { + const enum bind_action_vimode action = binding->action; + struct vimode_search *const search = &term->vimode.search; + + if (term->grid != &term->normal) { + return; + } + + switch (action) { + case BIND_ACTION_VIMODE_SEARCH_NONE: + break; + + // TODO (kociap): implement + case BIND_ACTION_VIMODE_SEARCH_CONFIRM: + break; + + case BIND_ACTION_VIMODE_SEARCH_CANCEL: + cancel_search(term); + 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'; + render_refresh_vimode_search_box(term); + } + 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_COUNT: + BUG("Invalid action type"); + break; + + default: + BUG("Unhandled action type"); + break; + } +} + +static struct key_binding const * +match_binding(key_binding_list_t const *bindings, uint32_t key, + xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed, + const xkb_keysym_t *raw_syms, size_t raw_count) { + /* Match untranslated symbols */ + tll_foreach(*bindings, it) { + const struct key_binding *bind = &it->item; + + if (bind->mods != mods || bind->mods == 0) + continue; + + for (size_t i = 0; i < raw_count; i++) { + if (bind->k.sym == raw_syms[i]) { + return bind; + } + } + } + + /* Match translated symbol */ + tll_foreach(*bindings, it) { + const struct key_binding *bind = &it->item; + + if (bind->k.sym == sym && bind->mods == (mods & ~consumed)) { + return bind; + } + } + + /* Match raw key code */ + tll_foreach(*bindings, it) { + const struct key_binding *bind = &it->item; + + if (bind->mods != mods || bind->mods == 0) + continue; + + tll_foreach(bind->k.key_codes, code) { + if (code->item == key) { + return bind; + } + } + } + + return NULL; +} + +void vimode_input(struct seat *seat, struct terminal *term, + const struct key_binding_set *bindings, uint32_t key, + xkb_keysym_t sym, xkb_mod_mask_t mods, + xkb_mod_mask_t consumed, const xkb_keysym_t *raw_syms, + size_t raw_count, uint32_t serial) { + LOG_DBG("vimode: input: sym=%d/0x%x, mods=0x%08x, consumed=0x%08x", sym, sym, + mods, consumed); + + enum xkb_compose_status compose_status = + seat->kbd.xkb_compose_state != NULL + ? xkb_compose_state_get_status(seat->kbd.xkb_compose_state) + : XKB_COMPOSE_NOTHING; + + // bool update_search_result = false; + + if (!term->vimode.is_searching) { + struct key_binding const *const binding = match_binding( + &bindings->vimode, key, sym, mods, consumed, raw_syms, raw_count); + if (binding != NULL) { + execute_vimode_binding(seat, term, binding, serial); + } + } else { + struct key_binding const *const binding = + match_binding(&bindings->vimode_search, key, sym, mods, consumed, + raw_syms, raw_count); + if (binding != NULL) { + execute_vimode_search_binding(seat, term, binding, serial); + } else { + // If not a binding, then handle it as text input. + uint8_t buf[64] = {0}; + int count = 0; + + if (compose_status == XKB_COMPOSE_COMPOSED) { + count = xkb_compose_state_get_utf8(seat->kbd.xkb_compose_state, + (char *)buf, sizeof(buf)); + xkb_compose_state_reset(seat->kbd.xkb_compose_state); + } else if (compose_status == XKB_COMPOSE_CANCELLED) { + count = 0; + } else { + count = xkb_state_key_get_utf8(seat->kbd.xkb_state, key, (char *)buf, + sizeof(buf)); + } + + // update_search_result = redraw = count > 0; + + if (count > 0) { + search_add_chars(term, (const char *)buf, count); + render_refresh_vimode_search_box(term); + } + } + } + + LOG_DBG("search: buffer: %ls", (const wchar_t *)term->vimode.search.buf); + // if (update_search_result) + // search_find_next(term, search_direction); +} diff --git a/vimode.h b/vimode.h new file mode 100644 index 00000000..7946b110 --- /dev/null +++ b/vimode.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "key-binding.h" +#include "terminal.h" + +void vimode_begin(struct terminal *term); + +/* vimode_search_begin + * + * Enter search mode directly without needing to interact with the + * vimode first. Enters vimode as well. + */ +void vimode_search_begin(struct terminal *term); + +void vimode_cancel(struct terminal *term); +void vimode_input(struct seat *seat, struct terminal *term, + const struct key_binding_set *bindings, uint32_t key, + xkb_keysym_t sym, xkb_mod_mask_t mods, + xkb_mod_mask_t consumed, const xkb_keysym_t *raw_syms, + size_t raw_count, uint32_t serial); +// void search_add_chars(struct terminal *term, const char *text, size_t len); + +struct search_match_iterator { + struct terminal *term; + struct coord start; +}; + +struct search_match_iterator search_matches_new_iter(struct terminal *term); +struct range search_matches_next(struct search_match_iterator *iter); + +void vimode_view_down(struct terminal *term, int delta);