Neovim has tab-local working directories now
published:
categories: vim
My Neovim PR to add tab-local working directories has recently been merged. In this post I will explain what it does and why you will love it.
What are working directories in Vim?
If you know Vim feel free to skip this section, otherwise read on. Vim is a terminal application and the directory you launch it from is the working directory for that session. Even if you use a GUI version of Vim you have a working directory which usually defaults to your home directory. Any time you issue an ex-command or call a shell command all paths will be interpreted as relative to the current working directory.
Take for instance the :make
ex-command. It will call your make program from
the working directory and that program will usually search for a sort of
makefile in that directory. Let's assume you have a C project in its own
directory and a makefile in that directory as well. If Vim's working directory
is the same as the project directory you can run :make
and have the project
built immediately.
This isn't limited to just :make
alone, any file navigation also depends on
it. If you have a file browser plugin like Netrw or NERDTree installed you can
just issue the ex-command :edit .
and browse your project directory. File
finders like Ctrl-P or Unite would have to go through a huge tree of completely
unrelated directories before they reach the sub-tree of your project. There are
many more examples, but the point is that your working directory should match
your project directory.
The motivation for tab-local working directories
Sometimes you need to work on more than one project at a time. Take this website: There is the website itself, which is one project, and there are the Jinja templates, which is another project. Sometimes I find a bug in the website and I have to switch to the template project. I could launch a new Vim instance, but the instances would be completely isolated. I couldn't transplant HTML snippets from one into the other without going through the system clipboard. Searches issued in one instance would not appear in the other, I would have to repeat them but that can be really annoying in case of complicated regexes.
The idea to implement this feature can from a Vim screencast by Drew Neil. In it for some reason his build of Vim does have separate working directories for each tab by default. He seems to think that's a regular feature, but if you take a look at the comment section you will see that it clearly is not. My best guess is that it was a bug, but even after working with the internals of Vim I cannot imagine how it could have happened.
There is a Vim plugin called vim-tabpagecd which makes all tabs have their own working directory by default. I was using it for a while, but giving every tab its own working directory every time seems overkill to me. So the only natural course of action was to implement it in Vim directly.
The :tcd
ex-command
I modified Neovim instead of Vim because that's the editor I actually use, but
if anyone wants to port this feature to Vim be my guest. Anyway, Vim already
has a similar feature in the :lcd
command, except that that command is
limited to just one window. I decided the call my command :tcd
; it's short,
easy to remember and it if you know :lcd
you also know :tcd
.
Working directories take precedence over each other with deeper ones overriding
higher ones. In practice this means that :tcd
overrides the global working
directory and that :lcd
overrides the tab-local working directory.
Bigger changes had to be made to the getcwd()
function. In order to not break
backwards compatibility the function will still work without any arguments, in
which case it returns the effective working directory. If you supply one
argument it is taken to be the number of the window in the current tab, so
you get that window's effective working directory. So far this is the same as
in Vim. However, if you supply a second argument it is taken to be the number
of a tab. In other words getcwd(3, 2)
returns the effective working directory
of window number 3 in tab number 2. You can always use the number 0 for the
current window or tab. Here is where it gets interesting: if you want the
working directory of a tab regardless what the working directory of the window
is you supply -1 as the number of the window. Even if the window has its own
local working directory it will be ignored.
The function haslocaldir()
works exactly the same as getcwd()
, except it
returns whether the working directory is local to that scope.
Even more scopes?
A major part of Neovim's missions statement is to bring the codebase of Vim into a better state. The tab-local working directory feature hasn't been just bolted onto the existing code, I have properly refactored the inner workings so that adding a new scope should take only a few lines of code from now on. One could for example add a "buffer" scope that takes precedence over tabs and affects all windows displaying that buffer. Of course such a scope would raise new design concerns and it's questionable whether it would even be useful, but the important point is that it is now possible.