Skip to content

[FEATURE]: vim motions in input box #1764

Closed as not planned
Closed as not planned

Description

@dudeinthemirror

Please add the option of using vim keyboard shortcuts when writing the prompt (ClaudeCode has it, see attached)

Image Image

Activity

  1. scaryrawr commented on Aug 10, 2025

    @scaryrawr
    Contributor

    Ah, if you're OK with editing messages in vim.

    If you configure the $EDITOR environment variable to be vim in your .profile, .bashrc, .zshrc, or equivalent thing for your shell.

    You can launch into your $EDITOR using leader, e (leader by default is ctrl+x. Then you can edit the message in vim or whatever editor you prefer. It's different from having vim mode in the default messaging, but it's nice as you also get your .vimrc customizations.

    I didn't realize the editor was for the messaging when I first started using opencode 🤣, and you can pop into it in the middle of editing a message and it'll handle the contents into vim and out when you write and quit (it does not auto send when you exit the editor).

  2. self-assigned this
    on Aug 11, 2025
  3. dudeinthemirror commented on Aug 12, 2025

    @dudeinthemirror
    Author

    thanks for your reply @scaryrawr but that's a little different. ctrl-x e opens the editor (e.g. vim) in a separate window. I meant supporting vim shortcuts inside the prompt input field. BTW, zed also has this feature for example (ofc that is built into the IDE).

  4. thdxr commented on Aug 12, 2025

    @thdxr
    Member

    there is a PR for this but not sure if we're going to add this feature at the moment - vim modes tend to be kind of shitty feeling because they end up as partial implementations

  5. dudeinthemirror commented on Aug 13, 2025

    @dudeinthemirror
    Author

    thanks Dax, that makes sense. Please close the issue, or let me know if you want me to do it.
    Thank you.

  6. AhmedNSidd commented on Aug 13, 2025

    @AhmedNSidd

    I'll just add my 2 cents, as someone who's looking for a vim implementation, that even basic vim modes are helpful / a good start. I'm pairing up opencode with the opencode.nvim plugin and having some sort of vim mode with being able to go into insert mode, out of insert mode, with some of the basic vim navigation keys would help in fitting this more into a vim code editor.

    That said, it's just my 2 cents, not a dealbreaker by any means.

  7. zsmatrix62 commented on Aug 15, 2025

    @zsmatrix62

    +1

  8. ferdinandyb commented on Oct 11, 2025

    @ferdinandyb
    Contributor

    An ambitious thing would be adding a neovim client to opencode, something like what firenvim does. I imagine you could hijack shift+enter to save and quit the buffer to send the prompt. At least there would be no partial vim mode.

  9. added
    discussionUsed for feature requests, proposals, ideas, etc. Open discussion
    on Dec 31, 2025
  10. changed the title [-]Feature request: edit mode vim[/-] [+][FEATURE]: vim motions in input box[/+] on Dec 31, 2025
  11. sepsol commented on Dec 31, 2025

    @sepsol

    I'd argue that this is more useful than using external full-fledged editors because some of the built-in autocomplete features such as referencing files by using @ doesn't work quite as well as in the OpenCode's prompt box when you use an external editor. Therefore, it'd be much better if the prompt box itself had a built-in Vim mode, just like Claude Code's TUI.

  12. ryardley commented on Jan 14, 2026

    @ryardley

    Would really love this as without it - it makes using opencode quite awkward for me. I find that using an external editor means I loose context when writing prompts.

  13. MartinDelille commented on Jan 14, 2026

    @MartinDelille

    What would be great is that when we open the external editor, the context is opened (like when exporting the current session). It would required to strip out the context before submitting the prompt but would allow all the benefit of neovim (like yanking context content)

  14. 13 remaining items

  15. ferdinandyb commented on Mar 6, 2026

    @ferdinandyb
    Contributor

    Claude Code's UI is terrible in many ways so I would not drag that here

  16. cherusk commented on Mar 6, 2026

    @cherusk

    @ferdinandyb many on the thread have done so though.

  17. mfn commented on Mar 7, 2026

    @mfn

    I let opencode go wild on opencode to make a PoC vim-mode:

    Screen.Recording.2026-03-07.at.10.58.15.mov

    It has only half the features one would need but has more bugs than a sieve has leaks: but it "sort of works".

    What I find most interesting is that I pressed the agent hard to make the changes as unobstrusive as possible:

    Only these changes in the actual code are necessary
    diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
    index 97c910a47..3c3a23719 100644
    --- a/packages/opencode/src/cli/cmd/tui/app.tsx
    +++ b/packages/opencode/src/cli/cmd/tui/app.tsx
    @@ -21,6 +21,7 @@ import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command
     import { DialogAgent } from "@tui/component/dialog-agent"
     import { DialogSessionList } from "@tui/component/dialog-session-list"
     import { KeybindProvider } from "@tui/context/keybind"
    +import { VimProvider } from "@tui/context/vim"
     import { ThemeProvider, useTheme } from "@tui/context/theme"
     import { Home } from "@tui/routes/home"
     import { Session } from "@tui/routes/session"
    @@ -153,6 +154,7 @@ export function tui(input: {
                                 <ThemeProvider mode={mode}>
                                   <LocalProvider>
                                     <KeybindProvider>
    +                                  <VimProvider>
                                         <PromptStashProvider>
                                           <DialogProvider>
                                             <CommandProvider>
    @@ -166,6 +168,7 @@ export function tui(input: {
                                             </CommandProvider>
                                           </DialogProvider>
                                         </PromptStashProvider>
    +                                  </VimProvider>
                                     </KeybindProvider>
                                   </LocalProvider>
                                 </ThemeProvider>
    diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
    index d63c248fb..9f67bade0 100644
    --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
    +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
    @@ -34,6 +34,7 @@ import { useToast } from "../../ui/toast"
     import { useKV } from "../../context/kv"
     import { useTextareaKeybindings } from "../textarea-keybindings"
     import { DialogSkill } from "../dialog-skill"
    +import { useVim } from "@tui/context/vim"
     
     export type PromptProps = {
       sessionID?: string
    @@ -77,6 +78,7 @@ export function Prompt(props: PromptProps) {
       const renderer = useRenderer()
       const { theme, syntax } = useTheme()
       const kv = useKV()
    +  const vim = useVim()
     
       function promptModelWarning() {
         toast.show({
    @@ -818,6 +820,22 @@ export function Prompt(props: PromptProps) {
                 backgroundColor={theme.backgroundElement}
                 flexGrow={1}
               >
    +            <Show when={vim.enabled}>
    +              <text
    +                position="absolute"
    +                bottom={0}
    +                right={1}
    +                fg={vim.mode === "insert" ? theme.textMuted : theme.primary}
    +              >
    +                {vim.mode === "visual"
    +                  ? "VISUAL"
    +                  : vim.mode === "visual-line"
    +                    ? "V-LINE"
    +                    : vim.mode === "normal"
    +                      ? "NORMAL"
    +                      : "INSERT"}
    +              </text>
    +            </Show>
                 <textarea
                   placeholder={placeholderText()}
                   textColor={keybind.leader ? theme.textMuted : theme.text}
    @@ -836,6 +854,7 @@ export function Prompt(props: PromptProps) {
                       e.preventDefault()
                       return
                     }
    +                if (vim.handle(e, input)) return
                     // Handle clipboard paste (Ctrl+V) - check for images first on Windows
                     // This is needed because Windows terminal doesn't properly send image data
                     // through bracketed paste, so we need to intercept the keypress and
    @@ -981,6 +1000,7 @@ export function Prompt(props: PromptProps) {
                   }}
                   ref={(r: TextareaRenderable) => {
                     input = r
    +                vim.cursor(r)
                     if (promptPartTypeId === 0) {
                       promptPartTypeId = input.extmarks.registerType("prompt-part")
                     }
    @@ -1000,6 +1020,7 @@ export function Prompt(props: PromptProps) {
                   <text fg={highlight()}>
                     {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
                   </text>
    +
                   <Show when={store.mode === "normal"}>
                     <box flexDirection="row" gap={1}>
                       <text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
    diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
    index 2320c08cc..90ab6e767 100644
    --- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx
    +++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
    @@ -683,6 +683,20 @@ function getSyntaxRules(theme: Theme) {
             bold: true,
           },
         },
    +    {
    +      scope: ["search.match"],
    +      style: {
    +        foreground: theme.background,
    +        background: theme.warning,
    +      },
    +    },
    +    {
    +      scope: ["search.current"],
    +      style: {
    +        foreground: theme.background,
    +        background: theme.accent,
    +      },
    +    },
         {
           scope: ["comment"],
           style: {
    diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
    index a50cd96fc..c82aaa322 100644
    --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
    +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
    @@ -16,6 +16,7 @@ import { Locale } from "@/util/locale"
     import { Global } from "@/global"
     import { useDialog } from "../../ui/dialog"
     import { useTuiConfig } from "../../context/tui-config"
    +import { useVim } from "../../context/vim"
     
     type PermissionStage = "permission" | "always" | "reject"
     
    @@ -473,9 +474,11 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: (
       const dimensions = useTerminalDimensions()
       const narrow = createMemo(() => dimensions().width < 80)
       const dialog = useDialog()
    +  const vim = useVim()
     
       useKeyboard((evt) => {
         if (dialog.stack.length > 0) return
    +    if (vim.handle(evt, input)) return
     
         if (evt.name === "escape" || keybind.match("app_exit", evt)) {
           evt.preventDefault()
    @@ -517,7 +520,10 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: (
             gap={1}
           >
             <textarea
    -          ref={(val: TextareaRenderable) => (input = val)}
    +          ref={(val: TextareaRenderable) => {
    +            input = val
    +            vim.cursor(val)
    +          }}
               focused
               textColor={theme.text}
               focusedTextColor={theme.text}
    diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx
    index 1565a3008..8fcd87fdf 100644
    --- a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx
    +++ b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx
    @@ -9,6 +9,7 @@ import { useSDK } from "../../context/sdk"
     import { SplitBorder } from "../../component/border"
     import { useTextareaKeybindings } from "../../component/textarea-keybindings"
     import { useDialog } from "../../ui/dialog"
    +import { useVim } from "../../context/vim"
     
     export function QuestionPrompt(props: { request: QuestionRequest }) {
       const sdk = useSDK()
    @@ -121,6 +122,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
       }
     
       const dialog = useDialog()
    +  const vim = useVim()
     
       useKeyboard((evt) => {
         // Skip processing if a dialog (e.g., command palette) is open
    @@ -128,6 +130,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
     
         // When editing custom answer textarea
         if (store.editing && !confirm()) {
    +      if (vim.handle(evt, textarea!)) return
           if (evt.name === "escape") {
             evt.preventDefault()
             setStore("editing", false)
    diff --git a/packages/opencode/src/config/migrate-tui-config.ts b/packages/opencode/src/config/migrate-tui-config.ts
    index dbe33ffb4..fbe1a1999 100644
    --- a/packages/opencode/src/config/migrate-tui-config.ts
    +++ b/packages/opencode/src/config/migrate-tui-config.ts
    @@ -22,6 +22,7 @@ const TuiLegacy = z
         scroll_speed: TuiOptions.shape.scroll_speed.catch(undefined),
         scroll_acceleration: TuiOptions.shape.scroll_acceleration.catch(undefined),
         diff_style: TuiOptions.shape.diff_style.catch(undefined),
    +    editor_mode: TuiOptions.shape.editor_mode.catch(undefined),
       })
       .strip()
     
    @@ -92,7 +93,8 @@ function normalizeTui(data: Record<string, unknown>) {
       if (
         parsed.scroll_speed === undefined &&
         parsed.diff_style === undefined &&
    -    parsed.scroll_acceleration === undefined
    +    parsed.scroll_acceleration === undefined &&
    +    parsed.editor_mode === undefined
       ) {
         return
       }
    diff --git a/packages/opencode/src/config/tui-schema.ts b/packages/opencode/src/config/tui-schema.ts
    index f9068e3f0..3f0c918d0 100644
    --- a/packages/opencode/src/config/tui-schema.ts
    +++ b/packages/opencode/src/config/tui-schema.ts
    @@ -22,6 +22,10 @@ export const TuiOptions = z.object({
         .enum(["auto", "stacked"])
         .optional()
         .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"),
    +  editor_mode: z
    +    .enum(["default", "vim"])
    +    .optional()
    +    .describe("Editor mode for text inputs: 'vim' enables vim keybindings"),
     })
     
     export const TuiInfo = z

    Besides that, these are the only other files added:

    packages/opencode/src/cli/cmd/tui/component/prompt/vim.ts
    packages/opencode/src/cli/cmd/tui/context/vim.tsx
    

    They're not re-using any library or so, they just implement the feature as they're known.


    Of course this is a classic vibe code: I've no proper experience with TS, bun, anything used in opencode.

    I'm not planning to make a PR, I don't think this is how collaborating here would work and likely no one would want to merge this. Anyone with more skills in these areas can probably make much better vibe coded results anyway.

    TL;DR: "sort of" working was easy, the last 80% are likely the hardest ;) and I don't know what I should do with the generated code. I would wish it was possible to just implement it as a plugin/extension with minimal intrusion.

  18. cherusk commented on Mar 7, 2026

    @cherusk

    @mfn that is the spirit!

  19. leohenon commented on Mar 8, 2026

    @leohenon

    ocv - opencode release with vim insert/normal mode that tracks upstream automatically.

  20. cherusk commented on Mar 9, 2026

    @cherusk

    @leohenon - supersweet - are you mainlining it? Or why not?

  21. leohenon commented on Mar 11, 2026

    @leohenon
  22. removed
    discussionUsed for feature requests, proposals, ideas, etc. Open discussion
    on May 3, 2026
  23. gpu004 commented on May 26, 2026

    @gpu004

    i recently tried cursor agent, it has couple of vim commands and option, it's very limited, but felt very referesing using vim edits the prompt input for the very first time, hope this get merged in opencode soon

  24. epicwhale commented on Jun 10, 2026

    @epicwhale

    vim motions are the only prompt box editing motions you don't need to re-learn, i'd really love this feature too! (as shitty and incomplete they can feel at times, not having any shortcuts that are intuitive today inside the prompt box, feels worse)

  25. claytonweaver commented on Jul 4, 2026

    @claytonweaver

    can we merge this pretty please ?

  26. AntoineGS commented on Jul 15, 2026

    @AntoineGS

    ocv - opencode release with vim insert/normal mode that tracks upstream automatically.

    Highly recommend anyone landing here to use this!

    @leohenon I take it the changes are too big for this to be a plugin?

  27. github-actions commented on Sep 14, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions