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
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Steps:

## Gotchas

- Leaving visual mode keeps the last visual selection until the user moves the cursor, starts another selection, lets an operator consume it, edits its text, or goes to another file. A visit to the Claude terminal keeps it. `getCurrentSelection` still returns it with `isEmpty: false`. Move the cursor before you test the empty state.
- Leaving visual mode keeps the last visual selection until the user moves the cursor, starts another selection, lets an operator consume it, edits its text, or goes to another file. A visit to the Claude terminal keeps it, and `getCurrentSelection` still returns it with `isEmpty: false`, unless the selected text changes or moves meanwhile (Neovim reloads the file, or a plugin edits it): then the cursor of that file goes out at once. Move the cursor before you test the empty state.
- While tracking is on, `getCurrentSelection` and `getLatestSelection` both return the selection Neovim last pushed. `getCurrentSelection` only adds `"success":true`. Neither reads the editor again, so during the debounce both return the older selection. Three events do not wait for the debounce, and each pushes at once: leaving visual mode, editing the held text or consuming it with an operator, and moving the cursor away from a held selection. Entering another file (`:b`, a window switch) waits for the debounce: measured about 100 ms.
- Most notifications are debounced; the three events above are not. Wait for the mode or cursor state with `$V until`, then allow about one second before you read `ws-in.jsonl`.
- Lines and characters are 0-based in every payload. `:3` in Neovim is `line: 2`.
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/verify-diff-lifecycle/covered.lock
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ file lua/claudecode/lockfile.lua 2746b4c6e0b2b630
== lua/claudecode/logger.lua
file lua/claudecode/logger.lua 17b404dda41c3591
== lua/claudecode/selection.lua
file lua/claudecode/selection.lua 0fa651cc95e0af0d
file lua/claudecode/selection.lua 05663913bc425259
== lua/claudecode/server/client.lua
file lua/claudecode/server/client.lua 5789c6ff18045f9f
block create_client 647b7523dea26af6
Expand Down
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ vim.keymap.set("n", "<leader>dq", exit_diff_mode)
Pushes a `selection_changed` notification when the selection Claude should see differs from the last one sent. Most updates are debounced, and three events are not:

- **Debounced, 100 ms.** `CursorMoved`, `CursorMovedI`, `BufEnter`, `ModeChanged`, `TextChanged`, `TextChangedI` and `TextChangedP` each restart a one-shot timer. When it fires, `update_selection` reads the mode and sends the selection, or the cursor in normal mode.
- **At once.** Leaving visual mode sends the selection from the `'<` and `'>` marks (the flush). Editing the held text, or consuming it with an operator, sends the cursor. Moving the cursor away from a held selection sends the cursor.
- **At once.** Leaving visual mode sends the selection from the `'<` and `'>` marks (the flush). Editing the held text, or consuming it with an operator, sends the cursor. Moving the cursor away from a held selection sends the cursor. When the held text changes while you are in the Claude terminal (Neovim reloads the file, or a plugin edits it), the cursor of its file goes out too: `TextChanged` fires only for the current buffer, so a listener from `nvim_buf_attach` on the file's buffer sees the change.
- **Entering another file** waits for the debounce.
- **Claude terminal buffers** are skipped.

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ Log levels for authentication events:

### Performance Optimizations

