Switching between projects in Neovim

I've historically used Vimr as my Neovim GUI. It's a pretty nice macOS application written in Swift that lets me open up multiple projects in separate windows and allows me to switch between those windows using Cmd-`.

My usual development workflow consists of always having two applications open: Kitty and Vimr. For each project, I have a terminal session open in a Kitty window and the source code open in a Vimr window. I constantly switch back and forth between the two using Cmd-Tab. And every now and then I need to switch between projects which is where the Cmd-` shortcut was quite handy.

The Problem

A few months ago, one of the plugins I use (Telescope) started using some functionality only available in Neovim 0.9+, while Vimr was based on 0.8.2. And it looked like the Vimr project had gone inactive, which meant that I couldn't update my plugins without breaking the whole thing. This in turn meant that the ability to switch between projects using Cmd-` (which is an essential part of how I work) was gone.

So I could either wait for the Vimr maintainers to push an update, or find a different way to switch between projects. I chose the second option. I replaced Vimr with Neovide and started looking for plugins that would let me switch between projects within a single Neovim session easily, essentially replacing Cmd-`.

Workspaces

One of the plugins I found was workspaces.nvim which lets you add, remove and switch between workspaces within a single Neovim session. There's also a Telescope integration so I could set up a keyboard shortcut that would pop up a list of all the available workspaces and let me switch between them easily.

This worked quite well. The only hiccup was that when I would switch between projects, all the open buffers from the older project would remain while Neovim's current working directory would be set to the one from the new project. This meant that every workspace switch involved manual work of closing the existing buffers and opening up new ones.

Luckily, the author of workspaces.nvim had written another plugin that helped me solve this problem.

Sessions

Vim/Neovim has a concept of sessions which represent the current state of your work (windows, buffers, etc.). The interface isn't the easiest to use as the native commands feel quite low level. But the author of workspaces.nvim wrote another plugin called sessions.nvim which provides a nice wrapper around the underlying interface.

After installing sessions.nvim, you can run SessionsSave and SessionsLoad to save and load session files. So with these two commands in place, what remained was to figure out how to hook up workspaces.nvim to save a session before a workspace switch and load a session right after.

Workspaces + Sessions

Workspaces.nvim allows for "hooks" which are functions or commands you can run when certain pre-defined events happen. In my case, what I wanted (roughly) was that when I switch from one project to another, Neovim should:

  1. save the state of the first project to a session
  2. close the existing buffers
  3. switch the current working directory to that of the second project
  4. refresh NvimTree to show the second project's file structure, and finally
  5. load a previously-saved session for the second project into the current window

It might sound like a lot of work but actually it was quite easy to represent in code using a combination of workspaces.nvim and sessions.nvim. The following snippet is what I ended up using:

require("workspaces").setup({
    hooks = {
        open_pre = {
            "SessionsSave",
            "silent %bdelete!",
        },
        open = {
            "SessionsLoad",
            "NvimTreeOpen",
        },
    }
})

This setup gives me exactly what I want. I had to give up Cmd-`, but I can now use Ctrl-k to bring up a list of available workspaces and switch between them easily.

The open_pre hook is run before a workspace switch, where we save the current state to a session and close the currently open buffers. And the open hook is run after a workspace switch, where we first load a previously-saved session and then update the NvimTree panel. The complete code containing the setup functions of the other plugins is available in my plugins.lua.


So far I'm quite happy with this solution. I really like that both workspaces.nvim and sessions.nvim provide a fairly minimal but functional API, particularly how workspaces.nvim hooks let you bring other plugins into the picture and enable cool things.

It's quite a pity for Vimr to not be active anymore. It's an excellent application and I hope the author would be able to spare some time again in the near future to bring it back. At the moment though, I'm quite happy with the switch to Neovide, and it's a joy to use the setup described in this post!