[FEATURE] Vim-mode-aware keybinding contexts — let `enter` newline in INSERT and submit in NORMAL
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
With vim mode enabled, there is no way to make a key behave differently in INSERT mode than in NORMAL mode — including Enter.
The specific thing I want is the standard vim-editor arrangement:
| Vim mode | Enter should |
|---|---|
| INSERT | insert a newline |
| NORMAL | submit the prompt |
This is not expressible today, and I confirmed that against the shipped binary rather than guessing. Three separate mechanisms could carry it, and all three are closed:
1. Keybinding contexts have no vim-mode qualifier. The full context list in 2.1.226 is Global, Chat, Autocomplete, Confirmation, Help, Transcript, HistorySearch, Task, ThemePicker, Settings, Tabs, Attachments, Footer, MessageSelector, DiffDialog, DiffPanel, ModelPicker, Select, Plugin, Scroll. There is no ChatInsert, no ChatNormal, no mode qualifier of any kind. Chat is mode-agnostic by construction, so anything written there necessarily applies in both modes.
2. vimInsertModeRemaps is the only vim-specific setting, and its own schema rules this out. Its description reads: "Vim INSERT-mode key-sequence remaps, e.g. {"jj": "<Esc>"}. Each key is exactly two printable characters typed in sequence; "<Esc>" (return to NORMAL mode) is the only supported target." Enter is not two printable characters, and "insert a newline" is not <Esc>. It fails both halves. A grep for "vim[A-Za-z]+" across the whole binary returns exactly one hit — this setting. There is nothing else.
3. Nothing can extend it. The plugin manifest component list is commands, agents, skills, hooks, outputStyles, themes, mcpServers, lspServers, experimental — no keybindings, and keybindings does not appear anywhere in the manifest key union either. keybindings.json resolves through a single hardcoded path (join(configDir(), "keybindings.json")) with no plugin layer and no project-level merge. Hooks are event-based, not keypress-based. So a user cannot work around this with an extension, and a plugin author cannot ship it.
The net result is that vim mode gives you the editing model but not the submitting model. Every newline still costs a modifier, which is the one thing vim mode ought to have made unnecessary.
Proposed Solution
Add vim-mode-aware keybinding contexts, so that the existing, already-good keybindings.json mechanism can express mode-conditional bindings:
{
"bindings": [
{ "context": "ChatVimInsert", "bindings": { "enter": "chat:newline" } },
{ "context": "ChatVimNormal", "bindings": { "enter": "chat:submit" } }
]
}
Names are illustrative — the shape is what matters. Suggested resolution order: ChatVimInsert / ChatVimNormal are consulted before Chat, and fall through to Chat when unbound, so nothing changes for anyone who does not write them.
Acceptance criteria
- With vim mode ON and neither context bound, behaviour is identical to today (no existing user affected).
- With the two blocks above,
Enterinserts a newline in INSERT and submits in NORMAL. - With vim mode OFF, the vim contexts are simply never active;
Chatbehaves as it does now. - Any action bindable in
Chatis bindable in the vim contexts — this is a scoping feature, not a new special case forEnter.
The general form of the ask, since the report asks for one feature: the extension point is "let a keybinding be scoped to the editor mode it applies in." Mode-conditional Enter is the motivating case, but the same mechanism covers every other key someone wants to behave differently per mode, without a new setting per key. A narrower alternative — widening vimInsertModeRemaps to accept named keys and arbitrary actions — would also solve my case, but it would build a second, parallel keybinding system next to keybindings.json rather than reusing the one that already exists.
Alternative Solutions
vimInsertModeRemaps— cannot express it: two-printable-characters keys only,<Esc>only as a target. See above.- A mode-agnostic
Chatbinding (enter→chat:newline,alt+enter→chat:submit) — this is what I run today. It delivers the INSERT half exactly and the NORMAL half not at all: submitting always costs a modifier, including from NORMAL where a bareEnterhas nothing else to do. It is a workaround, and it is strictly worse than what vim users expect. - #74655 (opt-in state-independent submit key) — related area, but explicitly not this. That request's central invariant is that submit behaviour must not depend on editor state; mine is that it should depend on vim mode specifically. Both can coexist: #74655 is for users without vim mode, this is for users with it.
- A plugin — impossible. Plugins cannot contribute keybindings (see Problem Statement).
Priority
Medium - Would significantly improve my workflow
Feature Category
Interactive mode (TUI)
Use Case Example
I write long, multi-line prompts in the terminal. With vim mode on:
- I compose in INSERT mode and press
Enterto start a new line — today it fires the half-written prompt, so every newline costs aShift+Enter(orAlt+Enter). - When the prompt is finished I press
Escto leave INSERT. I am now in NORMAL mode, whereEnterhas no editing meaning at all — it is the natural "I am done, send it" key, and it is exactly the key I cannot bind.
So the mode I am in already carries the intent — "still writing" vs "finished writing" — and Claude Code already tracks it and displays it. The request is just to let a keybinding read it.
Additional Context
Environment: Claude Code 2.1.226, Linux, Alacritty 0.17.0, vim mode enabled, editorMode: "vim".
Prior vim-remap requests I found are all narrower and all resolved by vimInsertModeRemaps as shipped — #25306 and #53039 (both jj/jk → Esc), #16717 (yank/paste/undo/redo). #76684 asks for a bindable chat:redo action in NORMAL mode, which is a new action rather than mode scoping for existing actions; if mode-aware contexts existed, that request would become one line of user config.
One implementation note that may be relevant: the machinery looks close to already being there. keybindings.json is a list of {context, bindings} blocks with a documented fall-through, contexts are already resolved dynamically per focused component, and the vim mode is already first-class state (it drives the -- INSERT -- / NORMAL footer indicator). This reads like adding two entries to the context enum and consulting them ahead of Chat, rather than new infrastructure.