feat: Allow for user-defined "chord" / multi-key keybindings (and correct vi motions when targeting <space> char)#1016
Open
benvansleen wants to merge 8 commits intonushell:mainfrom
Conversation
This was referenced Jan 28, 2026
Author
|
Am experimenting with the nushell-side of things for configuring in Currently, keychord configuration looks like this: $env.config.keybindings ++= [
{
name: "jj_normal"
modifier: "none"
keycode: [ "char_j", "char_j" ]
mode: "vi_insert"
event: { send: "vichangemode", mode: "normal" }
}
{
name: "save_buffer"
modifier: "control"
keycode: [ "char_x", "char_s" ]
mode: "emacs"
event: { send: "submit" }
}
{
name: "mixed_mods"
modifier: "none"
keycode: [
{ modifier: "alt", keycode: "char_w" }
{ modifier: "none", keycode: "char_j" }
]
mode: "emacs"
event: { send: "openeditor" }
}
]How do we feel about it? |
vi motions when targeting <space> char)vi motions when targeting <space> char)
|
I'm not in a position to review this, but just want to say that this is what's keeping me from using nushell – I've have a serious |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I care a great deal about nushell's vi mode support. I got myself addicted to "jj" to exit vi normal mode in basically all cli tools with a vi mode -- without it, I feel like I've lost my fingers!
A while back, I proposed #670. Since
reedlinedoes not support multi-key "chords," I special-cased logic intovi/mod.rsto listen for repeated keypresses in insert mode for a designated "exit-insert-mode" trigger.The feedback led to a broader discussion. The main gist (as I understood it at the time) was that we should not special-case this logic; ideally, we would generalize this functionality to enable other kinds of user-defined key chords.
In the meantime, I've been maintaining a personal
reedlinefork with this special-case logic. It's fulfilled my needs, but is kind of a PITA. So: I thought why not take another stab?Full disclosure: I've been trying to experiment with LSP-informed LLM code generation for languages with well-developed type systems (eg through something like rust LSP w/ opencode). The first draft of this PR was predominantly LLM generated, but I have reviewed the changes & am test-driving this as my daily-driver shell.
User-facing changes
ReedlineEvent:ViChangeModehandling to better mimic vi/vim behavior (eg when moving from insert -> normal modes, the cursor should move left 1 char)emacsandvi) now track a sequence state of keypressesf<space>does not behave as expected in nushell; it basically inserts a space at b.o.l. and jacks up the undo bufferWhat does this achieve for
nushell?in
nu-cli/src/reedline_config.rs, you could:Obviously, this would be better configured as part of the user's nushell startup script. If this PR is approved / we like this direction, I'll submit work on the nushell side to allow for configuring
$env.config.keybindingsaccordingly.How does it work?
Key event flow (Emacs / Vi):
KeyEvent -> normalize -> KeyCombination
-> KeySequenceState.process_combo(...)
-> SequenceResolution.into_event(|combo| fallback(combo))
-> ReedlineEvent returned to engine
Notes:
pending_exactholds an exact match that is also a prefix of a longer sequence.If the longer sequence fails, we emit the saved event and keep trailing keys.
SequenceResolution.events= matched sequencesSequenceResolution.combos= raw keys to replay through fallbackinto_eventcombines both into a single ReedlineEventVi specifics:
so
comboscan be re-fed into vi’s grammar instead of becoming edits.Timeout path:
Engine timeout -> EditMode.flush_pending_sequence()
-> KeySequenceState.flush_with_combos()
-> SequenceResolution.into_event(...)
-> ReedlineEvent
Walking through an example!
Scenario: insert mode, sequence binding
j j→ViChangeMode("normal".into())Initial state:
KeySequenceState.buffer = []pending_exact = Nonesequence_bindings contains [j, j] -> ViChangeMode("normal".into())Step 2: user presses j again
Result:
ViChangeMode("normal".into())is emitted immediately.Step 3: If the user doesn’t press another key
Failure/timeout example: