├── 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 |
--------------------------------------------------------------------------------