clipboard.vim (11652B)
1 " The clipboard provider uses shell commands to communicate with the clipboard. 2 " The provider function will only be registered if a supported command is 3 " available. 4 5 if exists('g:loaded_clipboard_provider') 6 finish 7 endif 8 " Default to 0. provider#clipboard#Executable() may set 2. 9 " To force a reload: 10 " :unlet g:loaded_clipboard_provider 11 " :runtime autoload/provider/clipboard.vim 12 let g:loaded_clipboard_provider = 0 13 14 let s:copy = {} 15 let s:paste = {} 16 let s:clipboard = {} 17 18 " When caching is enabled, store the jobid of the xclip/xsel process keeping 19 " ownership of the selection, so we know how long the cache is valid. 20 let s:selection = { 'owner': 0, 'data': [], 'stderr_buffered': v:true } 21 22 function! s:selection.on_exit(jobid, data, event) abort 23 " At this point this nvim instance might already have launched 24 " a new provider instance. Don't drop ownership in this case. 25 if self.owner == a:jobid 26 let self.owner = 0 27 endif 28 " Don't print if exit code is >= 128 ( exit is 128+SIGNUM if by signal (e.g. 143 on SIGTERM)) 29 if a:data > 0 && a:data < 128 30 echohl WarningMsg 31 echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(self.stderr) 32 echohl None 33 endif 34 endfunction 35 36 let s:selections = { '*': s:selection, '+': copy(s:selection) } 37 38 function! s:try_cmd(cmd, ...) abort 39 let out = systemlist(a:cmd, (a:0 ? a:1 : ['']), 1) 40 if v:shell_error 41 if !exists('s:did_error_try_cmd') 42 echohl WarningMsg 43 echomsg "clipboard: error: ".(len(out) ? out[0] : v:shell_error) 44 echohl None 45 let s:did_error_try_cmd = 1 46 endif 47 return 0 48 endif 49 return out 50 endfunction 51 52 " Returns TRUE if `cmd` exits with success, else FALSE. 53 function! s:cmd_ok(cmd) abort 54 call system(a:cmd) 55 return v:shell_error == 0 56 endfunction 57 58 function! s:split_cmd(cmd) abort 59 return (type(a:cmd) == v:t_string) ? split(a:cmd, " ") : a:cmd 60 endfunction 61 62 function! s:set_osc52() abort 63 let s:copy['+'] = v:lua.require'vim.ui.clipboard.osc52'.copy('+') 64 let s:copy['*'] = v:lua.require'vim.ui.clipboard.osc52'.copy('*') 65 let s:paste['+'] = v:lua.require'vim.ui.clipboard.osc52'.paste('+') 66 let s:paste['*'] = v:lua.require'vim.ui.clipboard.osc52'.paste('*') 67 return 'OSC 52' 68 endfunction 69 70 function! s:set_pbcopy() abort 71 let s:copy['+'] = ['pbcopy'] 72 let s:paste['+'] = ['pbpaste'] 73 let s:copy['*'] = s:copy['+'] 74 let s:paste['*'] = s:paste['+'] 75 let s:cache_enabled = 0 76 return 'pbcopy' 77 endfunction 78 79 function! s:set_wayland() abort 80 let s:copy['+'] = ['wl-copy', '--type', 'text/plain'] 81 let s:paste['+'] = ['wl-paste', '--no-newline'] 82 let s:copy['*'] = ['wl-copy', '--primary', '--type', 'text/plain'] 83 let s:paste['*'] = ['wl-paste', '--no-newline', '--primary'] 84 return 'wl-copy' 85 endfunction 86 87 function! s:set_wayclip() abort 88 let s:copy['+'] = ['waycopy'] 89 let s:paste['+'] = ['waypaste'] 90 let s:copy['*'] = ['waycopy', '-p'] 91 let s:paste['*'] = ['waypaste', '-p'] 92 return 'wayclip' 93 endfunction 94 95 function! s:set_xsel() abort 96 let s:copy['+'] = ['xsel', '--nodetach', '-i', '-b'] 97 let s:paste['+'] = ['xsel', '-o', '-b'] 98 let s:copy['*'] = ['xsel', '--nodetach', '-i', '-p'] 99 let s:paste['*'] = ['xsel', '-o', '-p'] 100 return 'xsel' 101 endfunction 102 103 function! s:set_xclip() abort 104 let s:copy['+'] = ['xclip', '-quiet', '-i', '-selection', 'clipboard'] 105 let s:paste['+'] = ['xclip', '-o', '-selection', 'clipboard'] 106 let s:copy['*'] = ['xclip', '-quiet', '-i', '-selection', 'primary'] 107 let s:paste['*'] = ['xclip', '-o', '-selection', 'primary'] 108 return 'xclip' 109 endfunction 110 111 function! s:set_lemonade() abort 112 let s:copy['+'] = ['lemonade', 'copy'] 113 let s:paste['+'] = ['lemonade', 'paste'] 114 let s:copy['*'] = ['lemonade', 'copy'] 115 let s:paste['*'] = ['lemonade', 'paste'] 116 return 'lemonade' 117 endfunction 118 119 function! s:set_doitclient() abort 120 let s:copy['+'] = ['doitclient', 'wclip'] 121 let s:paste['+'] = ['doitclient', 'wclip', '-r'] 122 let s:copy['*'] = s:copy['+'] 123 let s:paste['*'] = s:paste['+'] 124 return 'doitclient' 125 endfunction 126 127 function! s:set_win32yank() abort 128 if has('wsl') && getftype(exepath('win32yank.exe')) == 'link' 129 let win32yank = resolve(exepath('win32yank.exe')) 130 else 131 let win32yank = 'win32yank.exe' 132 endif 133 let s:copy['+'] = [win32yank, '-i', '--crlf'] 134 let s:paste['+'] = [win32yank, '-o', '--lf'] 135 let s:copy['*'] = s:copy['+'] 136 let s:paste['*'] = s:paste['+'] 137 return 'win32yank' 138 endfunction 139 140 function! s:set_putclip() abort 141 let s:copy['+'] = ['putclip'] 142 let s:paste['+'] = ['getclip'] 143 let s:copy['*'] = s:copy['+'] 144 let s:paste['*'] = s:paste['+'] 145 return 'putclip' 146 endfunction 147 148 function! s:set_clip() abort 149 let s:copy['+'] = ['clip'] 150 let s:paste['+'] = ['powershell', '-NoProfile', '-NoLogo', '-Command', 'Get-Clipboard'] 151 let s:copy['*'] = s:copy['+'] 152 let s:paste['*'] = s:paste['+'] 153 return 'clip' 154 endfunction 155 156 function! s:set_termux() abort 157 let s:copy['+'] = ['termux-clipboard-set'] 158 let s:paste['+'] = ['termux-clipboard-get'] 159 let s:copy['*'] = s:copy['+'] 160 let s:paste['*'] = s:paste['+'] 161 return 'termux-clipboard' 162 endfunction 163 164 function! s:set_tmux() abort 165 let tmux_v = v:lua.vim.version.parse(system(['tmux', '-V'])) 166 if !empty(tmux_v) && !v:lua.vim.version.lt(tmux_v, [3,2,0]) 167 let s:copy['+'] = ['tmux', 'load-buffer', '-w', '-'] 168 else 169 let s:copy['+'] = ['tmux', 'load-buffer', '-'] 170 endif 171 let s:paste['+'] = ['sh', '-c', 'tmux refresh-client -l && sleep 0.05 && tmux save-buffer -'] 172 let s:copy['*'] = s:copy['+'] 173 let s:paste['*'] = s:paste['+'] 174 return 'tmux' 175 endfunction 176 177 let s:cache_enabled = 1 178 let s:err = '' 179 180 function! provider#clipboard#Error() abort 181 return s:err 182 endfunction 183 184 function! provider#clipboard#Executable() abort 185 " Setting g:clipboard to v:false explicitly opts-in to using the "builtin" clipboard providers below 186 if exists('g:clipboard') && g:clipboard isnot# v:false 187 if v:t_string ==# type(g:clipboard) 188 " Handle string form of g:clipboard for all builtin providers 189 if 'osc52' == g:clipboard 190 " User opted-in to OSC 52 by manually setting g:clipboard. 191 return s:set_osc52() 192 elseif 'pbcopy' == g:clipboard 193 return s:set_pbcopy() 194 elseif 'wl-copy' == g:clipboard 195 return s:set_wayland() 196 elseif 'wayclip' == g:clipboard 197 return s:set_wayclip() 198 elseif 'xsel' == g:clipboard 199 return s:set_xsel() 200 elseif 'xclip' == g:clipboard 201 return s:set_xclip() 202 elseif 'lemonade' == g:clipboard 203 return s:set_lemonade() 204 elseif 'doitclient' == g:clipboard 205 return s:set_doitclient() 206 elseif 'win32yank' == g:clipboard 207 return s:set_win32yank() 208 elseif 'putclip' == g:clipboard 209 return s:set_putclip() 210 elseif 'clip' == g:clipboard 211 return s:set_clip() 212 elseif 'termux' == g:clipboard 213 return s:set_termux() 214 elseif 'tmux' == g:clipboard 215 return s:set_tmux() 216 endif 217 endif 218 219 if type({}) isnot# type(g:clipboard) 220 \ || type({}) isnot# type(get(g:clipboard, 'copy', v:null)) 221 \ || type({}) isnot# type(get(g:clipboard, 'paste', v:null)) 222 let s:err = 'clipboard: invalid g:clipboard' 223 return '' 224 endif 225 226 let s:copy = {} 227 let s:copy['+'] = s:split_cmd(get(g:clipboard.copy, '+', v:null)) 228 let s:copy['*'] = s:split_cmd(get(g:clipboard.copy, '*', v:null)) 229 230 let s:paste = {} 231 let s:paste['+'] = s:split_cmd(get(g:clipboard.paste, '+', v:null)) 232 let s:paste['*'] = s:split_cmd(get(g:clipboard.paste, '*', v:null)) 233 234 let s:cache_enabled = get(g:clipboard, 'cache_enabled', 0) 235 return get(g:clipboard, 'name', 'g:clipboard') 236 elseif has('mac') 237 return s:set_pbcopy() 238 elseif !empty($WAYLAND_DISPLAY) && executable('wl-copy') && executable('wl-paste') 239 return s:set_wayland() 240 elseif !empty($WAYLAND_DISPLAY) && executable('waycopy') && executable('waypaste') 241 return s:set_wayclip() 242 elseif !empty($DISPLAY) && executable('xsel') && s:cmd_ok('xsel -o -b') 243 return s:set_xsel() 244 elseif !empty($DISPLAY) && executable('xclip') 245 return s:set_xclip() 246 elseif executable('lemonade') 247 return s:set_lemonade() 248 elseif executable('doitclient') 249 return s:set_doitclient() 250 elseif executable('win32yank.exe') 251 return s:set_win32yank() 252 elseif executable('putclip') && executable('getclip') 253 return s:set_putclip() 254 elseif executable('clip') && executable('powershell') 255 return s:set_clip() 256 elseif executable('termux-clipboard-set') 257 return s:set_termux() 258 elseif !empty($TMUX) && executable('tmux') 259 return s:set_tmux() 260 elseif get(get(g:, 'termfeatures', {}), 'osc52') && &clipboard ==# '' 261 " Don't use OSC 52 when 'clipboard' is set. It can be slow and cause a lot 262 " of user prompts. Users can opt-in to it by setting g:clipboard manually. 263 return s:set_osc52() 264 endif 265 266 let s:err = 'clipboard: No clipboard tool. :help clipboard' 267 return '' 268 endfunction 269 270 function! s:clipboard.get(reg) abort 271 if s:selections[a:reg].owner > 0 272 return s:selections[a:reg].data 273 end 274 275 let clipboard_data = type(s:paste[a:reg]) == v:t_func ? s:paste[a:reg]() : s:try_cmd(s:paste[a:reg]) 276 if match(&clipboard, '\v(unnamed|unnamedplus)') >= 0 277 \ && type(clipboard_data) == v:t_list 278 \ && get(s:selections[a:reg].data, 0, []) ==# clipboard_data 279 " When system clipboard return is same as our cache return the cache 280 " as it contains regtype information 281 return s:selections[a:reg].data 282 end 283 return clipboard_data 284 endfunction 285 286 function! s:clipboard.set(lines, regtype, reg) abort 287 if a:reg == '"' 288 call s:clipboard.set(a:lines,a:regtype,'+') 289 if s:copy['*'] != s:copy['+'] 290 call s:clipboard.set(a:lines,a:regtype,'*') 291 end 292 return 0 293 end 294 295 if s:cache_enabled == 0 || type(s:copy[a:reg]) == v:t_func 296 if type(s:copy[a:reg]) == v:t_func 297 call s:copy[a:reg](a:lines, a:regtype) 298 else 299 call s:try_cmd(s:copy[a:reg], a:lines) 300 endif 301 "Cache it anyway we can compare it later to get regtype of the yank 302 let s:selections[a:reg] = copy(s:selection) 303 let s:selections[a:reg].data = [a:lines, a:regtype] 304 return 0 305 end 306 307 if s:selections[a:reg].owner > 0 308 let prev_job = s:selections[a:reg].owner 309 end 310 let s:selections[a:reg] = copy(s:selection) 311 let selection = s:selections[a:reg] 312 let selection.data = [a:lines, a:regtype] 313 let selection.argv = s:copy[a:reg] 314 let selection.detach = s:cache_enabled 315 let selection.cwd = "/" 316 let jobid = jobstart(selection.argv, selection) 317 if jobid > 0 318 call jobsend(jobid, a:lines) 319 call jobclose(jobid, 'stdin') 320 " xclip does not close stdout when receiving input via stdin 321 if selection.argv[0] ==# 'xclip' 322 call jobclose(jobid, 'stdout') 323 endif 324 let selection.owner = jobid 325 let ret = 1 326 else 327 echohl WarningMsg 328 echomsg 'clipboard: failed to execute: '.(s:copy[a:reg]) 329 echohl None 330 let ret = 1 331 endif 332 333 " The previous provider instance should exit when the new one takes 334 " ownership, but kill it to be sure we don't fill up the job table. 335 if exists('prev_job') 336 call timer_start(1000, {... -> 337 \ jobwait([prev_job], 0)[0] == -1 338 \ && jobstop(prev_job)}) 339 endif 340 341 return ret 342 endfunction 343 344 function! provider#clipboard#Call(method, args) abort 345 if get(s:, 'here', v:false) " Clipboard provider must not recurse. #7184 346 return 0 347 endif 348 let s:here = v:true 349 try 350 return call(s:clipboard[a:method],a:args,s:clipboard) 351 finally 352 let s:here = v:false 353 endtry 354 endfunction 355 356 " eval_has_provider() decides based on this variable. 357 let g:loaded_clipboard_provider = empty(provider#clipboard#Executable()) ? 0 : 2