Singleton Neovim for Sway Workspaces

September 1st, 2021

This post documents how to configure Sway and Neovim so that you have exactly one instance of Neovim per workspace. This gives you a workflow that is similar to using vs code, where only one editor is spawned. If all these names don't mean much to you, this post will likely not be very useful.

The code and configuration in what follows is very much coupled with my personal Arch Linux setup, so unless you have the exact same setup, a few things will have to be adapted. To make sure that you have the context right, let me start listing the relevant tools I'm using.

  • Arch Linux my linux distribution of choice, known to come with a minimal set of features by default.
  • Sway a 'tiling' compositor for Wayland. It follows the same philosophy as the i3 window manager. This means it's designed to be controlled primarily using your keyboard rather than your mouse.
  • Neovim a modern fork of the vim editor. Just like Sway it's meant to be controlled using the keyboard.
  • LunarVim a distribution of plugins for Neovim that leverages the new treesitter and lsp functionality in neovim.
  • Neovide a neovim graphical fronted, which uses google's C++ library SKIA for rendering. Neovide itself is written in Rust.

At the time of writing I'm using the following versions:

$ sway --version
sway version 1.6.1
$ uname -r
5.13.10-arch1-1
$ nvim -v
NVIM v0.5.0
Build type: Release
LuaJIT 2.0.5

Features: +acl +iconv +tui
See ":help feature-compile"

   system vimrc file: "$VIM/sysinit.vim"
  fall-back for $VIM: "/usr/share/nvim"

$ neovide -V
Neovide 0.8.0

This is a setup I've been using for a bit more than a month now. This means it's not entirely 'in my fingers' yet. Before I was using Spacevim, which is an awesome project that made working with vim less of a pain. Though the release of Neovim 0.5 with support for language servers and integration of treesitter really marks a point in the history of vim. LunarVim takes advantage of these new features and modern (new) neovim plugins and gives you the power of vs code within a native vim environment. On top of this LunarVim is much quicker to start up than Spacevim.

Sway I started using a bit later and has so far been an interesting experience. The road towards a keyboard centric desktop is bumpy, there's much to configure, learn and discover. This blogpost describes one episode on this road. One of the downsides when using neovim is that I often end up with many editors scattered over my desktop. When using vs code you can have one instance of code open and open your files in that instance. I want the same for sway and neovim.

The principal building block to enable this workflow is the following launch-script. If you have some basic experience with bash, this should be straightforward to understand.

#!/bin/sh
# ~/bin/nvide

# get the path to the sway socket.
SWAYSOCK=/run/user/`id -u`/sway-ipc.`id -u`.`pgrep -x sway`.sock

# we retrieve the workspaces as json data and parse the id of the focused workspace using jq:
wkspcid=$(SWAYSOCK=$SWAYSOCK swaymsg -t get_workspaces -r | jq '.[] | select(.focused == true) | .id')

# we configure the path to the nvim socket, this allows us to check if nvim is running/listening on the current workspace.
# but also allows us to communicate with neovim (so that we can send files to it.)
NVIM_LISTEN_ADDRESS="/tmp/nvim_socket_"$wkspcid 

# apply realpath to the input arguments, so that relative paths get transformed into absolute paths.
files=$(realpath $@)

if [[ ! -a "$NVIM_LISTEN_ADDRESS" ]]; then
  # socket exists -> nvim must be running
  # launch our editor with swaymsg
  # in this case I launch neovide configured to run lunarvim, but the same concept should be applicable to other frontends.
	swaymsg "exec NVIM_LISTEN_ADDRESS=$NVIM_LISTEN_ADDRESS neovide  --wayland-app-id nvide_$wkspcid $files  --  -u ~/.local/share/lunarvim/lvim/init.lua --cmd 'set runtimepath+=~/.local/share/lunarvim/lvim'"
else
  # neovide should already be running, because we've set the wayland app id 
  # we can identify the corresponding window using it and move the focus to this window. 
  swaymsg "[app_id=nvide_$wkspcid] focus"
fi

After putting this file in ~/bin/nvide (and adding ~/bin to my path). I can easily launch neovide by typing nvide. When neovide is already running on the currently focused workspaced, sway will move the focus to nvide. It's now trivial to bind it to key-combination in the sway configuration so that you can easily summon your editor, without having to type 'nvide' each time:

bindsym $mod+n exec ~/bin/nvide 

Opening files with nvim-remote

The script above allows to open files when launching neovide by passing the files as arguments. However when neovide is already running on the current workspace, the passed paths are ignored. To open files in a running neovim instance we can use the neovim-remote project. By using the NVIM_LISTEN_ADDRESS environment variable again, we can point neovim-remote to the neovim instance associated with the currently focussed workspace.

SWAYSOCK=/run/user/`id -u`/sway-ipc.`id -u`.`pgrep -x sway`.sock
wkspcid=$(SWAYSOCK=$SWAYSOCK swaymsg -t get_workspaces -r | jq '.[] | select(.focused == true) | .id')
NVIM_LISTEN_ADDRESS="/tmp/nvim_socket_"$wkspcid 
NVIM_LISTEN_ADDRESS=$NVIM_LISTEN_ADDRESS nvr --remote my_file.md

When integrated into the launch script, this becomes:

#!/bin/sh
# ~/bin/nvide

SWAYSOCK=/run/user/`id -u`/sway-ipc.`id -u`.`pgrep -x sway`.sock
wkspcid=$(SWAYSOCK=$SWAYSOCK swaymsg -t get_workspaces -r | jq '.[] | select(.focused == true) | .id')
NVIM_LISTEN_ADDRESS="/tmp/nvim_socket_"$wkspcid 
files=$(realpath $@)

if [[ ! -a "$NVIM_LISTEN_ADDRESS" ]]; then
	swaymsg "exec NVIM_LISTEN_ADDRESS=$NVIM_LISTEN_ADDRESS neovide  --wayland-app-id nvide_$wkspcid $files  --  -u ~/.local/share/lunarvim/lvim/init.lua --cmd 'set runtimepath+=~/.local/share/lunarvim/lvim'"
else
  swaymsg "[app_id=nvide_$wkspcid] focus"
  # tell neovim to open the file.
  NVIM_LISTEN_ADDRESS=$NVIM_LISTEN_ADDRESS nvr --remote $files
fi