💾 Archived View for dmerej.info › en › blog › 0008-vim-cwd-and-neovim.gmi captured on 2024-08-25 at 00:03:16. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2022-07-16)
-=-=-=-=-=-=-
2016, Apr 30 - Dimitri Merejkowsky License: CC By 4.0
This is quite a long post on the topic of working directory and (Neo)vim.
There will be a second part to this story, and maybe a `TLDR;` if you ask nicely :)
`cwd` is short for "current working directory".
Every command you run has its own current working directory. When you start a terminal emulator, your first `cwd` is your home directory (`/home/user`), and then you can use `cd` to change the working directory.
At any time you can display your working dir by typing `pwd`, and usually your prompt is configured to give you this information.
Here the prompt is configured to display the working directory between square brackets:
[/home/user] $ pwd /home/user [/home/user] $ cd /foo/bar [/foo/bar] $ pwd /foo/bar
Setting the working directory allows you (among other things), to type relative paths instead of full paths.
For instance, let's assume you have some `C` code in `/path/to/foo/src`, you need to edit the source code for `bar` and its header.
You could run:
[/home/user] $ vim /path/to/foo/src/bar.c [/home/user] $ vim /path/to/foo/src/bar.h
But it's much more convenient to use:
[/home/user] $ cd /path/to/foo/src [/path/to/foo] $ vim bar.c [/path/to/foo] $ vim bar.h
Vim is no different. When you start vim, it gets the working directory of your shell. And then you can type commands like `:e` to open paths relative to your working directory.
Using the same example, after:
[/home/user] cd /path/to/foo/src [/path/to/foo] vim
you can run `:e bar.c`, and then `:sp bar.h`
This is all well and good, but what happens when you start working on **several** projects ?
For instance, you could be working on the HTML documentation of your project, in `/path/to/foo/doc`.
You need to see the `.html` and `.css` files when you are editing the documentation, but also sometimes you want to have a look at the actual code.
An obvious solution is to create a new tab, with `:tabnew doc`, but then if you want to edit `index.html` you have to type `:e ../doc/index.html`.
An then if you want to edit the CSS you have to run: `:vs ../doc/style.css`
So you have to keep typing `../doc/` and it's annoying.
I've got this issue for **years**. It's taken me a long time to find a solution for this problem, so I thought I'd share this process with you.
Vim has an option for this. Here's the documentation:
'autochdir' 'acd' boolean (default off) global When on, Vim will change the current working directory whenever you open a file, switch buffers, delete a buffer or open/close a window. It will change to the directory containing the file which was opened or selected. Note: When this option is on some plugins may not work.
That was my first try.
I think it's not a good solution (and not only because it's what Emacs does this by default :P)
Here's why.
Let's assume your project became more complex, and you start having a subproject called `baz`.
Here's what your source code looks like:
<foo> src bar.h bar.c baz baz.c doc index.html baz baz.html
When you are editing `bar.h`, you can type `:e baz/baz.c` and it feels natural.
But then, if you want to go back from `baz/baz.c` to `bar.h`, you have to use `:e ../bar.h` which feels strange...
Worse, let's assume you have:
/* in baz/baz.h */ #include <bar.h>
You may want to open `bar.h` by using `gf`, or auto-complete the path to the header using `CTRL-X CTRL-F`, but you can't since you don't have the correct working directory!
Plus the doc says it may break some plugins...
Vim has a command to change the working directory as well.
So back to our example, you can do:
:cd /path/to/foo :cd src :e bar.c :e baz/baz.h :tabnew :cd ../doc :e index.html
Well that's much better! There's still a problem though: `:cd` changes the working directory for the whole vim process.
So if you run `tabprevious` to go back editing the `C` code, your working directory is no longer correct, and you have to re-type `:cd src`.
Luckily, vim has a command to change the working directory just for the current window: `:lcd`. So I started using that.
And then I realized I often started vim directly from my home directory, so I had to type things like:
:e /path/to/foo/src.c # Ah, I need to change the working directory... :cd /path/to/foo/
That's awful. You type the same path twice!
Or I used to type:
:cd /path/to/foo/src :e foo.h # Time to fix the doc :tabnew ../doc :cd ../doc # Ah crap, I meant :lcd ...
I don't recall how I found it, but here's what has been in my `.vimrc` since quite some time:
" 'cd' towards the dir in which the current file is edited " but only change the path for the current window map <leader>cd :lcd %:h<CR>
Explanation:
You can see the full list of filename modifiers with `:help filename-modifiers`, and to use them from vimscript you can use the `fnamemodify()` or `expand()` functions.
Well, that's much better. You can start opening a long path, and then change the working dir without retyping all the path components.
Also, you are always using `:lcd`, so you never change the path globally.
This quickly became **the** shortcut I could no longer live without...
This is another trick you can use when you know are going to edit a file that is "near" the file you are currently editing, but don't want to change the working directory at all.
The code looks like this:
" Open files located in the same dir in with the current file is edited map <leader>ew :e <C-R>=expand("%:p:h") . "/" <CR>
Explanation:
Here's how you use it
:e /some/long/path/to/foo.c <leader>ew foo.h # opens /some/long/path/to/foo.h
After a while, I realized I really liked having one working directory per tab. I found myself typing stuff like:
:tabnew /some/path :e some-path-at-the-top <leader>cd :e subdir/otherfile.c
I was opening a file at the top of the project **just** to be able to use my `<leader>cd` command and start thinking of better ways.
So finally I came up with a new command:
" Change local working dir upon tab creation function! TabNewWithCwD(newpath) :execute "tabnew " . a:newpath if isdirectory(a:newpath) :execute "lcd " . a:newpath else let dirname = fnamemodify(a:newpath, ":h") :execute "lcd " . dirname endif endfunction command! -nargs=1 -complete=file TabNew :call TabNewWithCwD("<args>")
Hopefully by now you should understand what this does: I create a function that calls `fnamemodify` to get the dirname of the file I want to open in a new tab, and then calls `:lcd` with the correct argument.
And then I switched to Neovim[1], and two things happened:
That's **exactly** what I needed!
So now my vimrc looks like:
function! OnTabEnter(path) if isdirectory(a:path) let dirname = a:path else let dirname = fnamemodify(a:path, ":h") endif execute "tcd ". dirname endfunction() autocmd TabNewEntered * call OnTabEnter(expand("<amatch>"))
So no matter how I enter a new tab, my working directory is automatically set to the correct location, and when I switch tabs I switch working directories too. Perfect!
To conclude my journey, I'd like to share just a tiny bug fix I had to do for using vim-fugitive
It occurred when I was working in a git project, with the Python code in `python/`, and the documentation in `doc/`.
I was in my `python/` tab, ran `:Ggrep` and suddenly I got results for **all** the files in the repositories, including the documentation.
That's because `:Ggrep` starts by changing the working directory to the top directory of the current git repository.
Fortunately the fix was easy, here's my pull request on github[2].
2: https://github.com/tpope/vim-fugitive/pull/787
I don't know if it will be accepted by Tim Pope, but I hope that now you understand why this is such a big deal for me :)
Thanks for reading!
----