Using Command Line Tools in Neovim

Running External Commands

Read Command Output into Buffer

:r !command                     " Read command output at cursor position
:r !ls                         " Insert directory listing
:r !date                       " Insert current date
:r !echo "Hello World"         " Insert text

Run Command on Current File

:!command %                    " % represents current filename
:!cat %                        " Show file contents in terminal
:!wc -l %                      " Count lines in file
:!python %                     " Run current Python file

Filter Buffer Through Command

:%!command                     " Filter entire buffer through command
:%!sort                        " Sort all lines
:%!uniq                        " Remove duplicate lines
:%!column -t                   " Format as columns

Filter Visual Selection

" 1. Select text in visual mode (V for lines, v for characters)
" 2. Type :
" 3. Command automatically shows: :'<,'>
:'<,'>!sort                    " Sort selected lines
:'<,'>!column -t               " Format selection as table

Filter Specific Line Range

:5,10!sort                     " Sort lines 5-10
:.,$!command                   " Current line to end of file

Working with JSON (jq)

Format JSON in Current Buffer

:%!jq '.'                      " Pretty-print entire buffer

Query Current File into New Buffer

Basic Pattern:

:enew | r !jq 'QUERY' #
  • :enew - Create new empty buffer
  • | - Chain commands
  • r !jq 'QUERY' # - Read jq output
  • # - Refers to alternate buffer (previous file)

Examples:

" Get all names
:enew | r !jq '.results[].name' #

" Get names and types
:enew | r !jq '.results[] | {name: .name, type: .type}' #

" Get unique names
:enew | r !jq '[.results[].name] | unique | .[]' #

" Create custom structure
:enew | r !jq '{results: [.results[] | {name: .name, type: .type}]}' #

Using Current Filename Explicitly

When # doesn’t work or you need more control:

:let filename = expand('%') | enew | execute 'r !jq ''.results[].name'' ' . shellescape(filename)

Breakdown:

  1. :let filename = expand('%') - Save current filename
  2. | enew - Create new buffer
  3. | execute 'r !jq ...' - Run jq with saved filename

Check JSON Structure

Before querying, understand the structure:

:!jq 'keys' %                  " Show top-level keys
:!jq '. | type' %              " Check root type (object/array)
:!jq '.[0] | keys' %           " Show keys of first element
:!jq '.results[0]' %           " Preview first result

Common jq Patterns

" Compact output (one line per object)
:enew | r !jq -c '.results[]' #

" Raw string output (no JSON quotes)
:enew | r !jq -r '.results[] | "\(.name): \(.type | join(", "))"' #

" Filter by condition
:enew | r !jq '.results[] | select(.legendary == true)' #

" Count results
:!jq '.results | length' %

" Get specific field from all items
:enew | r !jq '[.results[].name]' #

Custom Commands for jq

For init.vim

Add to ~/.config/nvim/init.vim:

" Format current buffer as JSON
command! JsonFormat %!jq '.'

" Query current JSON file (opens in new buffer)
command! -nargs=1 Jq let filename = expand('%') | enew | execute 'r !jq ' . shellescape(<q-args>) . ' ' . shellescape(filename)

" Compact JSON format
command! JsonCompact %!jq -c '.'

" Check JSON validity
command! JsonCheck !jq empty %

Usage:

:JsonFormat
:Jq '.results[].name'
:Jq '.results[] | select(.legendary == true)'
:JsonCompact
:JsonCheck

For init.lua

Add to ~/.config/nvim/init.lua:

-- Format current buffer as JSON
vim.api.nvim_create_user_command('JsonFormat', function()
  vim.cmd('%!jq \'.\'')
end, {})

-- Query current JSON file
vim.api.nvim_create_user_command('Jq', function(opts)
  local filename = vim.fn.expand('%')
  vim.cmd('enew')
  vim.cmd('r !jq ' .. vim.fn.shellescape(opts.args) .. ' ' .. vim.fn.shellescape(filename))
end, { nargs = 1 })

-- Compact JSON
vim.api.nvim_create_user_command('JsonCompact', function()
  vim.cmd('%!jq -c \'.\'')
end, {})

-- Check JSON validity
vim.api.nvim_create_user_command('JsonCheck', function()
  vim.cmd('!jq empty %')
end, {})

Practical Workflows

Quick JSON Analysis

1. :e data.json                              " Open file
2. :!jq 'keys' %                            " Check structure
3. :!jq '.[0]' %                            " Preview first item
4. :enew | r !jq '.results[].name' #        " Extract data
5. gg                                        " Jump to top

Format and Save JSON

:e messy.json
:%!jq '.'
:w

Compare Multiple Queries

" Open source file
:e data.json

" First query in vertical split
:vnew | r !jq '.results[] | select(.region == "Kanto")' #

" Second query in another split
:vnew | r !jq '.results[] | select(.legendary == true)' #

" Navigate between splits with Ctrl-w h/l

Extract Subset to New File

:e source.json
:enew | r !jq '{results: [.results[0:10]]}' # | w subset.json

Working with Other CLI Tools

Git Integration

:r !git log --oneline -5                   " Insert recent commits
:r !git diff %                             " Insert diff of current file
:!git blame %                              " Show git blame

Markdown/Text Processing

:%!pandoc -f markdown -t html              " Convert markdown to HTML
:%!fmt -w 80                               " Wrap text to 80 characters
:'<,'>!sort | uniq -c                      " Count occurrences in selection

CSV/Data Processing

:%!column -t -s','                         " Format CSV as table
:r !head -20 data.csv                      " Read first 20 lines

Code Formatting

:%!prettier --parser json                  " Format with Prettier
:%!black -                                 " Format Python (stdin)
:%!rustfmt                                 " Format Rust

Using Shell Substitutions

:r !ls %:h                                 " List files in current file's directory
:!wc -l %:p                                " Full path of current file

Modifiers:

  • %:h - Head (directory)
  • %:t - Tail (filename)
  • %:r - Root (without extension)
  • %:e - Extension
  • %:p - Full path

Advanced Patterns

Pipe Chain Multiple Commands

:%!sort | uniq | wc -l                     " Sort, unique, count
:%!grep "pattern" | sort                   " Filter and sort

Conditional Processing

" Only format if file is valid JSON
:if system('jq empty ' . expand('%')) == '' | %!jq '.' | endif

Read Command with Arguments

:r !curl -s https://api.example.com/data   " Fetch API data
:r !cat file1.txt file2.txt                " Combine files

Tips

  1. Test jq queries in terminal first - Build and verify before using in Neovim
  2. Use :!jq '.[0]' % to preview structure before extracting data
  3. The # buffer reference only works right after creating new buffer
  4. Use expand('%') when you need the filename in complex commands
  5. Chain with | to create multi-step workflows
  6. Visual select then : to automatically get '<,'>
  7. Create custom commands for frequently used patterns

Common Issues

Command Not Found

" Check if tool is available
:!which jq

" Use full path if needed
:%!/usr/local/bin/jq '.'

Quotes in Commands

" Use double quotes on outside, single inside
:!jq ".results[] | select(.name == \"Pikachu\")" %

" Or escape appropriately
:!jq '.results[] | select(.name == "Pikachu")' %

Large Output

" Limit output before reading
:r !jq '.results[0:100]' file.json

" Or use head
:r !jq '.results[]' file.json | head -50

Resources

  • :help :! - External commands
  • :help :r - Read command
  • :help expand() - Filename modifiers
  • :help shellescape() - Escaping for shell

Tags: #neovim #cli