- Selection tracking is debounced (100 ms) to reduce overhead. Leaving visual mode, editing or consuming the held text, and moving the cursor away from a held selection report at once
- Selection tracking is debounced (100 ms) to reduce overhead. Leaving visual mode, editing or consuming the held text, and moving the cursor away from a held selection report at once. So does a change to the held text while you are in the Claude terminal (a reload, a plugin's edit): a listener from `nvim_buf_attach` on its buffer sees it, because `TextChanged` fires only for the current buffer
- WebSocket frame processing optimized for JSON-RPC payload sizes
- Connection pooling and cleanup to prevent resource leaks

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,9 @@ The plugin runs the CLI through a [terminal provider](./lua/claudecode/terminal/
an error that names both directories [lua/claudecode/diff.lua:create_temp_file]. The `openDiff` tool writes no temp
files [lua/claudecode/diff.lua:M.open_diff_blocking].
- Editor state: autocmd groups (`ClaudeCodeShutdown`, `ClaudeCodeSelection`, `ClaudeCodeMCPDiff`,
`ClaudeCodeDiffCleanup`), `User` events (`ClaudeCodeSendComplete`, `ClaudeCodeDiffOpened`, `ClaudeCodeDiffClosed`),
`ClaudeCodeDiffCleanup`), `nvim_buf_attach` change listeners on the buffer of a held selection
[lua/claudecode/selection.lua:watch_selection_buffer], `User` events (`ClaudeCodeSendComplete`, `ClaudeCodeDiffOpened`,
`ClaudeCodeDiffClosed`),
and a wrapped global `vim.paste` when `terminal.fix_streamed_paste` is `true`, or `"auto"` on Neovim < 0.12.2
[lua/claudecode/terminal/paste_fix.lua:M.apply] [lua/claudecode/terminal/paste_fix.lua:M.install].
- Release tooling: `scripts/release-please-runner.js` without `--dry-run` creates GitHub tags, releases, and PRs
Expand Down
3 changes: 2 additions & 1 deletion lua/claudecode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Module map (direct files):
- A mention sent while disconnected goes to the queue with the same absolute path as a connected send, and the terminal opens. A connected send goes to the queue too, unless broadcast debouncing is off and the queue is empty [init.lua:M._broadcast_at_mention]
- The queue sends each mention once, oldest first: 50 ms after a send, `connection_wait_delay` ms after a client completed its handshake, and 25 ms apart. A mention leaves the queue only when a connected client took it. When the write of that frame then fails on every client, the error "Claude's connection failed while the @ mention <path> was sent" appears, and the mention is not sent again. When Claude is not connected, the mentions wait up to `connection_timeout` ms for a handshake, then the queue is cleared with the error "Connection timeout - clearing N queued @ mentions". A handshake stops that timeout, and a flush that finds no connected client starts it again. `queue_timeout` is accepted but unused [init.lua:schedule_mention_flush]
- `M.stop` clears the queue and stops its timers. A queue timer's callback does nothing once its timer was stopped or replaced, so nothing queued before a stop reaches Claude [init.lua:start_timer]
- Selection tracking is a no-op unless it is enabled. Updates are debounced by 100 ms, except that leaving visual mode, editing or consuming the held text and moving the cursor away from a held selection report at once [selection.lua:M.flush_visual_selection] [selection.lua:reconcile_held_selection]. Entering another file waits for the debounce. Claude terminal buffers are skipped [selection.lua:M.update_selection]
- Selection tracking is a no-op unless it is enabled. Updates are debounced by 100 ms, except that leaving visual mode, editing or consuming the held text and moving the cursor away from a held selection report at once [selection.lua:M.flush_visual_selection] [selection.lua:reconcile_held_selection]. The held text can also change while you are in the Claude terminal (a reload, a plugin's edit), where `TextChanged` does not fire for its buffer: a listener from `nvim_buf_attach` on that buffer sends the file's cursor then [selection.lua:watch_selection_buffer]. Entering another file waits for the debounce. Claude terminal buffers are skipped [selection.lua:M.update_selection]
- The automatic paths send `selection_changed` only when the selection differs from the last one sent [selection.lua:M.update_selection] [selection.lua:M.flush_visual_selection]. `send_current_selection` sends with no change check [selection.lua:M.send_current_selection]
- Select mode (`gh`, or `v`, `V` and CTRL-V when `'selectmode'` contains `cmd`) is tracked like Visual mode [selection.lua:is_visual_mode], and a command such as a `<cmd>ClaudeCodeSend<CR>` mapping takes the visual path there [visual_commands.lua:M.create_visual_command_wrapper]. The visual path ends Select mode. For a mapping made with `mode = "v"` (`:vmap`), Neovim queues a key that restores Select mode after the mapping, and the visual path cancels it with `gV` [visual_commands.lua:M.exit_visual_and_schedule]
- A visual selection is flushed on visual exit. The flush is skipped if the buffer changed during visual mode [selection.lua:M.flush_visual_selection]
Expand Down Expand Up @@ -157,6 +157,7 @@ Module map (direct files):
- The terminal env gets `ENABLE_IDE_INTEGRATION=true`, `FORCE_CODE_TERMINAL=true`, `CLAUDE_CODE_SSE_PORT=<port>` only while the server runs, the user `env` (merged last, so it can override these), and the merged `no_proxy`/`NO_PROXY` [terminal.lua:get_claude_command_and_env]
- `terminal.setup` can replace the global `vim.paste` through `paste_fix.apply` [terminal.lua:M.setup]
- The plugin broadcasts `at_mentioned` (`filePath`, `lineStart`, `lineEnd`) [init.lua:M._broadcast_at_mention] and `selection_changed` [selection.lua:M.send_selection_update] to every connected client
- While Claude holds a visual selection, `selection.lua` attaches an `nvim_buf_attach` listener (`on_lines`, `on_reload`, `on_detach`) to its buffer, once per buffer. It detaches itself at the next `on_lines` after Claude holds no selection of that buffer, and a reload with `:edit` or `:edit!` detaches it, so it attaches again [selection.lua:watch_selection_buffer]
- Augroups: `ClaudeCodeShutdown`, which holds the `VimLeavePre` autocmd that stops on exit [init.lua:M.setup], `ClaudeCodeSelection` [selection.lua:M._create_autocommands], `ClaudeCodeMCPDiff` [diff.lua:get_autocmd_group], `ClaudeCodeDiffCleanup` [diff.lua:M._open_native_diff], and a module-level `VimLeavePre` autocmd in `diff.lua` that calls `M._cleanup_all_active_diffs("shutdown")` [diff.lua:M._cleanup_all_active_diffs]
- Diffs create scratch buffers, splits, and tabs. They resize the terminal window and fire `User` events. Cleanup closes these and reloads unchanged file buffers 100 ms after a save [diff.lua:M.close_diff_by_tab_name]
- The native diff writes each temp file into its own session folder under `stdpath("cache")/claudecode_diffs`, or `claudecode_diffs_fallback` when it cannot create a directory in the first. The folder name includes the Neovim PID, so two running Neovim processes never share one. If both fail, `open_diff` returns `success = false` with an error that names both directories [diff.lua:create_temp_file]. A `BufDelete`/`BufWipeout` autocmd on the proposed buffer cleans up once, even when a wipe fires both events. It deletes the file and then its empty folder with `vim.fn.delete`, and warns once for each delete that fails. When the file delete fails, the folder is not empty, so its delete fails too and both warnings show [diff.lua:cleanup_temp_file]. `_cleanup_diff_state` does not [diff.lua:M._open_native_diff]
Expand Down
Loading
Loading