Vim's Arglist as a File-Centric Todo List
I often find myself wanting to make similar edits to a set of files within a
project. More than I can do with sed
, but simple enough that ag
+ vim
are
the perfect tools for the job. In the age-old tradition of using blog posts as
notes to one’s future self (and any other kind soul who happens to need the same
pile of hacks I stumbled upon), here we are.
Further below is an explanation of the different tools at play, but in short, this is the workflow:
- Open Vim with the desired files, e.g.,
vim $(ag -l "from 'react'")
- Edit the current file as needed using all the flexibility of Vim (macros, plugins, brute force edits all welcome here)
- When the edits for the current file are done, run the
:ThankYouNext
command (see below for the bit of Vimscript that makes that work) to move on to the next file. - Repeat from step 2 until all files are edited and you’re left with an empty Vim session.
Populating the Arglist
This workflow uses Vim’s arglist
feature, which allows us to manage a subset
of the open buffers as a distinct group. In our case, we can use it as a
file-centric todo list.
Starting from the command line, we can use any and all of our favorite tools
like find
, grep
, sed
, awk
, ag
(or ripgrep
or ack
or
whichever you search with), fd
, fzf
, etc. Here’s a handful of examples:
# Files matching a search
❯ vim $(ag -l "from 'react-dom'")
# Files w/ names matching a pattern
❯ vim $(fd '_spec.rb$' spec/)
# Files selected from fuzzy finding w/ fzf
❯ vim $(fzf --multi)
# Files with uncommitted changes
❯ vim $(git diff --name-only)
# Files modified on this branch
❯ vim $(git diff master --name-only)
# Files w/ failing specs (using RSpec's persisted failure file)
❯ vim $(grep 'failed' spec/examples.txt | awk '{print $1}' | sed 's/\[.*$//' | sort | uniq)
# Conflicted files in git
❯ vim $(git ls-files -u | awk '{print $4}' | sort -u)
# A list of files copied to our clipboard
❯ vim $(pbpaste)
When we open Vim by providing an initial set of files, Vim will load them into
the arglist and open the first file for editing. You can check the current state
of the arglist at any time by running :args
, with the output looking something
like the following:
[dir/file_a.txt] dir/file_b.txt dir/nested/file_last.txt
Many thanks to Drew Neil for the amazing Vimcasts series, in particular this video on the arglist which got me started down this path.
Note: While it’s possible to populate the arglist from within Vim, I found that approach to have a number of edge cases and instead I’ve had the best luck starting from the command line.
Other Note: All of the above examples use vim $(...)
style command
substitution as I think it looks a little more obvious, but thanks to a tip
from Rune Lausen I’ve learned that you can use ... | xargs -o vim
(where
...
is any of the above commands above like git diff --name-only
). This is
likely what I’ll end up using in my day to day as it makes it easier to iterate
on the file-finding commands, and then just tack on the | xargs -o vim
to pipe
the files to Vim.
Working Through the List
This is the novel part that I always find myself forgetting, which brings us to this very blog post. Once we’re done with the edits for a given file, we want to write the current file, remove it from the list and progress to the next one.
To wrap up the handful of Vimscript functions needed to do this, you can add the
following to your ~/.vimrc
:
function! s:ThankYouNext() abort
update
argdelete %
bdelete
if !empty(argv())
argument
endif
endfunction
command! ThankYouNext call <sid>ThankYouNext()
With this in place we can now use the new command, :ThankYouNext
, and we’ll be
taken to the next file or an empty Vim session to let us know we’re done.