{"id":51248945,"url":"https://github.com/oribarilan/vimcode","last_synced_at":"2026-06-29T06:03:19.877Z","repository":{"id":358956531,"uuid":"1241718639","full_name":"oribarilan/vimcode","owner":"oribarilan","description":"Vim for OpenCode.","archived":false,"fork":false,"pushed_at":"2026-06-16T08:48:59.000Z","size":237,"stargazers_count":89,"open_issues_count":7,"forks_count":4,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T10:27:20.255Z","etag":null,"topics":["harness","opencode","opencode-plugin","vim"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oribarilan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-17T18:23:17.000Z","updated_at":"2026-06-16T08:48:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/oribarilan/vimcode","commit_stats":null,"previous_names":["oribarilan/vimcode"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/oribarilan/vimcode","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oribarilan%2Fvimcode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oribarilan%2Fvimcode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oribarilan%2Fvimcode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oribarilan%2Fvimcode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oribarilan","download_url":"https://codeload.github.com/oribarilan/vimcode/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oribarilan%2Fvimcode/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34915002,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["harness","opencode","opencode-plugin","vim"],"created_at":"2026-06-29T06:03:19.174Z","updated_at":"2026-06-29T06:03:19.868Z","avatar_url":"https://github.com/oribarilan.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003evimcode\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003eVim keybindings for the \u003ca href=\"https://opencode.ai\"\u003eOpenCode\u003c/a\u003e prompt.\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cem\u003eExperimental. Things will break.\u003c/em\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cvideo src=\"https://github.com/user-attachments/assets/d0afa749-96c2-4f1c-adcb-5ab388d5798e\" width=\"800\" controls\u003e\u003c/video\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#install\"\u003eInstall\u003c/a\u003e ·\n  \u003ca href=\"#configuration\"\u003eConfiguration\u003c/a\u003e ·\n  \u003ca href=\"#what-it-does\"\u003eWhat it does\u003c/a\u003e ·\n  \u003ca href=\"#keybindings\"\u003eKeybindings\u003c/a\u003e ·\n  \u003ca href=\"#known-gaps\"\u003eKnown gaps\u003c/a\u003e ·\n  \u003ca href=\"#how-it-works\"\u003eHow it works\u003c/a\u003e ·\n  \u003ca href=\"#contributing\"\u003eContributing\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Install\n\nAdd to your `tui.json` (or `.opencode/tui.json`):\n\n```json\n{\n  \"plugin\": [\"vimcode@git+https://github.com/oribarilan/vimcode.git#v0.15.1\"]\n}\n```\n\n\u003e **Why a versioned ref?** OpenCode resolves `@latest` once and caches it forever. Bumping the version in your config is the only reliable way to get updates.\n\nYou'll see a toast when a newer version is available (can be turned off).\n\n## Configuration\n\nTo pass options, use the tuple form in `tui.json`:\n\n```json\n{\n  \"plugin\": [[\"vimcode@git+https://github.com/oribarilan/vimcode.git#v0.15.1\", { \"updateCheck\": false }]]\n}\n```\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `updateCheck` | `boolean` | `true` | On startup, check GitHub for new versions (at most once per day). This is the only network request vimcode makes. Set to `false` to disable. |\n| `modeIndicator` | `\"toast\"` \\| `\"none\"` | `\"toast\"` | How to show the current mode. `\"toast\"` flashes a brief notification on each switch. `\"none\"` disables it, relying on cursor shape alone. |\n| `startMode` | `\"insert\"` \\| `\"normal\"` | `\"insert\"` | Which mode to start in when OpenCode launches. |\n\n## What it does\n\nAdds normal/insert mode to OpenCode's prompt input. Escape enters normal mode, `i` goes back to insert. A brief toast shows the current mode on each switch (configurable).\n\nIn insert mode, typing works normally. Enter adds a newline, Ctrl+Enter submits. The file picker and autocomplete keep working: Enter picks the selected item, Escape closes the picker without leaving insert.\n\nIn normal mode, keys are vim commands. Unrecognized keys get swallowed so you don't accidentally type into the prompt. `:` opens the command palette.\n\n### Overlay passthrough\n\nWhen OpenCode shows its own UI (command palette, `/sessions`, the `@` file picker, question prompts, permission prompts) vimcode steps aside. All keys pass through to the overlay until it closes.\n\n### Escape behavior\n\nFirst Escape in insert mode switches to normal - it won't trigger OpenCode's double-escape interrupt. So canceling a running response from insert mode takes 3 escapes: one for normal, two more for the interrupt.\n\n### Leader key\n\nvimcode reads OpenCode's leader key from your `tui.json` keybinds and handles it automatically, no plugin-side config needed.\n\nIn **normal and visual mode**, the leader key and the follow-up key pass straight through to OpenCode, so leader shortcuts (`\u003cleader\u003ec` for copy, etc.) work as expected.\n\nIn **insert mode**, printable leaders (like space) insert their character. Non-printable leaders (like `ctrl+x`) pass through to OpenCode, so leader shortcuts work from any mode.\n\nThis allows, for example, to use the popular vim-style `space` leader, set it in your `tui.json`:\n\n```json\n{\n  \"keybinds\": {\n    \"leader\": \"space\"\n  }\n}\n```\n\nNote that OpenCode defaults the leader to `ctrl+x`.\n\n### Platform notes\n\nClipboard (`y`, `yy`, `p`) uses the system clipboard: `pbcopy` on macOS, `clip.exe` on Windows, `xclip` on Linux. Linux users need `xclip` installed (`apt install xclip` or equivalent). If the clipboard tool is missing, yank/paste still works within the session via an internal register.\n\nCursor shape (block in normal, bar in insert) works across all terminals. No special terminal support required.\n\nThe plugin checks GitHub for new versions once per day on startup. No other network requests, no telemetry.\n\n## Keybindings\n\n### Motions\n\n| Key | Action |\n|-----|--------|\n| `h` `j` `k` `l` | Left, down, up, right |\n| `w` `b` `e` | Word forward, backward, end of word |\n| `0` `^` | Line start |\n| `$` | Line end |\n| `gg` | Buffer start |\n| `G` | Buffer end |\n\nAll motions take counts: `3j` moves down 3 lines.\n\nWhen the input is empty, `j`/`k` scroll through prompt history instead of moving the cursor.\n\n### Operators\n\n`d` (delete), `c` (change), and `y` (yank) combine with motions:\n\n| Combo | Action |\n|-------|--------|\n| `dd` `cc` `yy` | Operate on whole line |\n| `D` `C` | Delete/change to end of line |\n| `dw` `cw` `yw` | To next word |\n| `db` `cb` `yb` | To previous word |\n| `de` `ce` `ye` | To end of word |\n| `d$` `c$` `y$` | To end of line |\n| `d0` `c0` `y0` | To start of line |\n| `d^` `c^` `y^` | To start of line |\n| `dh` `ch` `yh` | Character left |\n| `dl` `cl` `yl` | Character right |\n| `dj` `cj` `yj` | Current + line below |\n| `dk` `ck` `yk` | Current + line above |\n| `dG` `cG` `yG` | To end of buffer |\n\nCounts work on both operator and motion: `2dd` deletes 2 lines, `d3w` deletes 3 words.\n\n### Insert entries\n\n| Key | Action |\n|-----|--------|\n| `i` | Insert at cursor |\n| `a` | Insert after cursor |\n| `A` | Insert at end of line |\n| `o` | Open line below |\n| `O` | Open line above |\n\n`Ctrl+O` runs one normal-mode command and returns to insert. Motions, operators, counts, and `r{char}` all work.\n\n### Visual mode\n\nPress `v` in normal mode to enter character-wise visual mode. Press `V` to select the current line. Motions extend the selection, operators act on it:\n\n| Key | Action |\n|-----|--------|\n| `d` `x` | Delete selection |\n| `c` | Delete selection, enter insert mode |\n| `y` | Yank (copy) selection |\n| `V` | Select current line |\n| `Escape` `v` | Exit visual mode |\n\nAll normal-mode motions work for extending the selection: `h` `j` `k` `l` `w` `b` `e` `0` `$` `G`, with counts.\n\n### Other\n\n| Key | Action |\n|-----|--------|\n| `Ctrl+O` | One-shot normal mode (execute one command, return to insert) |\n| `r{char}` | Replace character under cursor with `{char}` |\n| `x` | Delete character |\n| `u` | Undo |\n| `Ctrl+r` | Redo |\n| `p` | Paste from yank register |\n| `:` | Command palette |\n| `:q` `:quit` `:wq` | Quit OpenCode (via command palette) |\n| `:vim` | Toggle vim mode on/off (persisted across restarts) |\n| `/` | Jump to message (session timeline) |\n| `[` `]` | Scroll conversation half-page up/down |\n| `{` `}` | Jump to previous/next message |\n| `X` | Backspace |\n| `J` | Join current line with next |\n| `j` `k` | Cycle prompt history (when input is empty) |\n| `Enter` | Submit prompt |\n| `Escape` | Pass through for double-escape interrupt |\n\n## Known gaps\n\n- `Ctrl+v` - block visual mode is not supported\n- `ciw`, `di\"`, etc. (text objects) - not yet implemented\n- No persistent mode indicator - the toast fades after about a second. A slot-based indicator needs the host's JSX runtime, which doesn't resolve reliably from git-installed plugins ([#3](https://github.com/oribarilan/vimcode/issues/3)).\n\nConfigurable key bindings are next once the core vim coverage stabilizes.\n\n## How it works\n\nvimcode registers a key intercept on every prompt keypress. A pure handler in `src/vim.ts` takes the current mode and key, returns a list of actions (move cursor, delete word, switch mode, etc.) without touching the plugin API. `src/index.ts` applies those actions through `@opentui/keymap` commands.\n\n## Contributing\n\n1. Try it\n2. If it's useful, a star helps others find it\n3. Open issues for bugs or missing keybindings\n4. PRs welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for dev setup and the release process.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foribarilan%2Fvimcode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foribarilan%2Fvimcode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foribarilan%2Fvimcode/lists"}