-
-
Notifications
You must be signed in to change notification settings - Fork 50
feat: add visual selection support for sending code context to Claude #91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add the ability to select code in visual mode and send it to Claude Code with a custom prompt. This feature sends only the file path and line range, allowing Claude Code to read the file directly for better context. New features: - Visual mode keymap (<leader>cs) to ask about selected code - Lua API: send(), open(), close() for programmatic control - Auto-detection of new session with appropriate delay for initialization Configuration: - keymaps.selection.ask: customize the visual mode keymap (default: <leader>cs) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
WalkthroughAdds visual-selection support that prompts Claude from visual mode, new tmux integration and terminal helper APIs, new public Lua functions to send/open/close Claude Code, config extensions (keymaps.selection and tmux settings), README updates, and a new selection-focused test suite. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant VisualKeymap as Visual Keymap
participant Prompt as Prompt
participant Init as Init Module
participant Tmux as tmux Module
participant Terminal as Terminal
User->>VisualKeymap: Press visual mapping (<leader>cs)
VisualKeymap->>VisualKeymap: Capture selection & file:range
VisualKeymap->>Prompt: Prompt "Ask Claude: "
Prompt->>VisualKeymap: Return user input
alt input provided
VisualKeymap->>Init: Check availability (M.is_available)
alt available via tmux
Init->>Tmux: find_claude_pane / focus_pane
note right of Tmux `#e8fff2`: short ready wait (~200ms)
Tmux->>Tmux: send_text_with_enter(pane, message)
else available via nvim terminal or start needed
Init->>Terminal: ensure_visible(config)
note right of Terminal `#fff4e6`: startup wait (~2000ms) if new
Terminal->>Terminal: send_text(instance, message)
Terminal->>Terminal: send Enter
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
README.md (2)
8-8: Inconsistent test count in badge.The badge still shows "44 passing" but line 39 states "60 comprehensive tests". Update the badge to reflect the actual test count.
-[](https://github.com/greggh/claude-code.nvim/actions/workflows/ci.yml) +[](https://github.com/greggh/claude-code.nvim/actions/workflows/ci.yml)
287-289: Update test count in development section.This section still mentions "44 comprehensive tests" but should be updated to 60 to match line 39.
-- Complete installation instructions for all platforms in [DEVELOPMENT.md](DEVELOPMENT.md) - Pre-commit hooks for code quality -- Testing framework with 44 comprehensive tests +- Testing framework with 60 comprehensive tests - Linting and formatting tools
🧹 Nitpick comments (5)
lua/claude-code/keymaps.lua (2)
55-66: Handle edge case for unsaved buffers.If the buffer has no file path (unsaved buffer),
vim.fn.expand('%:p')returns an empty string, resulting in a message like "See :1-5" which is not helpful to Claude.Consider checking for this case:
-- Capture file info BEFORE vim.ui.input (which exits visual mode) local filepath = vim.fn.expand('%:p') + if filepath == '' then + vim.notify('Cannot send selection from unsaved buffer', vim.log.levels.WARN) + return + end -- Use visual mode marks (current selection) local start_line = vim.fn.line('v')
79-92: Consider making initialization delay configurable.The hardcoded 2000ms delay for new sessions may not be sufficient on slower systems or too long on fast ones. Consider exposing this as a configuration option in a future iteration.
For now, the implementation is acceptable, but verify the 2000ms delay works reliably across different systems and Claude Code versions.
tests/spec/selection_spec.lua (3)
12-135: Extract common mock setup to reduce duplication.The mock setup for vim APIs is duplicated almost identically between the two test describe blocks. Consider extracting this into a shared helper function to improve maintainability and reduce test code bloat.
-- Example approach: Create a helper that returns a fresh mock environment local function setup_vim_mocks() _G.vim = _G.vim or {} _G.vim.api = _G.vim.api or {} _G.vim.fn = _G.vim.fn or {} -- ... rest of mocks return _G.vim end -- Then in before_each: before_each(function() setup_vim_mocks() -- ... test-specific setup end)Also applies to: 206-296
393-416: Expand test coverage for send/open/close to verify actual behavior.The tests for
init.send(),init.open(), andinit.close()are minimal and only verify happy-path scenarios. Consider adding tests for:
send(): empty text, text with special characters, when terminal is not runningopen(): verify window is actually made visible (not just returns true), state after calling openclose(): verify a window was actually closed, state after calling close, behavior when no window is open
160-177: Add edge case tests for send_text and send functions.The send_text tests don't cover scenarios such as:
- Sending empty strings
- Handling special characters or newlines in text
- Terminal sending text when disconnected/stale
Adding these would increase confidence in error handling and boundary conditions.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
README.md(4 hunks)lua/claude-code/config.lua(3 hunks)lua/claude-code/init.lua(1 hunks)lua/claude-code/keymaps.lua(2 hunks)lua/claude-code/terminal.lua(1 hunks)tests/spec/selection_spec.lua(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-02T19:40:53.423Z
Learnt from: CR
Repo: greggh/claude-code.nvim PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T19:40:53.423Z
Learning: Applies to lua/claude-code*/**/*.lua : Main plugin code should reside in '/lua/claude-code', with subdirectories for CLI integration, UI components, and context management
Applied to files:
lua/claude-code/terminal.lualua/claude-code/init.luaREADME.md
🧬 Code graph analysis (1)
lua/claude-code/init.lua (1)
lua/claude-code/terminal.lua (1)
M.toggle(451-477)
🔇 Additional comments (13)
README.md (4)
33-39: LGTM!The new visual selection feature and updated test count are well documented.
150-156: LGTM!The new
selection.askconfiguration option is clearly documented with the default keymap.
206-208: LGTM!Visual selection mapping documentation is clear and helpful.
223-238: LGTM!The new Lua API section provides clear examples for programmatic control.
lua/claude-code/terminal.lua (3)
383-398: LGTM!The
get_job_idfunction correctly handles nil checks for instance_id and buffer validation before accessing the terminal job ID.
400-414: LGTM!The
send_textfunction properly validates the job ID and provides user feedback when the terminal isn't running.
416-445: LGTM!The
ensure_visiblefunction correctly checks for existing windows before creating new ones, avoiding duplicate windows.lua/claude-code/config.lua (3)
42-49: LGTM!Type annotations for the new
ClaudeCodeKeymapsSelectionclass are well-defined and consistent with existing patterns.
139-142: LGTM!Default configuration for visual selection keymap follows the existing pattern and matches the documentation.
371-382: LGTM!Validation logic correctly handles the optional
keymaps.selectiontable and validatesaskas either a string orfalse.lua/claude-code/keymaps.lua (1)
127-134: LGTM!The which-key registration for the selection keymap follows the established pattern.
lua/claude-code/init.lua (2)
124-133: LGTM!The
open()function correctly handles both starting a new instance and ensuring visibility of an existing one.
135-151: LGTM!The
close()function correctly hides the terminal window without terminating the session, and handles edge cases gracefully.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
lua/claude-code/init.lua (1)
106-117: Document timing requirement or provide async API for open() → send() sequence.The existing code in keymaps.lua correctly handles the timing concern by using
vim.defer_fndelays (2 seconds for new sessions, 200ms otherwise) before callingsend()afteropen(). However, this timing requirement isn't documented in the function's comments, creating maintenance risk and poor API clarity for future callers.Either:
- Add documentation to
send()clarifying that the terminal must be fully initialized afteropen()(typically requiring 200-2000ms depending on context)- Or integrate the async pattern into the API itself (e.g., make
open()return a future/coroutine, or addsend_deferred())
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
lua/claude-code/init.lua(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-02T19:40:53.423Z
Learnt from: CR
Repo: greggh/claude-code.nvim PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T19:40:53.423Z
Learning: Applies to lua/claude-code*/**/*.lua : Main plugin code should reside in '/lua/claude-code', with subdirectories for CLI integration, UI components, and context management
Applied to files:
lua/claude-code/init.lua
🧬 Code graph analysis (1)
lua/claude-code/init.lua (1)
lua/claude-code/terminal.lua (1)
M.toggle(451-477)
🔇 Additional comments (1)
lua/claude-code/init.lua (1)
130-146: LGTM!The implementation correctly handles edge cases (missing instance, invalid buffer) and properly closes all windows displaying the Claude Code buffer. Using
force=trueis appropriate for terminal windows.
| --- Open Claude Code and focus the terminal window | ||
| --- @return boolean success True if window is now visible | ||
| function M.open() | ||
| if not M.claude_code.current_instance then | ||
| M.toggle() | ||
| return true | ||
| end | ||
|
|
||
| return terminal.ensure_visible(M, M.config) | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return value is misleading when starting a new instance.
When there's no current instance, open() returns true immediately after calling M.toggle(), but the terminal needs time to initialize. This suggests success before the terminal is actually ready for use (e.g., before send() can be called successfully).
Additionally, the function returns inconsistent types: literal true for new instances vs. the result of terminal.ensure_visible() for existing instances.
Consider either:
- Returning
falsewhen starting a new instance to indicate "not yet ready" - Documenting that
truemeans "initiated" rather than "ready" - Making the return value consistent by always returning a boolean indicating current visibility state
function M.open()
if not M.claude_code.current_instance then
- M.toggle()
- return true
+ M.toggle()
+ return false -- Terminal is starting but not yet ready for send()
end
return terminal.ensure_visible(M, M.config)
end📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| --- Open Claude Code and focus the terminal window | |
| --- @return boolean success True if window is now visible | |
| function M.open() | |
| if not M.claude_code.current_instance then | |
| M.toggle() | |
| return true | |
| end | |
| return terminal.ensure_visible(M, M.config) | |
| end | |
| --- Open Claude Code and focus the terminal window | |
| --- @return boolean success True if window is now visible | |
| function M.open() | |
| if not M.claude_code.current_instance then | |
| M.toggle() | |
| return false -- Terminal is starting but not yet ready for send() | |
| end | |
| return terminal.ensure_visible(M, M.config) | |
| end |
🤖 Prompt for AI Agents
In lua/claude-code/init.lua around lines 119 to 128, the function returns a
literal true when creating a new instance which misrepresents readiness and
produces inconsistent return types; change behavior so the function always
returns the actual visibility/readiness boolean by calling M.toggle() to start
the terminal and then returning terminal.ensure_visible(M, M.config) (i.e.,
don't return true immediately — invoke ensure_visible after toggling and return
its boolean result so callers get a consistent, accurate readiness state).
190ad3f to
e792803
Compare
- Replace fixed delay with dynamic CLI ready detection - Monitor terminal buffer for ">" prompt pattern - Poll every 50ms with 2.5s timeout fallback - Add unsaved buffer validation check - Existing sessions execute immediately without waiting 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks for the detailed review! I've addressed the feedback in commit 1de1c01: Changes Made1. Replaced fixed delay with dynamic CLI ready detectionInstead of using hardcoded delays (2000ms/200ms), the code now monitors the terminal buffer content and detects when Claude Code CLI shows the
2. Added unsaved buffer validationAdded check for empty filepath to prevent sending selections from unsaved buffers, as suggested in the review. 3. Timing/async concernsThe Let me know if there are any other concerns! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
lua/claude-code/keymaps.lua (1)
137-160: Clarify / tightenis_new_sessionvsclaude.open()contract
is_new_sessionis computed before callingclaude.open()based onclaude.claude_code.current_instanceandinstances[current_instance], and then passed intowait_for_cli_ready. This assumesclaude.open()will not spin up a fresh CLI session when those fields are non‑nil; ifopen()can restart or swap the underlying CLI while keepingcurrent_instance/instancestruthy,wait_for_cli_readywill skip polling and may send input before the new CLI is actually ready.If
open()guarantees that "truthycurrent_instance+ instances[idx] implies no new CLI process is started", consider documenting that invariant here in a short comment. If not, consider plumbing an explicitis_new_session/started_new_cliflag fromopen()(or recomputing afteropen()) sowait_for_cli_readyalways gets an accurate value.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
lua/claude-code/keymaps.lua(2 hunks)
🔇 Additional comments (2)
lua/claude-code/keymaps.lua (2)
55-164: Visual selection flow and CLI-ready polling look solidThe end-to-end flow (unsaved-buffer guard, visual range capture, prompt input, CLI-ready polling on the terminal buffer, then message + Enter send) is cohesive and aligns with the PR goals. The
wait_for_cli_readyloop is conservative (2.5s cap, buffer-validity checks, and timeout fallback), and the selection mapping correctly normalizesstart_line/end_lineand avoids sending anything on empty/cancelled input.
196-202: which-key integration for visual selection is consistentRegistering the selection keymap under
mode = 'x'with the sameconfig.keymaps.selection.askbinding and description keeps which-key metadata in sync with the actual mapping and mirrors the existing normal/terminal registrations.
- Add tmux.lua module for tmux pane detection and text sending - Use ps + awk instead of pgrep -P for macOS compatibility - Prioritize same-session panes before searching all sessions - Add tmux config options (enable, prefer_tmux) to config.lua - Update keymaps.lua to support tmux pane sending for visual selection - Add send_with_enter() and is_available() helper functions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Added: Tmux Integration SupportThis commit adds the ability to send text to Claude Code running in a separate tmux pane, which is useful when you prefer running Claude Code in a dedicated tmux window/pane rather than inside nvim. ChangesNew file:
Updated:
Updated:
Updated:
Usagerequire("claude-code").setup({
tmux = {
enable = true, -- Enable tmux integration
prefer_tmux = true, -- Prefer tmux pane over nvim terminal
},
})When you use
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
lua/claude-code/init.lua (2)
181-189:open()return value may misrepresent readiness when spawning a new instance.When there is no current instance,
M.open()callsM.toggle()and immediately returnstrue, but in that case the terminal may not yet be visible or ready for input. For existing instances you instead return the result ofterminal.ensure_visible(M, M.config), which is closer to “window is now visible”.To keep this API less surprising for callers, consider one of:
- Returning the actual visibility state in all cases (e.g., call
terminal.ensure_visibleafterM.toggle()and return its boolean), or- Updating the docstring/README to clarify that, for new instances,
truemeans “startup initiated” rather than “window is now visible”.This is the same behavioral concern raised in a previous review, now applied to the updated
open()implementation.#!/bin/bash # Inspect external usages of `open()` to see whether they rely on its return value rg -n "require\('claude-code'\).*open\(" --type lua -C2 || true
108-136:M.sendreturn contract is still misleading when starting a new nvim terminal.The docstring says
@return boolean success True if text was sent successfully, but when there is no current nvim instance you:
- Call
M.toggle()- Schedule
terminal.send_text(M, text)viavim.defer_fn- Immediately
return trueAt that point you don’t yet know whether
terminal.send_textwill succeed (job could fail to start, buffer could close, etc.), sotruereally means “send scheduled/initiated”, not “text was sent successfully”.Given this function is now part of the public API and also underpins
M.send_with_enter, it would be better to either:
- Relax the contract (update the docstring to clarify that
truemeans “send has been queued/attempted”, and callers should not rely on it as a delivery guarantee), or- Change the implementation so that it returns a meaningful success boolean in all code paths (e.g., by funneling through a queue that receives the eventual
terminal.send_textresult and only then resolves the boolean).Because this mirrors an earlier review concern around
M.send's deferred path, tagging as duplicate.#!/bin/bash # Show all call sites that rely on the boolean result of M.send rg -n "require\('claude-code'\).*send\(" --type lua -C2 || true
🧹 Nitpick comments (3)
lua/claude-code/keymaps.lua (1)
117-183: Visual selection flow looks solid; consider reusingsend_with_enterfor consistency.The overall selection path is well thought out: it captures the visual range before
vim.ui.input, guards against unsaved buffers, builds a compact “See :” message, and distinguishes tmux vs nvim with a new‑session awarewait_for_cli_ready.For the nvim‑terminal path, you could simplify and de‑duplicate send logic by using the new
claude.send_with_enter(message)instead of manually callingclaude.send(message)and thenclaude.send('\r'), letting the public API own the “append Enter” behavior:- wait_for_cli_ready(bufnr, function() - local sent = claude.send(message) - if sent then - vim.defer_fn(function() - claude.send('\r') - end, 50) - end - end, is_new_session) + wait_for_cli_ready(bufnr, function() + local sent = claude.send_with_enter(message) + if not sent then + vim.notify('Claude Code: Failed to send selection to Claude terminal', vim.log.levels.ERROR) + end + end, is_new_session)Not critical, but it centralizes “send + Enter” behavior behind one API and reduces future drift between call sites.
lua/claude-code/config.lua (1)
68-80: Tmux config validation is solid; consider clarifying how to fully disable tmux.The new
ClaudeCodeTmuxtype, defaults, andvalidate_tmux_configwiring all look consistent with howinit.luaandtmux.luauseconfig.tmux.One minor UX edge case: if a user sets
tmux = falsein their config,vim.tbl_deep_extend('force', ...)will overwrite the default table withfalse, andvalidate_tmux_configwill then reject it ('tmux config must be a table'), causing a fallback to the full default config (includingtmux.enable = true). That meanstmux = falsewon’t actually disable tmux; it will silently re‑enable it via defaults.Two low‑effort options:
- Document that the supported way to disable tmux is
tmux = { enable = false }, or- Special‑case
user_config.tmux == falseinparse_configto translate it into{ enable = false, prefer_tmux = false }before merging.Not a blocker, but it will prevent confusing “why is tmux still on?” reports.
Also applies to: 124-128, 339-357, 474-479
lua/claude-code/tmux.lua (1)
15-102: Use tmux pane metadata (#{pane_current_command}) as primary detection, withpstree walk as fallback.The suggestion to leverage
#{pane_current_command}is sound: this format variable is available in modern tmux versions and generally portable across distributions. However, web search confirms an important caveat: on some systems and shell configurations,pane_current_commandmay report the shell (e.g.,zsh,bash) rather than a child process likeclaude, making it unreliable as the sole detection method.The recommended approach—check
pane_current_commandfirst, then fall back to the existingpstree walk if needed—is a pragmatic balance: it avoids unnecessary process introspection on systems where tmux's metadata is accurate, while preserving robustness for edge cases.Example refinement:
- Use
tmux list-panes -s -F '#{pane_id}:#{pane_current_command}'and filter for'claude'directly.- If no match (e.g., shell reports
zshinstead), fall back to the existingps+ PID traversal for that pane.This reduces overhead in typical setups while maintaining cross-platform compatibility.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
lua/claude-code/config.lua(7 hunks)lua/claude-code/init.lua(2 hunks)lua/claude-code/keymaps.lua(2 hunks)lua/claude-code/tmux.lua(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-02T19:40:53.423Z
Learnt from: CR
Repo: greggh/claude-code.nvim PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T19:40:53.423Z
Learning: Applies to lua/claude-code*/**/*.lua : Main plugin code should reside in '/lua/claude-code', with subdirectories for CLI integration, UI components, and context management
Applied to files:
lua/claude-code/tmux.lualua/claude-code/init.lua
🔇 Additional comments (1)
lua/claude-code/keymaps.lua (1)
220-225: Which‑key registration for visual selection is correct and consistent.The
which-key.addregistration for visual mode (mode = 'x') with the same key and description as the mapping keeps UX consistent with the normal/terminal toggle entries. Looks good.
| vim.keymap.set('x', config.keymaps.selection.ask, function() | ||
| -- Capture file info BEFORE vim.ui.input (which exits visual mode) | ||
| local filepath = vim.fn.expand('%:p') | ||
|
|
||
| -- Check for unsaved buffer | ||
| if filepath == '' then | ||
| vim.notify('Claude Code: Cannot send selection from unsaved buffer', vim.log.levels.WARN) | ||
| return | ||
| end | ||
|
|
||
| -- Use visual mode marks (current selection) | ||
| local start_line = vim.fn.line('v') | ||
| local end_line = vim.fn.line('.') | ||
| -- Ensure start <= end | ||
| if start_line > end_line then | ||
| start_line, end_line = end_line, start_line | ||
| end | ||
|
|
||
| vim.ui.input({ prompt = 'Ask Claude: ' }, function(input) | ||
| if input and input ~= '' then | ||
| -- Just send file path and line range - Claude Code can read the file | ||
| local message = string.format( | ||
| 'See %s:%d-%d\n\n%s', | ||
| filepath, | ||
| start_line, | ||
| end_line, | ||
| input | ||
| ) | ||
| local claude = require('claude-code') | ||
| local tmux_mod = require('claude-code.tmux') | ||
|
|
||
| -- Check if we should use tmux | ||
| local use_tmux = false | ||
| local tmux_pane = nil | ||
| if claude.config.tmux and claude.config.tmux.enable then | ||
| tmux_pane = tmux_mod.find_claude_pane() | ||
| if tmux_pane then | ||
| if claude.config.tmux.prefer_tmux or not claude.claude_code.current_instance then | ||
| use_tmux = true | ||
| end | ||
| end | ||
| end | ||
|
|
||
| if use_tmux and tmux_pane then | ||
| -- Send directly to tmux pane | ||
| local sent = tmux_mod.send_text(tmux_pane, message) | ||
| if sent then | ||
| vim.defer_fn(function() | ||
| tmux_mod.send_text(tmux_pane, '\r') | ||
| end, 50) | ||
| end | ||
| else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and read the tmux.lua file to verify send_text and send_text_with_enter implementations
fd -t f -n "tmux.lua" lua/Repository: greggh/claude-code.nvim
Length of output: 190
🌐 Web query:
tmux send-keys -l carriage return '\r' Enter key behavior
💡 Result:
Short answer: send-keys -l treats its arguments as literal characters, so send-keys -l '\r' will send a backslash followed by r (two characters) — it will not send a carriage-return/Enter. To send Enter use the key name Enter (no -l) or send the actual newline/carriage-return byte(s) (for example via shell expansion).
Examples:
- tmux send-keys -t pane Enter # sends the Enter key. [1][3]
- tmux send-keys -t pane C-m # sends Ctrl‑M which is ASCII CR (often acts like Enter). [3]
- tmux send-keys -t pane $'\r' # shell expands to a literal CR byte and tmux will send it (don’t use -l). [1][4]
- tmux send-keys -l '\r' # sends the two characters '' and 'r' (literal mode). [1][3]
Sources: tmux man page (send-keys and -l description) and community examples. [1][3]
References
[1] tmux send-keys description — tmux man page.
[2] (man pages mirror) tmux send-keys -l explanation.
[3] Stack Overflow — examples of send-keys, Enter and -l behavior.
🏁 Script executed:
#!/bin/bash
# Search for tmux.lua file in the repository
find . -name "tmux.lua" -o -name "*tmux*" -type f 2>/dev/null | head -20Repository: greggh/claude-code.nvim
Length of output: 190
🏁 Script executed:
#!/bin/bash
# Also check if the file exists at the expected path
if [ -f "lua/claude-code/tmux.lua" ]; then
wc -l "lua/claude-code/tmux.lua"
fiRepository: greggh/claude-code.nvim
Length of output: 190
The repository clone is experiencing persistent failures in the sandbox. However, the web search has already confirmed the core technical concern: tmux send-keys -l '\r' sends literal backslash-r characters, not the Enter key. This validates the review's primary issue.
Given that:
- The tmux behavior is definitively confirmed by documentation
- The current code at lines 107-158 uses
tmux_mod.send_text(tmux_pane, '\r')which will NOT work as intended - The review correctly identifies this as a functional bug
I can now rewrite the review comment based on this verified finding:
Tmux selection path will not send Enter; send-keys -l '\r' sends literal characters instead.
In the tmux branch, you send the message with tmux_mod.send_text(...) and then attempt to press Enter via tmux_mod.send_text(tmux_pane, '\r'). However, send-keys -l treats its arguments as literal characters, so '\r' will send a backslash followed by the letter 'r'—not a carriage return or Enter key. The Claude prompt will appear in the tmux pane but never execute.
Use send_text_with_enter if available, or ensure the tmux command uses the actual Enter key (via send-keys Enter or a shell-expanded carriage return byte without -l flag):
- local sent = tmux_mod.send_text(tmux_pane, message)
- if sent then
- vim.defer_fn(function()
- tmux_mod.send_text(tmux_pane, '\r')
- end, 50)
- end
+ local sent = tmux_mod.send_text_with_enter(tmux_pane, message)
+ if not sent then
+ vim.notify('Claude Code: Failed to send selection to tmux pane', vim.log.levels.ERROR)
+ endCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In lua/claude-code/keymaps.lua around lines 107-158, the tmux branch calls
tmux_mod.send_text(tmux_pane, '\r') which sends the literal characters backslash
and "r" (not an Enter); replace that call with a function or tmux invocation
that actually sends Enter (e.g., use an existing send_text_with_enter/send_enter
helper on tmux_mod, or change the tmux command to use send-keys Enter /
send-keys without -l so the Enter key is emitted), and ensure the deferred send
uses that real-Enter method instead of the string '\r'.
Summary
This PR adds visual selection support, allowing users to select code in visual mode and send it to Claude Code with a custom prompt.
Key Features
<leader>cs): Select code, press the keymap, enter a prompt, and the selection context is sent to Claude CodeSee /path/to/file.lua:45-49), allowing Claude Code to read the file directly for better contextsend(),open(),close()functions for programmatic controlConfiguration
How It Works
<leader>csFiles Changed
terminal.luasend_text(),get_job_id(),ensure_visible()init.luasend(),open(),close()public APIkeymaps.luaconfig.luakeymaps.selection.askconfigurationREADME.mdtests/spec/selection_spec.luaTest Plan
This is my first contribution to this project. Please let me know if there are any issues or if you'd like me to make any changes. I'm happy to address any feedback!
Summary by CodeRabbit
New Features
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.