0

Background

Vim uses ranges to specify which lines should be passed to a command. For example, :5,10 will use lines 5-10, :'a,'b will use the lines between mark a and mark b, and :'<,'> will use the lines that are selected in visual mode.

Question

Is there a way to do this with sub-line granularity? What if I want to use only part of a line instead of the whole line?

Example use case

I want to pass some text within my file into an external shell program. For example, I can use write_c (see :help w_c) to pass some text into cat, which will just print it out. Let's say I have the following file:

hello
this is my file
goodbye

and I put my cursor on the m. Then I use v to enter visual mode and fe to go to the next e. Now my visual selection is my file. If I then press : to enter command mode and type :w !cat (so the entire command is :'<,'>:w !cat), then I see the message this is my file, when I only wanted to see my file because the entire line that the visual selection was on got piped into cat.

How can I select just my file and pipe only that part into cat?

1
  • Here is a link to cross-reference the original source of the question.
    – nullromo
    Commented Feb 17, 2023 at 18:28

2 Answers 2

1

Short answer

You can't. Ex commands are always linewise and there is no way around that.

Long answer

You can achieve your goal, though, just not with :help :range! or any other Ex command.

You can start with a relatively straightforward method (that happens to fit your simple example):

vfe
y
:w !cat <C-r>"<CR> 

but it quickly becomes rather complex when you start to deal with multiline text, special characters, etc.. And then, you have to consider what the external command expects: does it need \n-terminated line? does it handle multiline? what about quoting and expansion? etc.

Here is the same example, but with some sanitization added:

:w !cat <C-r>=shellescape(getreg('"'))<CR>

This is starting to become a lot more involved than:

:'<,'>w !cat

and we are not doing anything even remotely useful with that text.

See :help system() for another even more involved approach.

1
  • Thanks for the help. I had tried the possibility of yanking and pasting, but I didn't understand shellescape until now. I was able to combine stuff to get a working solution, which I posted as a separate answer.
    – nullromo
    Commented Feb 17, 2023 at 23:59
0

Thanks to @romainl for the help. Here's a solution that uses yanking and seems to work well:

  1. y to yank the selection
  2. :!echo to start the command
  3. <C-r>=escape(substitute(shellescape(getreg('"')), '\n', '\r', 'g'), '#%!')<CR> to put the contents of the " register into the command line
  4. <Bar> cat to pipe the result into cat (for example)
  5. <CR><CR> to run the command

An example overall mapping looks like this:

vnoremap <C-c> y:!echo <C-r>=escape(substitute(shellescape(getreg('"')), '\n', '\r', 'g'), '#%!')<CR> <Bar> clip.exe<CR><CR>

Using this, I can map Ctrlc in visual mode to copy text to the clipboard using clip.exe in WSL.

So far this works for all the visual highlight cases I have tested:

  • normal text
  • multiline text
  • text including ', ", (, ), #, %, or !
  • visual block selections
  • text ending in a newline (including visual line mode selections)

So I'm happy with it for now. Hopefully there isn't some weird case I come across later that breaks it...

2
  • I did come across a weird case that broke it. % expands to the filename of the current buffer, so I couldn't copy that character. I updated the answer and added % to the list of escaped characters passed into the escape function to solve this issue.
    – nullromo
    Commented Feb 27, 2023 at 22:59
  • I also needed to add # to the list.
    – nullromo
    Commented May 17, 2023 at 16:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.