Macos Vim Color



February 15, 2021

这里使用的配色方案是Solarized,Solarized 是目前最完整的 Terminal/Editor/IDE 配色项目,几乎覆盖所有主流操作系统(Mac OS X, Linux, Windows)、编辑器和 IDE(Vim, Emacs, Xcode, TextMate, NetBeans, Visual Studio 等),终端(iTerm2, Terminal.app, Putty 等)。. I'm using the macOS's Terminal.app to run MacVim in Terminal mode. I've experimented with:colorscheme peachpuff, since many recommends that color scheme, which comes with MacVim by default. In the picture below shows how peachbuff looks, when running using mvim in Terminal mode (left) and GUI mode (right).

I love dark mode. It makes reading text comfortable for me. Because I’m working remotely for a company with a large timezone difference, most of the time, this also means I’m working during the evenings. Initially, I was manually changing my light and dark modes in macOS. Apple later released an “Auto” mode, which would switch to dark and light based on your location’s time.There is one caveat, though.

It only works for GUI applications.

If you’re like me, using shell applications, such as Tmux, Vim, etc., it won’t work for you. I’ve started using a terminal when I was 17 years old. Since then, I never asked myself, “why does the terminal have a dark background?”. I just took it for granted.

Last week, when I had to increase my screen’s brightness, I’ve figured out that I was using a pitch-black terminal screen and all my applications (Vim, Alacritty, etc.) had dark backgrounds. So, I asked myself, “what if I use a light theme during the day and switch back to a darker theme later in the evening?”.

There are already light color schemes for Vim, Alacritty, and most of the popular applications.

  • In Vim, if your color scheme supports both a light and dark mode, you switch between by using the command: set background=dark or set background=light.
  • In Alacritty, you can define multiple color schemes and switch between them easily in the config file alacritty.yaml. Alacritty doesn’t have an API, though, but there are ways to emit an event. I’ll explain in a bit.
  • Tmux doesn’t use many colors usually unless you add a status bar or change the pane borders. Just like Vim or Alacritty, you can define the status bar colors in the config file, which is tmux.conf

I had a rough plan on how I wanted to tackle this issue:

Macos
  • Pick up a popular color scheme with both dark and light modes and pleasant to the eyes.
  • Implement the light and dark modes for each application separately.
  • Find a way to change the modes programmatically. I.e., I should be able to change Vim’s color mode from outside Vim.
  • Create a script called change_background that would change all applications modes from light-to-dark or dark-to-light
  • Run a daemon process that would listen to macOS “Appearance changed” events and call the change_background script.

Let me go over this list one by one and explain how things have evolved. I will share code snippets throughout the blog post, but my setup is open source and can be found in my GitHub dotfiles repo.

TL;DR; here is a demo of the final work:

Color theme

I’m a huge fan of the Molokai color theme. I even forked it and modified it for my liking. Some issues with the Molokai theme are 1. It’s not maintained anymore 2. It doesn’t have a light theme.

I had to find a new color theme that is well maintained and has excellent light and dark colors.

I checked many themes over the weekend with multiple dark mode options (solarized, gruvbox, papercolor, ayu, etc.). Eventually, I decided on gruvbox (for now at least). This color theme is community maintainedand is decent-looking.

.vimrc

Vim

One main issue I had with gruvbox was the pastel colors, which decreases the contrast quite a bit. Luckily, it has a dark_contrast and light_contrast options to increase the contrast. I set both to hard, which is more pleasant to read.

Vim

Now that we have a color scheme, we can easily change it inside our vimrc. There are plenty of blog posts that explain how to switch between light and dark mode in Vim. But for me, I had several more criteria:

  • It needs to be automatic.
  • It needs to be fast.

All the solutions I’ve seen so far didn’t meet these criteria.

  1. Automatic switch between the light and dark mode usually doesn’t exist. The function that checks whether to enable or disable a particular mode is only sourced when starting a new Vim session. If you have multiple Vim sessions open in various windows, they won’t change. You have two close and re-open all sessions.
  2. One way to solve this is to set a background job via start_timer. You can pass a callback, which will be called every nth second. That way, the function can check whether it’s time to switch the mode. The issue with this is, it’s affecting the performance in the long term, and you can feel it when you’re using Vim.

So, how do we solve it? We could make sure to receive events from within Vim and then automatically make the changes. This way, the event will automatically update any open Vim session. It’ll also be very fast because there will be no job running a background while using the editor (more on this later).

In Vim, we can use the autocmd setting. It’s a setting where you can listen to specific events and then trigger a function call. There are many events, such as BufEnter (after entering a buffer) or FocusLost (Vim lost input focus). One particular event that is useful for us is SigUSR1:

The SIGUSR1 signal can be sent to an application via the kill command. Usually, people use it to kill processes, but as some of you already know, the kill command is also used to signal a process. That means, if we find the PID of a running Vim process and send a SIGUSR1 signal, Vim can capture and trigger function for us. This is how we’re going to detect the processes and send the signal (macOS):

pgrep is a tool that finds the pid of a process by its name. Because we might have multiple Vim sessions running in our Terminal, we’ll be iterating over it in a for loop. Finally, we send the SIGUSR1 signal to each process.

