1

Essentially I want to create an operator in a Vim script that sends selected text to an external (Python) script. It should behave like one would normally expect an operator to, whether the text is selected in Visual mode or using a movement. This has proven more difficult for me than I expected, and one fun limitation is that the operator needs to work on Vim versions from 6.3 to 7.0. I am using the console version of Vim, rather than GVim, as it's easier for my personal workflow.

My motivation: I want to copy text to the system clipboard from Vim (without using the mouse), so that I can paste it in another terminal tab. Frustratingly, on the systems I work with, Vim has been compiled without clipboard support, so the "+ and "* registers won't help me. It's easy enough to slap together a quick Python script to put arbitrary text into the clipboard, but the trouble is passing the text to the script from Vim.

What I've tried: My first thought was to yank the text, create a new buffer, put the text in there, save it as a file in /tmp, and execute a command like "cat /tmp/stuff | clipboard.py" from the function. More specifically: I used let scratchFile = system("mktemp") to create a temp file, badd to add the file as a buffer, and buffer to switch to the new buffer. I then put the text I'm operating on in the buffer, write it, then do silent execute "!cat % | clipboard.py" followed by bd to get rid of the buffer. This doesn't really work. (It screws up the way Vim draws the contents of the original file on the screen, such that I have to highlight the file in Visual mode to get the text to appear again. :redraw does not fix the problem.)

Is there a better approach to solving this problem?

Edit to add my script-in-progress:

nnoremap <C-y> :set operatorfunc=<SID>CopyClipboard<cr>g@
vnoremap <C-y> :<c-u>call <SID>CopyClipboard(visualmode())<cr>

function! s:CopyClipboard(type)
    let saved_register = @@

    if a:type ==? 'v'
        normal! `<v`>y
        let scratchFile = system("mktemp")
        execute "badd " . scratchFile
        execute "buffer " . scratchFile
        normal! P
        write
        silent execute "!cat % | clipboard.py"
        execute "bd! " . scratchFile
    elseif a:type ==# 'char'
        normal! `[v`]y
    else
        return
    endif

    let @@ = saved_register
endfunction
2
  • Does your Vim screen still get messed up if you temporarily comment out the 'silent execute "!cat % | clipboard.py"' line?
    – Heptite
    Commented Dec 8, 2011 at 5:47
  • Aside from the question I just posted, I have two suggestions. First, I don't think it's necessary to use both "badd" and "buffer," it should work just to use 'execute "edit " . scratchfile'. Or you might consider using a split window instead (change "edit" to "split"). Second, use "bwipe" instead of "bdel".
    – Heptite
    Commented Dec 8, 2011 at 5:51

2 Answers 2

1

:buffer will open new temp file instead of current buffer. Use writefile() function instead. It may help with redraw issues as well.

0

I would say there is more than one "right" way to do it, but your steps could possibly be simplified, depending on your implementation.

First of all, you can directly write a range of text to a file without having to :badd it. If you have text visually selected, just press : and it will automatically insert the appropriate range for you. Continue typing "w somefile". Vim will write to the file (creating it if necessary) only the lines specified by the range to that file. Then you can proceed to send the text to your external program.

Secondly, you can even write text directly to a pipe, instead of writing to a file and then calling a command, by using ":w !command". (Don't forget your range, where necessary.)

These two options, however, will have issues with Vim's handling of full lines, rather than partial, in ranges on the :-command line that you will probably encounter, plus the fact that it won't (easily) cover all of your stated and implied use cases, so I would say that your original approach may be more appropriate. Without seeing your actual implementation I cannot come up with a solid reason why it is messing up Vim's display—I have ideas, but they are harder to explain in the abstract than if there were examples to work with.

1
  • Thank you for the response. Yes, passing the range the way you describe was the first thing I thought, though I was unaware you could write directly to a pipe in that way. With line ranges grabbing whole lines at a time, it's not really appropriate for what I'd like to do. I have added my script to the original question.
    – user108471
    Commented Dec 7, 2011 at 14:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .