diff --git a/README.md b/README.md index d346be1..63810e7 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,7 @@ let g:claude_code_map_zoom = 'z' | Normal | `cV` | Toggle with `--verbose` | | Terminal | `` | Hide Claude Code terminal | | Terminal | `z` | **Zoom Toggle**: Maximize or restore terminal | +| Terminal | `` | **Paste**: Paste system clipboard content | | Terminal | `` | Navigate to adjacent window | ### Extended keymaps (`g:claude_code_map_extended_prefix` + key) @@ -307,6 +308,7 @@ let g:claude_code_float_border = 'double' | `g:claude_code_map_extended_keys` | `1` | Register `c*` keymaps | | `g:claude_code_map_toggle` | `''` | Toggle key | | `g:claude_code_map_zoom` | `'z'` | Zoom key | +| `g:claude_code_map_paste` | `''` | Paste key | | `g:claude_code_map_continue` | `'cC'` | Continue key | | `g:claude_code_map_verbose` | `'cV'` | Verbose key | | `g:claude_code_map_extended_prefix` | `'c'` | Prefix for all extended keymaps | @@ -319,6 +321,7 @@ let g:claude_code_float_border = 'double' | `g:claude_code_model` | `''` | Claude model override | | `g:claude_code_debug` | `0` | Enable debug logging to message area | | `g:claude_code_diff_preview` | `0` | Auto-start diff preview polling on Vim startup | +| `g:claude_code_bracketed_paste` | `1` | Enable bracketed paste mode support | | `g:claude_code_terminal_start_delay` | `300` | Delay (ms) before attaching to Claude terminal | Buffer-local `b:claude_code_*` overrides take precedence over `g:` variables. diff --git a/autoload/claude_code/config.vim b/autoload/claude_code/config.vim index 39c978f..28c4bff 100644 --- a/autoload/claude_code/config.vim +++ b/autoload/claude_code/config.vim @@ -34,6 +34,8 @@ let s:defaults = { \ 'map_continue': 'cC', \ 'map_verbose': 'cV', \ 'map_zoom': 'z', + \ 'map_paste': '', + \ 'bracketed_paste': 1, \ 'debug': 0, \ 'terminal_start_delay': 300, \ 'scroll_keys': 1, diff --git a/autoload/claude_code/git.vim b/autoload/claude_code/git.vim index a6b52c3..e0b8bec 100644 --- a/autoload/claude_code/git.vim +++ b/autoload/claude_code/git.vim @@ -19,18 +19,16 @@ function! claude_code#git#root() abort return s:git_root_cache[l:cwd] endif - let s:output_redirect = ' 2>/dev/null' - if has("win32") - let s:output_redirect = ' 2>nul' - endif + let l:null = has('win32') ? 'nul' : '/dev/null' + let l:redirect = ' 2>' . l:null - let l:inside = system('git -C ' . shellescape(l:cwd) . ' rev-parse --is-inside-work-tree' . s:output_redirect) + let l:inside = system('git -C ' . shellescape(l:cwd) . ' rev-parse --is-inside-work-tree' . l:redirect) if v:shell_error || trim(l:inside) !=# 'true' let s:git_root_cache[l:cwd] = '' return '' endif - let l:root = trim(system('git -C ' . shellescape(l:cwd) . ' rev-parse --show-toplevel' . s:output_redirect)) + let l:root = trim(system('git -C ' . shellescape(l:cwd) . ' rev-parse --show-toplevel' . l:redirect)) if v:shell_error let s:git_root_cache[l:cwd] = '' return '' diff --git a/autoload/claude_code/keymaps.vim b/autoload/claude_code/keymaps.vim index ca5dc81..a1b0ef0 100644 --- a/autoload/claude_code/keymaps.vim +++ b/autoload/claude_code/keymaps.vim @@ -19,6 +19,19 @@ function! claude_code#keymaps#setup_terminal(bufnr) abort execute 'tnoremap l' execute 'tnoremap ' . claude_code#config#get('map_zoom') . ' :Claude zoom' + " Paste from system clipboard + let l:paste_key = claude_code#config#get('map_paste') + if !empty(l:paste_key) + execute 'tnoremap ' . l:paste_key . ' :call claude_code#terminal#paste()' + endif + + " Bracketed paste support + if claude_code#config#get('bracketed_paste') + " This allows Vim to handle the escape sequence sent by terminal emulators + " during a paste. We bridge it to our mapping-free paste function. + execute 'tnoremap [200~ :call claude_code#terminal#paste()' + endif + " Mouse/touchpad scroll in terminal mode: escape to Normal, scroll, stay in " Normal so the user can keep reading. Vim passes raw ScrollWheel events " through to the running program when in terminal mode, so we must intercept diff --git a/autoload/claude_code/meta_commands.vim b/autoload/claude_code/meta_commands.vim index 9848990..a5ae1a8 100644 --- a/autoload/claude_code/meta_commands.vim +++ b/autoload/claude_code/meta_commands.vim @@ -91,7 +91,8 @@ function! claude_code#meta_commands#version() abort \ ] if executable('claude') - let l:cli_ver = trim(system('claude --version 2>/dev/null')) + let l:null = has('win32') ? 'nul' : '/dev/null' + let l:cli_ver = trim(system('claude --version 2>' . l:null)) if v:shell_error || empty(l:cli_ver) let l:cli_ver = '(could not determine)' endif diff --git a/autoload/claude_code/terminal.vim b/autoload/claude_code/terminal.vim index d74c970..8476830 100644 --- a/autoload/claude_code/terminal.vim +++ b/autoload/claude_code/terminal.vim @@ -145,18 +145,38 @@ function! s:instance_cwd(instance_id) abort return '' endif " Normalise both sides to forward slashes for a reliable comparison. - let l:inst = substitute(a:instance_id, '\\', '/', 'g') - let l:cwd = substitute(getcwd(), '\\', '/', 'g') + let l:inst = tr(a:instance_id, '\', '/') + let l:cwd = tr(getcwd(), '\', '/') if l:inst ==# l:cwd return '' endif " Return an OS-native path so term_start's cwd option works on all platforms. if has('win32') - return substitute(a:instance_id, '/', '\\', 'g') + return tr(a:instance_id, '/', '\') endif return a:instance_id endfunction +" Paste from system clipboard into the terminal buffer. +" Bypasses terminal mappings using term_sendkeys(). +function! claude_code#terminal#paste() abort + let l:bnr = bufnr('%') + if getbufvar(l:bnr, '&buftype') !=# 'terminal' + return + endif + + " Use + register (system clipboard) if available, fallback to * or default. + let l:reg = has('clipboard') ? '+' : '"' + let l:text = getreg(l:reg) + if empty(l:text) && l:reg == '+' + let l:text = getreg('*') + endif + + if !empty(l:text) + call term_sendkeys(l:bnr, l:text) + endif +endfunction + " Create a brand-new Claude Code terminal. function! s:create_new(instance_id) abort call claude_code#util#debug('terminal: creating new instance for ' . a:instance_id) diff --git a/doc/claude_code.txt b/doc/claude_code.txt index 109e1d0..beb2834 100644 --- a/doc/claude_code.txt +++ b/doc/claude_code.txt @@ -313,6 +313,7 @@ Default terminal keymaps (when |g:claude_code_map_keys| is 1): Terminal mode (inside the Claude window): ~ Hide Claude Code terminal z Zoom Toggle: Maximize or restore terminal + Paste: Paste system clipboard content Navigate to adjacent window Extended keymaps (when g:claude_code_map_extended_keys is 1): @@ -423,6 +424,11 @@ g:claude_code_map_toggle Default: '' *g:claude_code_map_zoom* g:claude_code_map_zoom Default: 'z' Key to toggle the zoomed (maximized) state. + + *g:claude_code_map_paste* +g:claude_code_map_paste Default: '' + Key to paste text from the system clipboard into the terminal. + Bypasses terminal mappings using |term_sendkeys()|. *g:claude_code_map_continue* g:claude_code_map_continue Default: 'cC' @@ -477,6 +483,11 @@ g:claude_code_diff_preview Default: 0 register the hooks. > let g:claude_code_diff_preview = 1 < + *g:claude_code_bracketed_paste* +g:claude_code_bracketed_paste Default: 1 + Enable bracketed paste mode support. When enabled, terminal emulator + pastes (e.g. Ctrl-Shift-V) are captured and sent via |term_sendkeys()| + to avoid triggering Vim mappings. *g:claude_code_terminal_start_delay* g:claude_code_terminal_start_delay Default: 300