Toggle light and dark mode in Vim, Tmux and Alacritty

A tale of wanting nice things on Linux for Vim, Tmux and Alacritty and how it ended in a filthy shell script.

Toggling dark and light mode on Vim, Tmux, Allacrity and Firefox
Toggling dark and light mode on Vim, Tmux, Allacrity and Firefox

Out of the shadows

For the majority of the time I use a dark theme. Throughout the winter months I have been under the blankets bingeing on Nordic Noir and dark drama but now it is time to get outside into the English spring and listen to high bpm techno the birds.

The problem with a dark themed desktop of course is that it is hard to see the screen in any kind of sunlight. Much like a black t-shirt it is just no good for a really hot day. I use the terminal for most of the time with Vim Neovim, Tmux and Alacritty. I run Arch Linux with Sway as my window manager. Nothing completely crazy.

So I want to be able to toggle to light mode, expose myself to sunshine for five minutes, toggle back, before I run back inside. I don’t need this for too many applications. Literally all I use is:

This can’t be too hard right?

Firefox

Firefox is the easiest application as it honours GTK preferences.

# Light mode
gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
# Dark mode
gsettings set org.gnome.desktop.interface color-scheme 'prefer-mode'

This works great for Firefox and the theme is switched based on your preference and the browser will respect prefers-color-scheme css media queries for the sites that you are browsing. Increasingly sites are using this technique to offer light and dark modes based on the operating system preference but you may also need to use something like the Dark Reader extension, which thankfully also follows the GTK preference.

/* Light Mode */
@media (prefers-color-scheme: light) {
}
/* Dark Mode */
@media (prefers-color-scheme: dark) {
}

Alacritty

Alacritty can live reload configuration on changes with the following configuration file setting.

live_config_reload: true

With this in place we I can import a color theme that matches the preference, save the file and Alacritty will switch themes.

import:
  - ~/.config/alacritty/themes/catppuccin/catppuccin-mocha.yml

We can do this faster with sed to switch the theme and also make this task scriptable.

sed -i 's/catppuccin-mocha/catppuccin-latte/' ~/.config/alacritty/alacritty.yml

Tmux

As far as I know Tmux doesn’t have offer any apis, either external or internal for setting a colour scheme dynamically. There is no live reload of configuration either so this is going to be harder.

We can use a similar to technique to Alacritty though, where a theme is included as part of the configuration and this is modified via sed. In the tmux.conf file a file is inclued to set the colours to the dark theme.

source-file /home/go/.config/tmux/catppuccin-mocha.conf;

We can then use sed to switch this to a theme with light colors.

sed -i 's/catppuccin-mocha/catppuccin-latte/' ~/.config/tmux/tmux.conf

What happens? Nothing. We need to reload the tmux config.

tmux source-file ~.config/tmux/tmux.conf

Vim

Vim is the most difficult. Nvim offers a remote RPC interface that looks useful but I would like whatever I use to be compatible with old skool vim, probably because I am over 21.

I always run my terminal sessions in tmux and this is one of the reasons why. Tmux offers apis that support composition of terminal windows and this is a perfect example.

We can use the tmux api to get a list of panes across windows and sessions and inspect the command being run.

tmux list-panes -a -F '#{pane_id} #{pane_current_command}'
%0 nvim
%2 hugo
%3 tmux
%6 nvim
%7 vim

Mini fist pump. We are going to make this via a disgusting hack! So now we can use some UNIX tools to filter out what we need need before we pipe to xargs.

tmux list-panes -a -F '#{pane_id} #{pane_current_command}' |
grep vim |
cut -d ' ' -f 1
%0
%6
%7

Great. We have the pane ids that we want to address. Using tmux’s send-keys we can craft a message that switches all of the instances of (n)vim, across sessions and windows to use dark mode.

xargs -I PANE tmux send-keys -t PANE ESCAPE ":set background=dark" ENTER

So putting it all together.

tmux list-panes -a -F '#{pane_id} #{pane_current_command}' |
grep vim |
cut -d ' ' -f 1 |
xargs -I PANE tmux send-keys -t PANE ESCAPE ":set background=light" ENTER

One issue here is that this is a temporary setting so if you open another instance of vim you will get whatever is set in your configuration.

To get round this you will need a default setting in either your ~/.vimrc or wherever you choose to manage this in your fancy lua neovim config.

set background=dark " Set dark background in Vimscript
vim.opt.background = "dark" -- Set dark background in Lua

So now we can update this value using our friend sed.

sed -i 's/dark/light/' ~/.vimrc

Making a script

Please don’t hate me for this. I know unmaintainable shell scripts are bad and I really don’t want to be doing this. If you think you can improve this, or know a better way send me a PR. I’d love to delete this post.

#!/bin/sh
# Toggle dark and light themes for firefox, tmux, alacritty,
# and (neo)vim. Either run it from a shell or add a keybinding
# in tmux / alacritty

LIGHTTHEME="catppuccin-latte"
DARKTHEME="catppuccin-mocha"
VIMCONF="${XDG_CONFIG_HOME}/nvim/lua/config/set.lua"
ALACRITTYCONF="${XDG_CONFIG_HOME}/alacritty/alacritty.yml"
TMUXCONF="${XDG_CONFIG_HOME}/tmux/tmux.conf"
CURRENT_MODE=$(gsettings get org.gnome.desktop.interface color-scheme)

# Function to switch theme in n(v)im panes inside tmux
switch_vim_theme() {
  theme_for_vim_panes="$1"
  tmux list-panes -a -F '#{pane_id} #{pane_current_command}' |
    grep vim | # this captures vim and nvim
    cut -d ' ' -f 1 |
    xargs -I PANE tmux send-keys -t PANE ESCAPE \
      ":set background=${theme_for_vim_panes}" ENTER
}

# Toggle logic based on current mode
if [ "$CURRENT_MODE" = "'prefer-dark'" ]; then
  gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
  sed -i "s/${DARKTHEME}/${LIGHTTHEME}/" "$ALACRITTYCONF" "$TMUXCONF"
  sed -i 's/dark/light/' "$VIMCONF"
  switch_vim_theme "light"
else
  gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
  sed -i "s/${LIGHTTHEME}/${DARKTHEME}/" "$ALACRITTYCONF" "$TMUXCONF"
  sed -i 's/light/dark/' "$VIMCONF"
  switch_vim_theme "dark"
fi

tmux source-file "$TMUXCONF"

Conclusion

Switching themes is way too hard on Linux. GTK and CSS media queries both offer a sane approach but this is only relevant to GTK applications and web pages. As there is no cross platform standard for this, application developers face the prospect of supporting multiple approaches within different operating systems, window managers and frameworks. For most open source maintainers of course this is not something they want to spend their time on.

So here I am in the garden a couple of hours later staring at a bright screen as the sun hits my face. Wouldn’t it be really nice to have a cross platform, cross framework approach to this for graphical and terminal developers? Time to go dark and go inside.

Further reading

Tags

Can you help make this article better? You can edit it here and send me a pull request.

See Also