Skip to content

feat(tui): vim motions in prompt input - #12679

Closed
leohenon wants to merge 28 commits into
anomalyco:devfrom
leohenon:feat/vim-prompt-input-core
Closed

feat(tui): vim motions in prompt input#12679
leohenon wants to merge 28 commits into
anomalyco:devfrom
leohenon:feat/vim-prompt-input-core

Conversation

@leohenon

@leohenonleohenon commented Feb 8, 2026

Copy link
Copy Markdown

What does this PR do?

Closes#1764
Closes#11111

Adds optional Vim motions to the prompt input. Enable with tui.vim: true or toggle from the menu. Disabled by default.

demo

Supported:

  • Mode switching: i I a A o O S, cc, cw, Esc
  • Motions: h j k l, w b e, W B E, 0 ^ $
  • Deletes: x, dd, dw
  • Session navigation: gg/G
  • Scrolling: Ctrl+e/y/d/u/f/b
  • Enter in normal mode submits

Implementation: Vim state isolated in /vim module. Handler owns mode + pending operator state. Mode resets to insert after submit. Visual mode intentionally excluded.

I saw thdxr's concern in #1764 about partial Vim feeling incomplete. This PR covers only the core motions needed for prompt editing and is intentionally kept small to make review manageable. I have a fuller implementation with yank/paste, visual mode and other motions that I can split into follow-up PRs if this direction looks good or keep it at this core subset as it seems many people are satisfied with a minimal vim mode.

How did you verify your code works?

I use the fuller implementation daily. Reimplemented the core subset for this PR and verified manually (editing, scrolling, session switching, edge cases with pending operators, empty buffers, multiline). Added targeted tests for mode transitions, motions, deletes, pending state, and submit reset.

Not knowing when this will be reviewed - If you would like to use this now with a more complete vim mode, use opencode-vim.

CopilotAI review requested due to automatic review settings February 8, 2026 09:43
@github-actions

Copy link
Copy Markdown
Contributor

Hey! Your PR title vim motions in prompt input box doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

No duplicate PRs found

@leohenonleohenon changed the title vim motions in prompt input boxfeat(tui): vim motions in prompt input boxFeb 8, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional Vim-style keybinding layer for the TUI prompt input (configurable via tui.vim or a settings toggle), including a mode indicator, motion/operator handling, and related documentation/tests.

Changes:

  • Introduces a /vim module (state, handler, motions, scroll/jump mapping, indicator) and wires it into the prompt input flow.
  • Adds a command-palette setting to toggle Vim input, plus config schema + docs updates.
  • Adds a targeted test suite covering mode transitions, motions, operators, scrolling, and jumps.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
packages/web/src/content/docs/tui.mdxDocuments Vim mode and supported keys for the TUI prompt.
packages/web/src/content/docs/config.mdxDocuments tui.vim config flag.
packages/opencode/src/config/config.tsAdds tui.vim boolean to config schema.
packages/opencode/src/cli/cmd/tui/component/vim/vim-state.tsNew Vim mode/pending state container.
packages/opencode/src/cli/cmd/tui/component/vim/vim-scroll.tsMaps Ctrl+key combos to scroll actions.
packages/opencode/src/cli/cmd/tui/component/vim/vim-motions.tsImplements cursor motions + delete/edit primitives on the textarea.
packages/opencode/src/cli/cmd/tui/component/vim/vim-motion-jump.tsImplements gg/G jump behavior.
packages/opencode/src/cli/cmd/tui/component/vim/vim-indicator.tsComputes INSERT/NORMAL indicator text.
packages/opencode/src/cli/cmd/tui/component/vim/vim-handler.tsKey-event interpreter coordinating state + motions + submit/scroll/jump.
packages/opencode/src/cli/cmd/tui/component/vim/index.tsProvides useVimEnabled() integration with KV + config.
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsxIntegrates Vim handler/state into prompt input, cursor styling, submit behavior, and indicator rendering.
packages/opencode/src/cli/cmd/tui/app.tsxAdds command-palette toggle to enable/disable Vim input.
packages/opencode/test/cli/tui/vim-motions.test.tsAdds tests for handler behavior, operators, and scroll mapping.
Comments suppressed due to low confidence (1)

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx:918

  • The inline comment says "Don't preventDefault" but the code immediately calls e.preventDefault(). Either update the comment to match the behavior or adjust the behavior if the intent really is to let the textarea handle the event.
 await exit()
// Don't preventDefault - let textarea potentially handle the event
e.preventDefault()
return

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +102 to +106
let pos = offset
if (pos < text.length) pos++
while (pos < text.length && !match(text[pos])) pos++
while (pos < text.length && match(text[pos])) pos++
if (pos > offset + 1) pos--

