From 37b796b1c15096b62e87c827e19ece1c0a61ab87 Mon Sep 17 00:00:00 2001 From: Peter Adams <18162810+Maxteabag@users.noreply.github.com> Date: Mon, 25 May 2026 13:52:15 +0200 Subject: [PATCH] feat(keybindings): expose cursor_word_end / cursor_WORD_end as unbound actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds vim's `e`/`E` end-of-word motions as customizable actions in query_normal mode. The actions are declared but left unbound by default so they don't conflict with the existing `e` → focus_explorer binding in the navigation context. Users who want vim-style behavior can bind them in `~/.config/sqlit/keymap.json`, e.g.: { "keymap": { "action_keys": { "query_normal": { "cursor_word_end": "e", "cursor_WORD_end": "E" }, "navigation": { "focus_explorer": "ctrl+e" } } } } The motions themselves already work as operator suffixes (`de`, `ye`, `ce`) via the existing motion registry; this just lets users use them as standalone cursor motions too. --- sqlit/core/keymap.py | 5 +++++ sqlit/domains/query/state/query_normal.py | 2 ++ sqlit/domains/query/ui/mixins/query_editing_cursor.py | 8 ++++++++ 3 files changed, 15 insertions(+) diff --git a/sqlit/core/keymap.py b/sqlit/core/keymap.py index 79d814f0..fe5c1bfa 100644 --- a/sqlit/core/keymap.py +++ b/sqlit/core/keymap.py @@ -371,6 +371,11 @@ def _build_action_keys(self) -> list[ActionKeyDef]: ActionKeyDef("W", "cursor_WORD_forward", "query_normal"), ActionKeyDef("b", "cursor_word_back", "query_normal"), ActionKeyDef("B", "cursor_WORD_back", "query_normal"), + # Unbound by default — users can bind in their keymap.json + # (typically "e"/"E"), but they must also rebind whatever + # `focus_explorer` action is currently using those keys. + ActionKeyDef("", "cursor_word_end", "query_normal"), + ActionKeyDef("", "cursor_WORD_end", "query_normal"), ActionKeyDef("0", "cursor_line_start", "query_normal"), ActionKeyDef("circumflex_accent", "cursor_first_non_blank", "query_normal"), ActionKeyDef("dollar_sign", "cursor_line_end", "query_normal"), diff --git a/sqlit/domains/query/state/query_normal.py b/sqlit/domains/query/state/query_normal.py index 2f2f6ae6..dedbda7c 100644 --- a/sqlit/domains/query/state/query_normal.py +++ b/sqlit/domains/query/state/query_normal.py @@ -35,6 +35,8 @@ def _setup_actions(self) -> None: self.allows("cursor_WORD_forward", help="Move to next WORD") self.allows("cursor_word_back", help="Move to previous word") self.allows("cursor_WORD_back", help="Move to previous WORD") + self.allows("cursor_word_end", help="Move to end of word") + self.allows("cursor_WORD_end", help="Move to end of WORD") self.allows("cursor_first_non_blank", help="Move to first non-blank") self.allows("cursor_line_start", help="Move to line start") self.allows("cursor_line_end", help="Move to line end") diff --git a/sqlit/domains/query/ui/mixins/query_editing_cursor.py b/sqlit/domains/query/ui/mixins/query_editing_cursor.py index 106fb5a5..3aed3de0 100644 --- a/sqlit/domains/query/ui/mixins/query_editing_cursor.py +++ b/sqlit/domains/query/ui/mixins/query_editing_cursor.py @@ -131,6 +131,14 @@ def action_cursor_WORD_back(self: QueryMixinHost) -> None: """Move cursor to previous WORD (B).""" self._move_with_motion("B") + def action_cursor_word_end(self: QueryMixinHost) -> None: + """Move cursor to end of word (e). Unbound by default.""" + self._move_with_motion("e") + + def action_cursor_WORD_end(self: QueryMixinHost) -> None: + """Move cursor to end of WORD (E). Unbound by default.""" + self._move_with_motion("E") + def action_cursor_first_non_blank(self: QueryMixinHost) -> None: """Move cursor to first non-whitespace character (^).""" self._move_with_motion("^")