diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ae4244..bed1dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,11 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version ## [Unreleased] -## [0.15.2] — 2026-07-01 +## [0.15.3] — 2026-07-01 + +### Fixed + +- Repeated `e` in visual mode now extends the selection progressively instead of getting stuck after the first press. ### Fixed @@ -301,7 +305,8 @@ First release. Modal editing for the OpenCode prompt. > `g` fires immediately as buffer-home instead of waiting for `gg`. The `yy` line tracker drifts on clicks and arrow keys. Visual mode and text objects aren't feasible without cursor position access. -[Unreleased]: https://github.com/oribarilan/vimcode/compare/v0.15.2...HEAD +[Unreleased]: https://github.com/oribarilan/vimcode/compare/v0.15.3...HEAD +[0.15.3]: https://github.com/oribarilan/vimcode/compare/v0.15.2...v0.15.3 [0.15.2]: https://github.com/oribarilan/vimcode/compare/v0.15.1...v0.15.2 [0.15.1]: https://github.com/oribarilan/vimcode/compare/v0.15.0...v0.15.1 [0.15.0]: https://github.com/oribarilan/vimcode/compare/v0.14.0...v0.15.0 diff --git a/README.md b/README.md index 9ca7c68..99c76b7 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Add to your `tui.json` (or `.opencode/tui.json`): ```json { - "plugin": ["vimcode@git+https://github.com/oribarilan/vimcode.git#v0.15.2"] + "plugin": ["vimcode@git+https://github.com/oribarilan/vimcode.git#v0.15.3"] } ``` @@ -40,7 +40,7 @@ To pass options, use the tuple form in `tui.json`: ```json { - "plugin": [["vimcode@git+https://github.com/oribarilan/vimcode.git#v0.15.2", { "updateCheck": false }]] + "plugin": [["vimcode@git+https://github.com/oribarilan/vimcode.git#v0.15.3", { "updateCheck": false }]] } ``` diff --git a/package.json b/package.json index e3bb28c..72a2bac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vimcode", - "version": "0.15.2", + "version": "0.15.3", "description": "Vim keybindings for the OpenCode prompt", "author": "Ori Bar-ilan", "license": "MIT", diff --git a/src/index.ts b/src/index.ts index 9e69b26..7fe99d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -216,7 +216,9 @@ const plugin: TuiPluginModule = { } case "selectRange": { const editor = api.renderer?.currentFocusedEditor; - editor?.setSelectionInclusive?.(action.start, action.end); + if (editor) { + editor.setSelectionInclusive?.(action.start, action.end); + } break; } } diff --git a/src/version.ts b/src/version.ts index 8c5ddb2..2057bc1 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,5 +1,5 @@ // Keep in sync with package.json on each release. -export const VERSION = "0.15.2"; +export const VERSION = "0.15.3"; // GitHub API returns fresh content immediately; raw.githubusercontent.com // is CDN-cached for up to 5 minutes which delays update detection. diff --git a/src/vim.ts b/src/vim.ts index aeced32..f3e01d6 100644 --- a/src/vim.ts +++ b/src/vim.ts @@ -568,6 +568,7 @@ export function handleVisualKey(state: VimState, key: string, ev: KeyEvent, prom const n = consumeCount(state); const target = endOfWord(prompt.getPlainText(), prompt.getCursorOffset(), n); actions.push({ type: "selectRange", start: state.visualAnchor ?? 0, end: target }); + actions.push({ type: "cursorTo", offset: target }); return { consume: true, actions }; } diff --git a/test/vim.test.ts b/test/vim.test.ts index 143bb94..70dc454 100644 --- a/test/vim.test.ts +++ b/test/vim.test.ts @@ -955,6 +955,26 @@ describe("handleVisualKey — motions", () => { expect(selectRanges(r.actions)).toEqual([{ start: 0, end: 10 }]); }); + it("e pressed twice extends selection to successive word ends", () => { + // "hello world" cursor at 0, anchor at 0 + // First e → end of "hello" (offset 4), must also emit cursorTo + // Second e (cursor now at 4) → end of "world" (offset 10) + state.visualAnchor = 0; + let cursorPos = 0; + const prompt: PromptAccess = { ...mockPrompt, getCursorOffset: () => cursorPos }; + + const r1 = handleVisualKey(state, "e", ev("e"), prompt); + expect(selectRanges(r1.actions)).toEqual([{ start: 0, end: 4 }]); + expect(cursorTos(r1.actions)).toEqual([4]); + + // Simulate effect layer applying the cursorTo action + cursorPos = cursorTos(r1.actions)[0]; + + const r2 = handleVisualKey(state, "e", ev("e"), prompt); + expect(selectRanges(r2.actions)).toEqual([{ start: 0, end: 10 }]); + expect(cursorTos(r2.actions)).toEqual([10]); + }); + it("g sets pendingChar, no actions", () => { const r = handleVisualKey(state, "g", ev("g")); expect(r.consume).toBe(true);