From 919f31ffcbc6ea03cd7b690b4c79651b082a0364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 29 Jul 2020 17:27:01 +0200 Subject: [PATCH] search/config: configurable key bindings for (scrollback) search mode --- CHANGELOG.md | 1 + config.c | 133 +++++++++++++++++++++++++++++++++++++++++++---- doc/footrc.5.scd | 70 +++++++++++++++++++++++++ footrc | 18 +++++++ 4 files changed, 213 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf1afb01..ed1fe1c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ controlled by the **scrollback-indicator-position** and **scrollback-indicator-format** options in `footrc` (https://codeberg.org/dnkl/foot/issues/42). +* Key bindings in _scollback search_ mode are now configurable. ### Deprecated diff --git a/config.c b/config.c index 5a1bc282..5220f8fb 100644 --- a/config.c +++ b/config.c @@ -48,7 +48,7 @@ static const uint32_t default_bright[] = { 0xffffff, }; -static const char *binding_action_map[] = { +static const char *const binding_action_map[] = { [BIND_ACTION_NONE] = NULL, [BIND_ACTION_SCROLLBACK_UP] = "scrollback-up", [BIND_ACTION_SCROLLBACK_DOWN] = "scrollback-down", @@ -70,6 +70,29 @@ static const char *binding_action_map[] = { static_assert(ALEN(binding_action_map) == BIND_ACTION_COUNT, "binding action map size mismatch"); +static const char *const search_binding_action_map[] = { + [BIND_ACTION_SEARCH_NONE] = NULL, + [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_EXTEND_WORD] = "extend-to-word-boundary", + [BIND_ACTION_SEARCH_EXTEND_WORD_WS] = "extend-to-next-whitespace", +}; + +static_assert(ALEN(search_binding_action_map) == BIND_ACTION_SEARCH_COUNT, + "search binding action map size mismatch"); + static char * get_shell(void) { @@ -523,6 +546,7 @@ parse_section_csd(const char *key, const char *value, struct config *conf, static bool verify_key_combo(const struct config *conf, enum bind_action_normal action, + const char *const binding_action_map[], const char *combo, const char *path, unsigned lineno) { tll_foreach(conf->bindings.key, it) { @@ -602,7 +626,7 @@ parse_section_key_bindings( return true; } - if (!verify_key_combo(conf, action, value, path, lineno)) { + if (!verify_key_combo(conf, action, binding_action_map, value, path, lineno)) { return false; } @@ -641,6 +665,95 @@ parse_section_key_bindings( } +static bool +parse_section_search_bindings( + const char *key, const char *value, struct config *conf, + const char *path, unsigned lineno) +{ +#if 0 + const char *pipe_cmd = NULL; + size_t pipe_len = 0; + + if (value[0] == '[') { + const char *pipe_cmd_end = strrchr(value, ']'); + if (pipe_cmd_end == NULL) { + LOG_ERR("%s:%d: unclosed '['", path, lineno); + return false; + } + + pipe_cmd = &value[1]; + pipe_len = pipe_cmd_end - pipe_cmd; + + value = pipe_cmd_end + 1; + } +#endif + + for (enum bind_action_search action = 0; + action < BIND_ACTION_SEARCH_COUNT; + action++) + { + if (search_binding_action_map[action] == NULL) + continue; + + if (strcmp(key, search_binding_action_map[action]) != 0) + continue; + + if (strcasecmp(value, "none") == 0) { + tll_foreach(conf->bindings.search, it) { + if (it->item.action == action) { + free(it->item.key); + // free(it->item.pipe_cmd); + tll_remove(conf->bindings.search, it); + } + } + return true; + } + + if (!verify_key_combo(conf, action, search_binding_action_map, value, path, lineno)) { + return false; + } + + bool already_added = false; + tll_foreach(conf->bindings.search, it) { + if (it->item.action == action +#if 0 + && + ((it->item.pipe_cmd == NULL && pipe_cmd == NULL) || + (it->item.pipe_cmd != NULL && pipe_cmd != NULL && + strncmp(it->item.pipe_cmd, pipe_cmd, pipe_len) == 0)) +#endif + ) + { + + free(it->item.key); + // free(it->item.pipe_cmd); + + it->item.key = strdup(value); +#if 0 + it->item.pipe_cmd = pipe_cmd != NULL + ? strndup(pipe_cmd, pipe_len) : NULL; +#endif + already_added = true; + break; + } + } + + if (!already_added) { + struct config_key_binding_search binding = { + .action = action, + .key = strdup(value), + // .pipe_cmd = pipe_cmd != NULL ? strndup(pipe_cmd, pipe_len) : NULL, + }; + tll_push_back(conf->bindings.search, binding); + } + return true; + } + + LOG_ERR("%s:%u: invalid key: %s", path, lineno, key); + return false; + +} + static bool parse_section_mouse_bindings( const char *key, const char *value, struct config *conf, @@ -785,6 +898,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path) SECTION_CURSOR, SECTION_CSD, SECTION_KEY_BINDINGS, + SECTION_SEARCH_BINDINGS, SECTION_MOUSE_BINDINGS, SECTION_TWEAK, SECTION_COUNT, @@ -799,13 +913,14 @@ parse_config_file(FILE *f, struct config *conf, const char *path) parser_fun_t fun; const char *name; } section_info[] = { - [SECTION_MAIN] = {&parse_section_main, "main"}, - [SECTION_COLORS] = {&parse_section_colors, "colors"}, - [SECTION_CURSOR] = {&parse_section_cursor, "cursor"}, - [SECTION_CSD] = {&parse_section_csd, "csd"}, - [SECTION_KEY_BINDINGS] = {&parse_section_key_bindings, "key-bindings"}, - [SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"}, - [SECTION_TWEAK] = {&parse_section_tweak, "tweak"}, + [SECTION_MAIN] = {&parse_section_main, "main"}, + [SECTION_COLORS] = {&parse_section_colors, "colors"}, + [SECTION_CURSOR] = {&parse_section_cursor, "cursor"}, + [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_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"}, + [SECTION_TWEAK] = {&parse_section_tweak, "tweak"}, }; static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch"); diff --git a/doc/footrc.5.scd b/doc/footrc.5.scd index 6bba164f..7ce8b1dd 100644 --- a/doc/footrc.5.scd +++ b/doc/footrc.5.scd @@ -265,6 +265,76 @@ e.g. *search-start=none*. Default: _not bound_ +# SECTION: search-bindings + +This section lets you override the default key bindings used in +scrollback search mode. The syntax is exactly the same as the regular +**key-bindings**. + +*cancel* + Aborts the search. The viewport is restored and the _primary + selection_ is **not** updated. Default: _Control+g Escape_. + +*commit* + Exit search mode and copy current selection into the _primary + selection_. Viewport is **not** restored. To copy the selection to + the regular _clipboard_, use *Control+Shift+C*. Default: _Return_. + +*find-prev* + Search **backwards** in the scrollback history for the next + match. Default: _Control+r_. + +*find-next* + Searchs **forwards** in the scrollback history for the next + match. Default: _Control+s_. + +*cursor-left* + Moves the cursor in the search box one **character** to the + left. Default: _Left Control+b_. + +*cursor-left-word* + Moves the cursor in the search box one **word** to the + left. Default: _Control+Left Mod1+b_. + +*cursor-right* + Moves the cursor in the search box one **character** to the + right. Default: _Right Control+f_. + +*cursor-right-word* + Moves the cursor in the search box one **word** to the + right. Default: _Control+Left Mod1+b_. + +*cursor-home* + Moves the cursor in the search box to the beginning of the + input. Default: _Home Control+a_. + +*cursor-end* + Moves the cursor in the search box to the end of the + input. Default: _End Control+e_. + +*delete-prev* + Deletes the **character before** the cursor. Default: _BackSpace_. + +*delete-prev-word* + Deletes the **word before** the cursor. Default: _Mod1+BackSpace + Control+BackSpace_. + +*delete-next* + Deletes the **character after** the cursor. Default: _Delete_. + +*delete-next-word* + Deletes the **word after** the cursor. Default: _Mod1+b + Control+Delete_. + +*extend-to-word-boundary* + Extend current selection to the next word boundary. Default: + _Control+w_. + +*extend-to-next-whitespace* + Extend the current selection to the next whitespace. Default: + _Control+Shift+W_. + + # SECTION: mouse-bindings This section lets you override the default mouse bindings. diff --git a/footrc b/footrc index 30eeee39..3336cb2e 100644 --- a/footrc +++ b/footrc @@ -63,5 +63,23 @@ # pipe-visible=[sh -c "xurls | bemenu | xargs -r firefox"] none # pipe-scrollback=[sh -c "xurls | bemenu | xargs -r firefox"] none +[search-bindings] +# cancel=Control+g Escape +# commit=Return +# find-prev=Control+r +# find-next=Control+s +# cursor-left=Left Control+b +# cursor-left-word=Control+Left Mod1+b +# cursor-right=Right Control+f +# cursor-right-word=Control+Right Mod1+f +# cursor-home=Home Control+a +# cursor-end=End Control+e +# delete-prev=BackSpace +# delete-prev-word=Mod1+BackSpace Control+BackSpace +# delete-next=Delete +# delete-next-word=Mod1+d Control+Delete +# extend-to-word-boundary=Control+w +# extend-to-next-whitespace=Control+Shift+W + [mouse-bindings] # primary-paste=BTN_MIDDLE