Easy navigation in native Vim

Introduction

In the days of fancy plugins super charging vim to make it look like a fully fledged IDE, I sometimes like to go back to the basics and (re)discover how vim often already has something builtin to do things, albeit maybe in a slightly different way.

Before LSP was a thing, tags were already there to help us navigate a codebase and we also have the :compiler command to run linters and populate the quickfix list with errors. Before fuzzy finders were all the rage, we already had :edit, :find and the argument list.

Exploring the file tree

When I explore a new codebase, I like to start by browsing the file structure with netrw to get a sense of the organisation. With :15Lex for example to get a window styled in the same manner as some other file tree plugins. Once I’m comfortable with the file tree structure I barely use netrw, hence why I feel I dont need an external file tree plugin. Here’s my config to make it look nicer (to my taste):

let g:netrw_banner = 0 | let g:netrw_browse_split = 4
let g:netrw_altv = 1 | let g:netrw_liststyle = 3
let g:netrw_list_hide = ',\(^\|\s\s\)\zs\.\S\+,.*\.swp$,.*\.un~$,.git,target'

Edit and find

One straightforward way to open a file is with the :edit command. Just type :e and start typing the filename, maybe prefixed by some wildcards, then you can hit <Tab> to see matching candidates and cycle through them. It can also be quite handy to hit <C-d> to list names matching the current pattern. I use it a lot to get a preview when I am unsure about some part of the file path.

Another possibility is to use the :find command, which allows you to search files in 'path'. One simple/naive way to set it up can be to :set path=**, but beware that it might be very slow on gigantic file trees, for example if your current directory is $HOME. One can easily come up with smarter ways of adding only relevant directories to 'path', specially inside a git repository. Indeed, we could do something like this:

" Fallback to using `find` when not in the root of a git repo
let path_cmd = isdirectory(".git") ? 'git ls-tree --name-only -d HEAD'
    \ : 'find . -maxdepth 1 ! -path "./.*" -type d'
exe "set path=,," .. systemlist(path_cmd)->join(",")

We could also make use of :h findunc, for example:

" Simple example function which could be improved by using a file cache
func! GitFind(cmdarg, cmdcomplete)
  let files = systemlist("git ls-files")
  if empty(a:cmdarg) | return files | endif
  return files->matchfuzzy(a:cmdarg)
endfunc

set findunc=GitFind

The power of the arglist

But I would like to suggest another option, which I find interesting: using the arglist. With the :args command, you can populate the arglist with a list of files, e.g. :ar src/gui_*.[ch], and navigate it with :next or :prev. Adding one simple command, such as:

command! -nargs=1 -complete=arglist Argedit edit <args>`

You can now easily edit any file in the arglist. You can even add a keybinding for quicker access if you want: nnoremap <your-key> :Argedit<space> Given this, what I like to do is use :arglocal to define different arglist for different windows (and therefore different tabs as well). Files are grouped together where it makes sense: documentation only, CI/CD, some particular module in the codebase, etc.

Now, it does not matter if you are working in a giant repository like the linux kernel, you can select what you want to add to the different local arglists and instantly navigate between files, instead of relying on a command that would scan through the entire codebase each time.

Another cool functionality of the arglist is to be able to execute commands on all files in the arglist, with :argdo. So dont forget to also have a look at :h argdo.

Side note, you can do something similar in the quickfix list with :cdo. For example, we can populate the quickfix list with some :grep search and perform substitution with something like :cdo s/<pattern>/<replace>/. Speaking of grep, we can search only the files in the arglist this way :grep /<pattern>/ ##.

Final thoughts

Some plugins claim to help you switch between files faster (harpoon?), but guess what, you can already do this with marks. Set a mark on a file with mA (replace A with any capital letter or digit of your choice), then you can instantly come back to this file with 'A or `A.

Once I am satisfied with the organisation of the files and windows I can save the session with :mksession, so I dont have to redo it next time I work on this code.

So in the end, I think plugins like fuzzy finders, file trees and buffer switchers mainly provide fancier UI, but most of the raw functionalities already exists in Vim.

 

GCGC

Small Adventures in the world of Computer Science, Machine Learning, and Computer Vision