CopilotAIFeb 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wordEnd() currently increments pos before checking whether the cursor is already on a word character. This makes e/E behave incorrectly for 1-character words or when the cursor is on the last character of a word followed by whitespace (e.g., in "a b", e from a will jump to b, and in "a" it can return text.length, which then breaks normal-mode assumptions like l clamping). Consider rewriting wordEnd to first find the end of the current word when on a word char, and ensure it never returns an offset past the last character when used for normal-mode motions; add a regression test for the 1-character word case.

Suggested change
letpos=offset
if(pos<text.length)pos++
while(pos<text.length&&!match(text[pos]))pos++
while(pos<text.length&&match(text[pos]))pos++
if(pos>offset+1)pos--
// If the buffer is empty, stay at 0.
if(text.length===0)return0
// Clamp starting position to the last valid character index.
letpos=offset
if(pos>=text.length)pos=text.length-1
// If we're on a word character, move to the end of the *current* word.
if(match(text[pos])){
while(pos+1<text.length&&match(text[pos+1]))pos++
returnpos
}
// Otherwise, skip forward to the start of the next word (if any)...
while(pos<text.length&&!match(text[pos]))pos++
if(pos>=text.length)returntext.length-1
// ...then move to the end of that word.
while(pos+1<text.length&&match(text[pos+1]))pos++

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines +6 to +10
export function createVimState(input: {
enabled: Accessor<boolean>
active: Accessor<boolean>
initial?: Accessor<VimMode | undefined>
}) {

CopilotAIFeb 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createVimState requires an active accessor in its input but never reads it. Either remove active from the API (to avoid misleading callers) or use it to reset mode/pending when the prompt becomes inactive (e.g., hidden/disabled), since callers are already computing that signal.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@leohenonleohenon changed the title feat(tui): vim motions in prompt input boxfeat(tui): vim motions in prompt inputFeb 8, 2026
@leohenon
leohenonforce-pushed the feat/vim-prompt-input-core branch from 2c180c9 to 87b4487CompareFebruary 8, 2026 13:28
@broskees

Copy link
Copy Markdown

This would be so good.

@kaich

Copy link
Copy Markdown

Good, This is exactly what I wanted.

@leohenon
leohenonforce-pushed the feat/vim-prompt-input-core branch 3 times, most recently from 108b765 to 054eabcCompareFebruary 13, 2026 00:48
@s-yakubovskiy

Copy link
Copy Markdown

Waiting for this as well <3

@Alimedhat000

Copy link
Copy Markdown

Exactly what i wanted

@leohenon
leohenonforce-pushed the feat/vim-prompt-input-core branch from 25a54ed to 97db8f9CompareFebruary 26, 2026 22:41
@broskees

Copy link
Copy Markdown

This is big!

@hamidzr

hamidzr commented Mar 1, 2026

Copy link
Copy Markdown

thanks for the work, this is great and I need it! Not knowing how long it'll take the team to get to merging this I put up a branch on my fork that will track the latest stable release + this + some additions I had around vim motion + a simple install script. I'll be using it and I thought I'd share in case you also can't wait to use it!

@ppiwko

Copy link
Copy Markdown

@leohenon, could you please rebase this on the latest OpenCode version? Alternatively, would you consider releasing your own Vim-based version of OpenCode? I’m currently using your fork as my primary coding agent, and it’s been great! 👍

@leohenon

leohenon commented Mar 8, 2026

Copy link
Copy Markdown
Author

@leohenon, could you please rebase this on the latest OpenCode version? Alternatively, would you consider releasing your own Vim-based version of OpenCode? I’m currently using your fork as my primary coding agent, and it’s been great! 👍

Released a vim build that tracks upstream - ocv.

@claytonweaver

Copy link
Copy Markdown

Get this merged!!!

@leohenon

Copy link
Copy Markdown
Author

Update: I ended up building the plugin opencode-vim-plugin:

 opencode plugin @leohenon/opencode-vim-plugin --global

It covers everything in this PR and more.

With the plugin available I'm closing this, it covers everything here without core changes. opencode-vim remains for what plugins can't reach yet.

@leohenon
leohenon deleted the feat/vim-prompt-input-core branch July 22, 2026 09:50
@augustocdias

Copy link
Copy Markdown

@leohenon what your version has that the plugin doesn't? I'm considering if I should use the plugin or your fork

@leohenon

leohenon commented Jul 23, 2026

Copy link
Copy Markdown
Author

@augustocdias The fork adds copy mode, including subagent and tool navigation. The plugin has essentially everything else for prompt editing, apart from a few configuration options that may be added later. If you only need vim prompt editing, then the plugin is sufficient.

Discussion #183

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: vim motions in input box [FEATURE]: VIM Keyboard Layout

20 participants

@leohenon@broskees@kaich@s-yakubovskiy@Alimedhat000@hamidzr@ppiwko@mahersaba@kpetroff@Hekmoo@gotgenes@aizigao@ccolorado@VimelWei@rsdrahat@kriscoleman@m2cci-bouzentm@jonnjonnjo@fsilly@claytonweaver