├── LICENSE ├── README.md └── plugin └── pbcopy.vim /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Andrew Hallagan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vim-pbcopy 2 | ========== 3 | This is a Vim plugin that exposes a `cy` mapping in Visual mode and a 4 | `cy{motion}` mapping in Normal mode which both attempt to yank the 5 | selected/moved-over text and pipe it (via `ssh` if necessary) to the 6 | `pbcopy` command on a Mac OS X client. What does this mean? It means you can 7 | use `cy` wherever you'd normally use `y` and have that text available in 8 | your Mac OS X system clipboard, whether you're working locally or remotely. 9 | It also performs a simple `y` yank operation on your selection so you'll 10 | have it in the default `"` yank register as well. 11 | 12 | Installation 13 | ------------ 14 | If you don't have a preferred installation method, I recommend 15 | installing [pathogen.vim](https://github.com/tpope/vim-pathogen), and 16 | then simply copy and paste: 17 | 18 | cd ~/.vim/bundle 19 | git clone https://github.com/ahw/vim-pbcopy.git 20 | 21 | 22 | Local Usage 23 | ----------- 24 | Use `cy{motion}` to copy text to the Mac OSX system clipboard or hit `cy` 25 | after selecting text in Visual mode. In the background it is simply running 26 | running 27 | 28 | ```sh 29 | echo -n "whatever text you copied" | pbcopy 30 | ``` 31 | 32 | You can configure the local pbcopy command in your `~/.vimrc` file: 33 | 34 | > **~/.vimrc** 35 | > 36 | > ```vim 37 | > let g:vim_pbcopy_local_cmd = "pbcopy" 38 | > ``` 39 | 40 | 41 | Remote Usage 42 | ------------ 43 | ### Option 1: Port Forwarding + Clipper 44 | 45 | > Clipper is an OS X 'launch agent' that runs in the background providing a 46 | > service that exposes the local clipboard to tmux sessions and other 47 | > processes running both locally and remotely. ... We can use it from any 48 | > process, including Vim 49 | > 50 | > Source: https://github.com/wincent/clipper/blob/master/README.md 51 | 52 | Clipper listens for tcp data on a select localhost port, and puts all 53 | input it receipes on the system clipboard. This is useful for both local and 54 | remote use. Locally, using clipper rather than `pbcopy` circumvents known 55 | problems with `pbcopy` and `tmux`. Remotely, you can forward a remote port 56 | to the local clipper's port via an ssh reverse tunnel. 57 | 58 | Clipper can be configured in a variety of ways. Here's what's worked for me. 59 | 60 | 1. `brew install clipper` (install Clipper) 61 | 2. `brew services start clipper` (start the Clipper daemon) 62 | 3. `ssh -R 8377:localhost:8377 my.remote.host` (set up SSH remote port forwarding) 63 | 4. Add the following to `~/.vimrc` on the **remote host**: `let g:vim_pbcopy_remote_cmd = "nc localhost 8377"`. If for some reason things still aren't working, you can debug Clipper in isolation by running `echo "hello from local machine" | nc localhost 8377` on your local machine, and you can debug the port forwarding setup by running `echo "hello from remote machine" | nc localhost 8377` from the remote machine. 64 | 65 | ### Option 2: Direct SSH Access to Local Host 66 | Nothing changes except you need to set the Vim global variable 67 | `g:vim_pbcopy_remote_cmd` variable in your `~/.vimrc` file to your preferred 68 | way of running pbcopy on your local machine. 69 | 70 | > **~/.vimrc** 71 | > 72 | > ```vim 73 | > let g:vim_pbcopy_remote_cmd = "ssh your-mac-laptop.example.com pbcopy" 74 | > ``` 75 | 76 | This will pipe the copied text to your Mac client's `pbcopy` over SSH. 77 | 78 | ```sh 79 | echo -n "whatever text you copied" | ssh your-mac-laptop.example.com pbcopy 80 | ``` 81 | 82 | This assumes that you have an SSH server running on your laptop, of course. 83 | Note: I haven't tested this using password-based SSH logins (my 84 | configuration uses SSH keys). 85 | 86 | See **Option #1** above for other remote usage possiblities. 87 | 88 | Troubleshooting 89 | --------------- 90 | 91 | ### Problems with Newlines 92 | There are some inconsistencies around the way backslashes are escaped on 93 | different systems. Try copying some text with `\` and `\n` characters. If 94 | you're getting incorrect output when you then try to paste that text somewhere, 95 | see the table below for how to remedy. 96 | 97 | 98 | If you copied this | and pasted this | add this to ~/.vimrc 99 | --- | --- | --- 100 | console.log('some\nthing'); | console.log('some\nthing'); | Nothing! It Just Works™ 101 | console.log('some\nthing'); | console.log('some
thing'); | `let g:vim_pbcopy_escape_backslashes = 1` 102 | console.log('some\nthing'); | console.log('some\\\nthing'); | `let g:vim_pbcopy_escape_backslashes = 0` 103 | -------------------------------------------------------------------------------- /plugin/pbcopy.vim: -------------------------------------------------------------------------------- 1 | let g:vim_pbcopy_local_cmd = "pbcopy" 2 | 3 | vnoremap cy :call copyVisualSelection(visualmode(), 1) 4 | nnoremap cy :set opfunc=copyVisualSelectiong@ 5 | 6 | function! s:getVisualSelection() 7 | let [lnum1, col1] = getpos("'<")[1:2] 8 | let [lnum2, col2] = getpos("'>")[1:2] 9 | let lines = getline(lnum1, lnum2) 10 | let lines[-1] = lines[-1][:col2 - (&selection == 'inclusive' ? 1 : 2)] 11 | let lines[0] = lines[0][col1 - 1:] 12 | return lines 13 | endfunction 14 | 15 | function! s:isRunningLocally() 16 | if len($SSH_CLIENT) 17 | return 0 18 | else 19 | return 1 20 | endif 21 | endfunction 22 | 23 | function! s:getTransformedLine(line) 24 | let transforms = {} 25 | if exists("g:vim_pbcopy_regex_transforms") 26 | let transforms = g:vim_pbcopy_regex_transforms 27 | endif 28 | 29 | for key in keys(transforms) 30 | echom "[getTransformedLine] line: <" . a:line . ">" 31 | echom "[getTransformedLine] match expression: " . key . ", regex transform: " . transforms[key] 32 | if a:line =~ key 33 | let a = substitute(a:line, key, transforms[key], "") 34 | echom "[getTransformedLine] tranformed line: " . a 35 | endif 36 | endfor 37 | 38 | return a:line 39 | endfunction 40 | 41 | function! s:getTransformedLines(listOfLines) 42 | let transforms = {} 43 | if exists("g:vim_pbcopy_regex_transforms") 44 | let transforms = g:vim_pbcopy_regex_transforms 45 | endif 46 | 47 | for key in keys(transforms) 48 | " echom "match expression: " . key . ", regex transform: " . transforms[key] 49 | endfor 50 | return a:listOfLines 51 | endfunction 52 | 53 | function! s:getShellEscapedLines(listOfLines) 54 | " Join the lines with the literal characters '\n' (two chars) so that 55 | " they will be echo-ed correctly. Passing a non-zero second argument to 56 | " shellescape means it will escape "!" and other characters special to 57 | " Vim. See :help shellescape. We need this because otherwise execute" 58 | " will replace "!" with the previously-executed command and chaos will 59 | " ensue. 60 | 61 | " Original content 62 | " Note there is very weird behavior when attempting to copy a line which 63 | " contains the literal character \n. Example: 64 | " 65 | " console.log('hello\nthere'); 66 | " 67 | 68 | if exists("g:vim_pbcopy_escape_backslashes") && g:vim_pbcopy_escape_backslashes 69 | " Global override is set and is truthy 70 | echom "[vim-pbcopy debug] forcing shellescape(escape(...))" 71 | return shellescape(escape(join(a:listOfLines, "\n"), '\'), 1) 72 | elseif exists("g:vim_pbcopy_escape_backslashes") 73 | " Global override is set and is falsey 74 | echom "[vim-pbcopy debug] forcing shellescape(...)" 75 | return shellescape(join(a:listOfLines, "\n"), 1) 76 | endif 77 | 78 | if s:isRunningLocally() 79 | " echom "[vim-pbcopy debug] shellescape(escape(...))" 80 | " return shellescape(escape(join(a:listOfLines, "\n"), '\'), 1) 81 | 82 | " Confirmed working on Mac OS X Yosemite 83 | echom "[vim-pbcopy debug] shellescape(...)" 84 | return shellescape(join(a:listOfLines, "\n"), 1) 85 | else 86 | " So far works on all Linux distros I've used. Assuming that when 87 | " Vim is not running locally it's because you're SSH-ing into a 88 | " Linux host. 89 | echom "[vim-pbcopy debug] shellescape(escape(...))" 90 | return shellescape(escape(join(a:listOfLines, "\n"), '\'), 1) 91 | endif 92 | endfunction 93 | 94 | function! s:sendTextToPbCopy(escapedText) 95 | try 96 | if s:isRunningLocally() 97 | " Call the UNIX echo command. The -n means do not output trailing newline. 98 | execute "silent !echo -n " . a:escapedText . " | " . g:vim_pbcopy_local_cmd 99 | else 100 | " Call the UNIX echo command. The -n means do not output trailing newline. 101 | execute "silent !echo -n " . a:escapedText . " | " . g:vim_pbcopy_remote_cmd 102 | endif 103 | redraw! " Fix up the screen 104 | return 0 105 | catch /E121/ 106 | " Undefined variable error 107 | echohl WarningMsg 108 | echom "Please set g:vim_pbcopy_remote_cmd in your ~/.vimrc with something like: 'let g:vim_pbcopy_remote_cmd = \"ssh hostname.example.com pbcopy\"'" 109 | echohl None 110 | return 1 111 | endtry 112 | endfunction 113 | 114 | function! s:copyVisualSelection(type, ...) 115 | let sel_save = &selection 116 | let &selection = "inclusive" 117 | let reg_save = @@ 118 | 119 | if a:0 " Invoked from Visual mode, use '< and '> marks. 120 | silent exe "normal! `<" . a:type . "`>y" 121 | elseif a:type == 'line' 122 | silent exe "normal! '[V']y" 123 | elseif a:type == 'block' 124 | silent exe "normal! `[\`]y" 125 | else 126 | silent exe "normal! `[v`]y" 127 | endif 128 | 129 | let lines = split(@@, "\n") 130 | 131 | " -- " Transform individual lines 132 | " -- let i = 0 133 | " -- while i < len(lines) 134 | " -- let lines[i] = s:getTransformedLine(lines[i]) 135 | " -- let i = i + 1 136 | " -- endwhile 137 | 138 | " -- " Transform entire list of lines 139 | " -- let transformedLines = s:getTransformedLines(lines) 140 | 141 | let escapedLines = s:getShellEscapedLines(lines) 142 | let error = s:sendTextToPbCopy(escapedLines) 143 | 144 | " Reset the selection and register contents 145 | let &selection = sel_save 146 | " let @@ = reg_save 147 | endfunction 148 | --------------------------------------------------------------------------------