How does Vim catch this signal?

First, we create the command that changes the Vim theme. And then we also add an autocmd for the SigUSR1 event (somehow Vim decided to call the event SigUSR1 instead of SIGUSR1) :

This script was my first solution. However, I later discovered, until you focus on each Vim buffer, the callback is never called. It only changes the background if I’m using Vim or switch to any of my Vim instances.

Macos Vim Highlighting

This solution won’t work for us. We need to send a command to Vim, but Vim doesn’t have a proper API (or an RPC interface) we could use. It comes with a feature called clientserver, but it’s not a widely used feature and only works with specific OS’s. (nit: looks like NeoVim solved this already).

Luckily, I’m using tmux already and had an idea on how to fix it. Tmux has a command called send-keys where you can send keystrokes to a particular pane. What if we could find all Vim instances and safely call the ChangeBackground function?

That’s what we did finally. Here is the tmux script (written in Fish) that finds all the Vim instances and then calls the ChangeBackground() function:

Tmux

For tmux, I used the https://github.com/edkolev/tmuxline.vim plugin. Once sourced, this plugin automatically changes your Tmux status bar based on your Vim color scheme. It has support for pretty much all color schemes. Once you configure it in your vimrc, it automatically changes the tmux status bar colors when you open Vim. It also supports dark mode.

There is one problem, if you don’t have any open vim instance and change the background to dark, it means that tmux's background will never change. To overcome this problem, we’re going to use the command :TmuxlineSnapshot [file]. The command saves the file into a set of tmux directives, which you can put into tmux.conf or source separately. I called this twice, for both the light and dark mode of my Vim theme:

After that, I can easily enable the tmux configuration by calling the tmux source-file command:

Alacritty

Alacritty has support for defining multiple themes. So, you can define two themes, one being dark and another one to be light. However, the problem with Alacritty is, it doesn’t have an option or a command to change the theme on-the-fly. But as we did so far, there is also a hacky solution for this.

.vimrc

First, we can emulate “listen to events” for Alacritty by enabling the option to reload the config if it detects a change automatically. The option is:

Once it’s set, we can now make changes to our configuration (which also contains the color schemes). The next step is to let alacritty know that we want to source a second configuration file (which will only have our color schemes):

Finally, here is the color scheme I’m using (it’s long, you can see the full file here, so I’ll show an excerpt):

The colors field works like a switch. It’s a YAML reference, and the name should match one of the words inside the schemes.

Here is the trick, we’re going to change the colors field with a script. And because live reload is enabled, it’ll automatically detect the change. Here is the script I wrote (using sed) to achieve this:

With this script, I can now easily switch my theme with the following command:

Putting everything together

Now that we know how to change the mode of all the terminal applications (and the Terminal itself), we can easily put them into a single script. I’m calling this script change-background.fish, and here is how we put it all together:

Vim

I can use this script in two modes. If I don’t pass any arguments, it’ll read the value from the macOS preferences and change all the themes accordingly. Assuming it’s during noon if I run the following command in my Terminal:

It will change all my terminal themes to light mode (because we assumed it’s noon). Another way of using this script is to pass an argument explicitly:

This command will change the background mode and the themes of my Terminal to dark mode. Because it also changes the global OS setting, all the GUI applications also will switch to dark mode.

Now that we have a single switch to change the Terminal from dark to light (or vice versa), how can we automatically switch it? One idea I had is to write a script that would poll the global OS setting (via: defaults read -g AppleInterfaceStyle). If it were dark, we would call change_background dark otherwise change_background light.

Mac Os Vim Color Scheme

The problem with that is, it wouldn’t be instantaneously, and it would periodically have to run the command. I wanted it to be automatic and listen to an event that macOS would fire.

After searching for it a bit, I talked to my friend Bouke due to his low-level system experience. He immediately showed me a Swift script that would check for events. But then he decided to make it even simpler and released a Swift script that one can easily compile and run in the background via launchctl. The repo is called dark-mode-notify.

Macos Vim Colorscheme

All you have to do is to compile the app that listens to dark-mode events:

And then use this new binary in a launchctl script with our change-background function we created:

As you see here, we’re passing the arguments fish -c change_background to the dark-mode-notify app. Launchctl runs in the background, and then the rest is now handled via dark-mode-notify. Whenever we change the background, dark-mode-notify will receive an event and then call our fish -c change_background arguments, which changes our Terminal themes.

And finally, here is again a demo of how it looks like when the appearance changes in macOS:

Verdict

As you see, I can’t say it was an easy task. Most of the scripts we wrote are hacky and rely on certain things (tmux sending actual keystrokes to Vim, alacritty reading the configuration file and making changes, etc.). These could be improved if I would switch my editor (Vim → NeoVim) or Terminal (Alacritty → Kitty) because these applications have a proper API, and we could programmatically change the backgrounds. But for now, I’m happy with what we have.

Also, I think macOS terminal applications could listen to the dark-mode notify events and automatically switch the theme, just like how GUI applications change currently. But I don’t have hope this will happen soon, so this is the best I can achieve for now.

Vim Color Setting

If you have any questions or feedback, please feel free to share them with me on Twitter: @fatih