DEV Community

Daniel Rotter
Daniel Rotter

Posted on • Originally published at

Use external programs like git in Neovim commands

I have been an enthusiastic (Neo)vim user for years now, and until today I love to improve my setup and often learn one or the other trick that makes me more efficient, even if it is just by a little bit. Therefore I want to start writing about that as well.

Recently I started using the command mode of Neovim a lot more, and do not stop at the most basic commands like :w to save a file or the infamous :q for exiting it (why would you want to exit such a great program anyway). What I find particularly interesting is the fact that you can use any command installed on the machine in the command mode by starting the line with a bang.

I usually have an instance of Neovim running within the awesome kitty terminal, along with a few other windows running shells. These other shells I use to run all kinds of commands, like git status, docker compose up, etc. I do not plan to get rid of those windows, since the main purpose of an editor is not to run commands, for which reason it can feel clunky sometimes. So while it is possible to execute ls in Neovim and get the directory listed, it does not feel right to me.

However, there are some commands, mainly those without any meaningful output at all, that are more comfortable to use from within Neovim. This is especially true for commands that require some input Neovim can deliver, like the path of the currently opened file, which can be inserted using the % placeholder.

E.g. I am still not using a GUI for Git, since I feel much more efficient this way. But staging a specific file using the git add command always felt a bit tedious, since the path has to be passed as an argument. Since this command makes use of the file path and has no output, I started to execute that command directly in Neovim instead of typing it into the shell, which was cumbersome even with tab completion.

To do so, I type the following when the file I want to stage is currently opened in normal mode:

:! git add %
Enter fullscreen mode Exit fullscreen mode

This way the currently opened file will be staged, without providing the entire file path! Doing this feels so natural that installing a separate plugin for Git handling almost feels like a waste of resources (at least if you only need it for adding files to the staging area).

There is another use case I am using quite often lately: If I am currently editing a test I usually want to execute it right afterward, ideally only this single test file instead of my entire suite. Most test runners support that by passing the path of the test file as an argument. But again, doing so felt quite tedious to me. Fortunately, that task can also be improved by using a Neovim command:

:! echo % | pbcopy
Enter fullscreen mode Exit fullscreen mode

Here the built-in echo command is used to output the current file path using the % placeholder to stdout, which will then be piped to stdin of the pbcopy command. pbcopy comes with macOS, and will put anything it gets via stdin into the clipboard. So instead of typing the command for my test runner and tediously passing the file path as an argument I can now only type the command of the test runner and pass the test file path by pasting it from the clipboard using command+v.

In addition, commands like that can also be used to alter the text in the editor. This is different from the uses before since commands need a meaningful output for that to make sense, but it shows how extremely powerful that concept is. An example of that is the following command, which inserts the current date using the date command from the operating system:

:read !date
Enter fullscreen mode Exit fullscreen mode

read is a Neovim command, that can be used to insert text at the current cursor position. And this read command can also be used in combination with any operating system command like date.

It is also not only possible to insert text, but also to manipulate existing text using such commands. Imagine you have a list of numbers in your editor, that you would like to sort. Instead of building some special functionality in Neovim, it is quite easy to use the already existing sort command. This can be done by marking the numbers using the visual mode (pressing v in normal mode) and then execute the following command (do not worry, the '<,'> will be inserted automatically):

:'<,'>! sort -n
Enter fullscreen mode Exit fullscreen mode

This shows that it even is possible to pass any arguments to the shell commands.

I love this about this entire ecosystem since I always get excited when different systems can be combined so easily. Do you have any other ideas on how to use this approach?

Top comments (4)

pbnj profile image
Peter Benjamin (they/them)

There is also :h filter.

If you press !! in normal mode, you will notice the vim command-line turn into :.! and wait for additional input:

  • The . means "current line"
  • The ! means "execute external program"

Putting both of these things together means the current line in the buffer is piped to an external program and the output is piped back/written into the current line in the buffer.

You can also press !<motion> in normal mode, like !ip, which will be translated to :.,.+n! (where n is the number of lines in your paragraph; if you have 3 lines in your paragraph, it will be :.,.+3!).

This is a more robust version of :'<,'>!.

This is also useful for when you want to execute the current line in an external program.

To put it all together, given a shell script like this:


# 1. get the current user
# 3. print date
# 2. list files


Enter fullscreen mode Exit fullscreen mode
  • Place the cursor on line 3, press !ip, type sort, hit enter. This will sort the comments to be in correct order
  • Place cursor on line 7, press !!, type sh, hit enter. This will execute whoami and write the output back into the buffer.
  • Place cursor on line 9, press !ip, type sh, hit enter. This will execute both ls and date in external shell and write the output back into the buffer.
danrot90 profile image
Daniel Rotter

So far I've only used the visual mode instead of motions for filters, but depending on the situation this might be the easier thing to do. Thanks for mentioning it!

pbnj profile image
Peter Benjamin (they/them)

Running external programs to modify the current buffer is one of the main features I use vim for.

You can also take :! to the next step by wrapping it in a Vim command.

For example, instead of typing :! echo % | pbcopy every time, you can wrap it in a command like:

command! CopyFilename :! echo % | pbcopy
Enter fullscreen mode Exit fullscreen mode

Now, you can just run :CopyFilename.

You can also take it another step by binding it to a key map, like:

nnoremap <leader>cf <cmd>CopyFilename<cr>
" or
nnoremap <leader>cf :CopyFilename<cr>
Enter fullscreen mode Exit fullscreen mode
danrot90 profile image
Daniel Rotter

Yeah, creating your own Vim commands is also something really nice :-) Whether or not I do that depends a bit on my personal intuition. E.g. for the git command I would not like to do that, because I prefer to use them in the exact same way as on the command line, this way I don't have to remember two different ways of doing the same thing 🙂