Introduction
This chapter explores the many ways to open text files — or “edit text files” in Vim parlance — at startup and from Vim itself.
(more to come)
For argument’s sake
Like other text editors, Vim can be started without arguments:
$ vim
in which case it creates an empty buffer for your convenience.
Or with one or more explicit filename as arguments :
$ vim file1 file2 file3 $ vim *.js
in which case n buffers are created and named after their respective argument.
The buffers are stored in the global buffer list and the filenames are stored in the global argument list.
The two lists differ in many ways:
-
the argument list only stores filenames whereas the buffer list also stores cursor position and state,
-
it is possible to insert a new “argument” anywhere in the argument list but it is only possible to add a new item at the end of the buffer list,
-
adding an item to the argument list also adds an item to the buffer list but the opposite is not true,
-
…
The buffer list is best seen as a partial representation of Vim’s internal state. Not being very flexible makes it reliable.
The argument list is best seen as a transient list of files. It is certainly possible to use it for navigation but its flexibility makes it less than reliable. Generally, the argument list is better suited for other tasks such as:
-
adding a bunch of files to the buffer list in one go,
-
holding a temporary list of files for later processing,
-
keeping a number of “special” files on hand…
Reference
:help argument-list :help buffer-list
Editing a single file
That’s all good but… how do we open other files from Vim itself?
:edit
(shortened to :e
) is probably the most common way to edit a single file, whether that file exists on disk or not.
:e foo.txt :e ../bar.txt
Reference
:help :edit
Editing multiple files at once
:argadd
(shortened to :arga
) can be used to add multiple files to the argument list:
:arga file4 file4 :arga *.js
followed by :next
(shortened to :n
) to edit the first one of those new files:
:arga *.c :n
or the short form:
:arga *.c|n
If you don’t care about the argument list, :arg
can be used as a drop-in replacement for :argadd
+ :next
:
:arg *.c
Reference
:help :argadd :help :next :help :arg
Tab-completion
:e
, :arg
, and :argadd
cover a lot of ground already, but we will need fast fingers and a vast memory when we’ll need to edit files buried at the bottom of a large and complex directory structure.
Fortunately, tab-completion lets us cycle through eligible files:
Here are the default bindings:
-
<Tab>
selects the next item, -
<S-Tab>
selects the previous item, -
<Down>
enters a directory, -
<CR>
chooses the selected item, -
<C-d>
lists the completion items.
Pretty basic…
Reference
:help cmdline-completion
The wildmenu
But we are in the dark, here. We have no idea how large the completion list is unless we press <C-d>
every couple of keystrokes or if and how we could refine our query or how to leave the current directory! Basic tab-completion is better than nothing but meh…
Don’t worry! Vim has our back with a brilliant feature called “wildmenu” that temporarily replaces the status line with a handy menu:
Navigation is a lot easier now that we have an idea of where we are and what’s next. It is also easier to traverse directories with the new <Up>
binding. You can enable this feature for the current session with:
:set wildmenu
and for every further session by adding this line to your vimrc
, as usual:
set wildmenu
Reference
:help 'wildmenu'
Wildcards up our sleeve
Most of the time, though, the first two or three letters of a filename may not be enough… or we only remember the end. Or nothing at all beyond the extension! That’s where the “star” wildcard comes in handy, just like in your shell:
:e *fr<Tab> :e README_fr.txt
Vim even has a special wildcard called “starstar”, that makes it possible to recurse through subdirectories.
:e **/*use<Tab> :e app/controllers/user_management.js
“starstar” has two main benefits:
-
it allows us to navigate through a flat list instead of a potentially deep hierarchy, saving quite a bunch of keystrokes and brain cells in the process,
-
we can avoid typing subdirectories names.
Reference
:help starstar
Mappings
What if we could skip the pretty but too repetitive :e **/*
part?
“Macros” are a core aspect of the Vim experience. The name is most often associated with “recording” but they can also be used directly, with :normal
, or as part of a mapping. The principle is always the same, though: we give Vim a bunch of keys to “press” very quickly, expecting the same result as if we pressed those keys ourself.
Mappings are very important when it comes to customizing Vim, simply because they turn repetitive actions into near-instantaneous magic. Allowing us to save many thousands of keystrokes with minimal configuration.
Here is the anatomy of a mapping:
:map key action
where action
is what you want to happen when you press key
; it could be a macro, an Ex command, a function call…
The command used to define your mapping — map
in the example above — can be any of the following, try to be as specific as possible:
recursive |
non-recursive |
mode |
|
|
normal, visual, select, operator-pending |
|
|
command-line |
|
|
insert |
|
|
normal |
|
|
operator-pending |
|
|
select |
|
|
visual, select |
|
|
visual |
Here are a few examples for you…
-
Press
<F5>
to add quotes around the word under the cursor in normal mode::nnoremap <F5> ciw"<C-r>""
-
Press
<F6>
to call a function in normal mode::nnoremap <F6> :call MyFunction()<CR>
-
Press
<F7>
to execute a command in normal mode::nnoremap <F7> :MyCommand<CR>
-
Press
<F8>
to filter the current visual selection through 'uniq'::xnoremap <F8> !uniq<CR>
No need to worry about those examples for now.
Let’s go back to our current “problem”: we want Vim to type :e **/*
for us when we press <F6>
in normal mode. Well, the solution is pretty obvious:
:nmap <F6> :e **/*
Easy! We only have to do <F6>foo<Tab>
to list every file whose name contains foo
under the working directory and every subdirectory. Woohoo!
But what’s the deal with that *map
versus *noremap
distinction? It’s really quite simple…
-
nmap key command
means that pressingkey
in normal mode will executecommand
with its current meaning. This form is only useful when we want to use another mapping in our mapping; it is called “recursive mapping”. Example:" change 'b' to work like 'B' :nmap b B " '<F5>' works like 'dB', not like 'db' :nmap <F5> db
-
nnoremap key command
means that pressingkey
in normal mode will executecommand
with its default meaning. This form is usually the one we want, it is called “non-recursive mapping”. Example:" change 'b' to work like 'B' :nmap b B " '<F5>' works like 'db' :nnoremap <F5> db
Our mappings have to be solid because they will serve as the foundation of our workflow. Non-recursive mappings are thus the safest choice:
:nnoremap <F6> :e **/*
A leader worth following
While the whole purpose of the <Fx>
keys is to be “programmed” to do whatever specific function the user needs, they don’t fit very well with Vim’s other highly mnemonic bindings so it is certainly wiser to use a key combo that “maps” to the idea of editing. But we have a problem: Vim already uses most — if not all — of the freaking keys on our keyboard!
The “leader” mechanism allows us to define a <leader>
key (\
by default) that will work as a “mini-mode” of sort, or a “namespace” for our custom mappings. :help mapleader
gives us the following example :
:let mapleader = ","
which allows us to use the comma as <leader>
in all our mappings:
:nnoremap <leader>e :edit **/*
We are of course free to choose which key to use as our leader. <Space>
, for example, can be a more sensible choice because:
-
,
is a very useful key (repeat lastfFtT
in the other direction) with no alternative, -
<Space>
is synonymous withl
and<Right>
so it can safely be remapped, -
<Space>
is the largest key of the keyboard and it can be pressed with any of our two thumbs.
Let’s end this section by adding these lines to our vimrc
:
let mapleader = "\<Space>" nnoremap <leader>e :edit **/*
and try them out after sourcing our vimrc
again:
Neat!
Reference
:help mapping :help mapleader
:find
Vim comes with an often overlooked command fittingly named :find
that differs from :edit
in one big way: it can be set to visit specific directories.
The key to using :find
efficiently is to define a good value for the path
option that tells Vim where to find files. The default value may be a good starting point for C programmers but others can set it to a more generic — and simplistic — value:
:set path=.,**
which allows us to find files in the directory of the current file (the .
) and anywhere under the working directory, recursively, (the ) without needing to use
explicitly.
Or we can use a more project-specific value:
:set path=app/views/**,app/controllers/**
The sky is still the limit…
We can now use the :find
command as a slightly smarter replacement for :edit
:
:find foo<Tab>
versus:
:e **/foo<Tab>
But there’s a catch: like :edit
, :find
does its completion from the start of the filename so :find foo
will match foobar.txt
but not model_foo.txt
. Let’s add a wildcard for an even more useful completion:
:find *foo<Tab>
Here is :find
in action:
Reference
:help :find :help 'path'
More mappings
Customizing filename completion and the “wildmenu”
We can further customize the behavior of Vim’s filename completion with a bunch of options that work for :edit
and :find
, as well as many other commands:
:help wildmode " defines the behavior of the wildmenu :help wildignore " tells Vim to ignore some patterns :help wildignorecase " enables case insensitivity :help suffixes " sets pattern-based priority
Let’s go through them one by one:
wildmode
'wildmode'
defines the behavior of the wildmenu. You can tell Vim to show a list of completions or not but also when to show it. It is recommended to play with the many possible combinations until you find the right one.
The default value, full
, is pretty good, here is anotehr reasonably useful one:
set wildmode=list:full
wildignore
'wildignore'
serves the same purpose as .gitignore
and similar configuration files: patterns are used to tell Vim what files/directories to ignore when doing completion. Again, the right values depend on your actual needs.
Here is an example value that ignores tags
and cscope.out
files:
set wildignore+=tags,cscope.out
Note the +=
operator that allows us to add new values instead of redefining the whole thing every time.
wildignorecase
'wildignorecase'
is a more generic variant of 'fileignorecase'
; it allows this:
:e read<Tab>
to yield:
:e README.md
and is enabled with a simple:
set wildignorecase
suffixes
'suffixes'
is a mechanism that allows Vim to give low priority to files matching the defined patterns.
Example usage:
set suffixes+=.foo,.min.bar
Reference
:help 'wildmode' :help 'wildignore' :help 'wildignorecase' :help 'suffixes'
But I need a file explorer!
Sometimes, we just need to find our way in the deep and uncharted waters of a project that was started by the guy whom just left the company. We only have a rough idea of the structure of the project and choosing what to edit on the command-line can be less than fun, even with our shiny mappings.
Thankfully, Vim comes with Netrw, a full-featured (some say “bloated”) text-based file explorer that allows us to dig down that new project much like we would do in a graphical file explorer:
Here are a few default bindings for reference:
-
<cr>
open the file/directory under the cursor, -
-
go up one directory, -
o
open the file/directory under the cursor in a new window, -
P
open the file/directory under the cursor in the previous window, -
t
open the file/directory under the cursor in a new tab page.
And the three most basic commands:
-
:Ex
open a listing of the current directory, -
:Lex
open a listing of the current directory in a smaller vertical window, similar to the "project" pane common in other editors and IDEs, -
:Rex
come back to the previous listing.
Reference
Netrw’s documentation is massive and covers a lot more than what you probably need for basic exploration and file-handling but you should at least take a look at the following sections…
:help netrw-browse-maps :help netrw-quickhelp :help :Lexplore
Conclusion
Opening files for editing is neither complex nor hard but — as with everything in Vim — it can be made quicker and easier with a couple of settings and mappings. Make sure you have exhausted the built-in ways before installing the latest and greatest fuzzy gadget people rave about on Reddit, Twitter or Hacker News.
But, now that we have a bunch of files to edit, how are we supposed to work with them?