22

Something I've found useful in other editors is the ability to:

  • take the selected text
  • run an external command and pass the selection to its stdin
  • take the external commands stdout and replace the current selection with it.

This way you can write useful text tools which operate on the selection using any language that can do basic io.

How can this be done with vim using the selection? ... a single character, word, paragraph... etc.

(Directly in the command line, or via a key binding?)


Note

Commands such as !sort work on the line-level, the reason I'm asking this question is because I would like to operate on the selection. (i.e., the text that would be removed if x was pressed).

16

I find that often the easiest way is to use visual mode with v (or capital V to select whole lines), and select the text you want to pipe.

Then type:

:!cat

This doesn't do anything useful, as such. A useful command I use often is:

:!python -m json.tool

to format JSON.

You can also just type (without visual mode):

:<range>!command

For example, for the entire file:

:%!python -m json.tool

Or for the current line:

:.!python -m json.tool
  • Another really useful tool to use with this is the sort command. – Cody Poll Feb 4 '15 at 16:24
  • 4
    @CodyPoll What's wrong with Vim's :sort? – Martin Tournoij Feb 4 '15 at 16:25
  • 5
    @Carpetsmoker The external sort command has a lot more options and (depending on locale, etc.) produces a different order. – derobert Feb 4 '15 at 16:34
  • 1
    This doesn't work for selecting a single word in a line. (see question mentions word level selection) – ideasman42 Feb 5 '15 at 4:40
  • @ideasman42 Right ... I think I originally read this question as "lines or words" instead of "lines and words" ... In any case, doing this with words is non-obvious, UNIX programs work on lines, and Vim is no exception. I did some experimenting and sort of have a solution, but it doesn't work very well ... Maybe my approach is wrong, so I'll just leave it be for a few days and get back to it later. – Martin Tournoij Feb 13 '15 at 0:25
7

I wrote a plugin called express.vim that can help with this. The plugin defines an operator g= that lets you evaluate a VimScript expression over a motion (or Visual selection). It prompts for an expression, in which you can use v:val as a placeholder for the text covered by the motion (or, again, Visual selection).

In this case, a useful expression would use the system() function, which runs an external command.

system('tr a-z A-Z', v:val)

(Here I used tr as a simple example external command.)

The way to use express.vim for this problem would be to select the text with Visual mode, then type g= followed by that expression, then hit Enter:

g=system('tr a-z A-Z', v:val)<CR>

I'll admit, it's not easy to use. (In fact, I rarely use this plugin myself!) I'm considering adding a shortcut to invoke an external command, such as starting the expression with !. I'll post an update here if I end up doing that.

EDIT

I've modified express.vim to treat expressions beginning with ! as external commands. So, sticking with the tr example, the above becomes a lot simpler. After a Visual selection, type the following:

g=!tr a-z A-Z<CR>

Explanation:

  1. Invoke the express operator with g=
  2. Enter !tr a-z A-Z as the expression
  3. Press Enter
3

You are looking for the filter commands in Vim. See :help filter. Here is the relevant documentation:

!{motion}{filter}   Filter {motion} text lines through the external
                    program {filter}.
!!{filter}          Filter [count] lines through the external program
                    {filter}.
{Visual}!{filter}   Filter the highlighted lines through the external
                    program {filter} (for {Visual} see |Visual-mode|).
                    {not in Vi}

:{range}![!]{filter} [!][arg]               *:range!*
                    Filter {range} lines through the external program
                   {filter}.  ...[See documentation for details]

So typing 5!!sort will sort the next 5 lines starting from the cursor.

  • Is it possible to filter a single word? (not line level) – ideasman42 May 13 '16 at 8:25
  • Yes. The third option (visual mode) works for whatever is highlighted (words, partial line, multiple lines, rectangular block, etc.). – Sameer Jun 27 '16 at 18:19
  • 1
    The third option also filters entire lines. If you select a single word and filter it, for example, through tr [:lower:] [:upper], than vim will upcase not just this word but the entire line. – maria s Jan 26 '17 at 13:30
0

Note that it can be done with:

  1. visually select whatever you wish
  2. s^R=system('tr a-z A-Z', @")[:-2]^M (with ^R being CTRL+R, and ^M being ENTER)

It could also work with anything that does selection + replace like ci"^R=....

  • When running this on a multi line text selection (after pressing v and moving the cursor) I get the error E16: Invalid range\nE16: Invalid range\nE476: Invalid command (on Linux so tr and other basic commands exist). – ideasman42 Jan 26 '17 at 19:04
  • Have you pressed s that goes into insert mode or :s the command? – Luc Hermitte Jan 26 '17 at 21:26
  • Suspect not, but also not sure how you mean, perhaps the example could show how to bind this to a key? – ideasman42 Jan 26 '17 at 21:46
  • Do you hit directly s from the visual mode, or do you hit : first? There is no : involved in the sequence of keys I've written. A mapping would be: vnoremap µ s<c-r>=system('tr a-z A-Z', @")[:-2]<cr>. – Luc Hermitte Jan 27 '17 at 9:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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