Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
```

Expand All @@ -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 }]]
}
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/vim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

Expand Down
20 changes: 20 additions & 0 deletions test/vim.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading