Never Type the Same Path Twice
This is a follow-up of my
previous post on Vim and cwd
, so I suggest you go read it first.
If you’ve read the previous post carefully, you may have noticed that
the ultimate goal of all the shortcuts I’ve described (<leader>cd
),
(<leader>ew
) or the way I care about the working directory of each vim tab,
always boils done to one thing:
do not type the same path twice
Here are a few more tricks I use on top of the other vim settings I’ve previously described.
vim --remote
#
This is useful when you have two terminals open.
Here’s an example:
In this case, the working directory on the left is correct, but the
vim
instance running on the right was started from $HOME
Let’s assume you want to edit ~/src/dmerej/blog/content/post/hello.md
.
You don’t want to type ~/src/dmerej/blog/content/post/
again.
A solution is to use vim --remote hello.md
from the terminal on the left.
vim
instance was started using the --servername
option
Neovim #
Neovim
folks removed the --remote
and --servername
options.
The idea is that you should use the new RPC interface instead.
You can use a nvr or your own solution.
Personally, I have something like this:
# in vim_wrapper.py
SOCKET_PATH="/tmp/neovim"
def remote_nvim(filename):
nvim = neovim.attach("socket", path=SOCKET_PATH)
nvim.command(":e %s" % filename)
def main_nvim():
env = os.environ.copy()
env["NVIM_LISTEN_ADDRESS"] = SOCKET_PATH
rc = subprocess.call(["nvim"], env=env)
def main():
if "--remote" in sys.argv:
remote_nvim()
else:
main_nvim()
In a nutshell:
-
If
vim_wrapper.py
is called without a--remote
argument, I make sureNeovim
is always listening to the same socket (/tmp/neovim
) -
Otherwise I use the
neovim
Python client API to attach to a runningNeovim
instance and open the file there.
(More about vim_wrapper.py
later …)
Open recent files #
I use the Ctrl-P plugin.
It has a “Most Recent Used” mode that I find very convenient.
This means that most of the time, I start vim
from anywhere,
then run <leader>p
(which is bound to CtrlPMRUFiles
), and
only then do I set the working directory. (with <leader>cd
,
remember?)
Changing directory after Vim exits #
Often, when I’m done editing, I want to run some git
commands.
(Typically, git push
)
So I use this trick to change the working directory of the calling terminal.
First, I use an auto command to write the working dir in
an hard-coded file (/tmp/nvim-cwd
)
" Write cwd when leaving
function! WriteCWD()
call writefile([getcwd()], "/tmp/nvim-cwd")
endfunction
autocmd VimLeave * silent call WriteCWD()
Then I define a zsh function to call the vim_wrapper.py
script
and change the working directory accordingly:
# Change working dir when Vim exits
function vim() {
vim_wrapper.py $*
cd $(cat /tmp/nvim-cwd)
}
z
: or the cd
that learns #
But sometimes I first want to change working directory before running
vim
. (Typically, to run git pull
)
To do so I use z.
This tool installs a zsh
hook and store every working directory in
a “database”.
Then you can just type a small part of the directory you want to go to, and it will use a “frecency” algorithm to get you there.
See the README for more information.
Opening files from error messages #
Often, in error messages you get something like:
/path/to/foo.cpp:42: 'spam' was not declared in this scope
There’s the filename, followed by a colon (:
), followed by a line number.
Of course, you want to open the file in vim
to fix it.
You can try to carefully select only the filename without the
:42
part, or after having copy/pasted the full word, removing
the extra characters using backspace
.
And then you remember 42
and type 42G
(or :42
)
to go to the correct line.
Personally, I do this in the vim_wrapper.py
script:
It tries to see if there are column in filenames, and
then starts vim with the correct +
option:
def parse_filespecs_for_cmdline(filespec):
if ":" in filespec:
parts = filespec.split(":")
line = parts[1]
filename = parts[0]
return ["+%s" % line, filename]
Or, when used with the --remote
option, opens a new tab
and the move the cursor to the correct location:
def parse_filespecs_for_remote(filespecs):
res = list()
for filespec in filespecs:
parts = filespec.split(":")
parts += ["1"] * (3 - len(parts))
parts[0] = os.path.abspath(parts[0])
for i in (1, 2):
try:
parts[i] = int(parts[i])
except ValueError:
sys.exit("Failed to parse %s" % filespec)
res.append(parts)
return res
def remote_nvim(filespecs):
nvim = neovim.attach("socket", path=SOCKET_PATH)
to_open = parse_filespecs_for_remote(filespecs)
nvim.command(":tabnew")
for fullpath, line, column in to_open:
nvim.command(":e %s" % fullpath)
nvim.feedkeys("%iG" % line)
nvim.feedkeys("%i|" % column)
Conclusion #
That’s all folks :) Hope you liked it.
You can see the vim_wrapper
script in my
dotfiles repo on github
Thanks for reading this far :)
I'd love to hear what you have to say, so please feel free to leave a comment below, or read the contact page for more ways to get in touch with me.
Note that to get notified when new articles are published, you can either:
- Subscribe to the RSS feed
- Follow me on Mastodon
- Follow me on dev.to (mosts of my posts are mirrored there)
- Or send me an email to subscribe to my newsletter
Cheers!