Switch colorscheme in terminal, vim and tmux from dark to light with one command
I've figured out a method to simultaneously toggle Solarized between light and dark for iTerm2, tmux, and Vim, with a single command from within Vim:
-
Set up two profiles in iTerm2's preferences, one with the Solarized Dark color scheme, and one with the Solarized Light color scheme. The color presets are very helpful here, since they include both versions of Solarized.
-
Add a keyboard shortcut to each profile to switch to the other (dark switches to light, light switches to dark). I used the same shortcut for both, so it just toggles between them. You can use different shortcuts if you want, you'll just need to make a small modification to a later step.
In case you're not familiar with the glyphs, that's Control-Option-Shift-s. Feel free to pick something different, but remember it, we'll need it later. And I'm only showing Solarized Dark here, but don't forget to set it up for both profiles.
Get yourself some tmux color schemes. I used the ones from this repository on Github. I'll refer to it's location on your computer as
~/tmux-colors-solarized
.Install Solarized for Vim via your preferred method. Since we've set the 16 ANSI colors to the Solarized values in the iTerm2 profiles, we can use the standard Solarized color scheme, rather than the degraded 256 color version.
-
Add a function to your
.vimrc
, and optionally a command:function! Solar_swap() if &background ==? 'dark' set background=light execute "silent !tmux source-file " . shellescape(expand('~/tmux-colors-solarized/tmuxcolors-light.conf')) else set background=dark execute "silent !tmux source-file " . shellescape(expand('~/tmux-colors-solarized/tmuxcolors-dark.conf')) endif silent !osascript -e 'tell app "System Events" to keystroke "s" using {shift down, option down, control down}' endfunction command! SolarSwap call Solar_swap()
Line-by-line...ish explanation:
function! Solar_swap()
Define a
function
namedSolar_swap
that takes no parameters()
. The!
on the end offunction
causes it to silently replace an existing function of the same name, handy if you end up sourcing your.vimrc
again.if &background ==? 'dark'
Simple check if Vim's background is currently dark.
==?
does a case insensitive comparison. The&
is necessary to use a setting (such asbackground
) in an expression.set background=light
Just setting Vim's background to light.
execute "silent !tmux source-file " . shellescape(expand('~/tmux-colors-solarized/tmuxcolors-light.conf'))
execute
the following string as an Ex (i.e.:
) command.silent
saves us from having to press return every time.!
runs an external (to Vim) command, in this casetmux source-file [path]
. The.
concatenates the expanded, escaped path on to the rest of the command string.else set background=dark execute "silent !tmux source-file " . shellescape(expand('~/tmux-colors-solarized/tmuxcolors-dark.conf')) endif
This chunk is basically just the light-to-dark version of the above lines.
silent !osascript -e 'tell app "System Events" to keystroke "s" using {shift down, option down, control down}'
silent !
is the same as before.osascript
will run some AppleScript for us.-e
means to execute the following argument as one line of a script. The line that we're running is one to send that keyboard shortcut from step 2 to the active application, which should, as far as the operating system is concerned, be iTerm2.1 You may need to modify the line to fit your choice of shortcut. Note that if you had two different shortcuts to switch iTerm2 you'll need to move this line up into theif
's branches, and change the keystrokes to match.endfunction command! SolarSwap call Solar_swap()
And finally, we close the function and define a
command
calledSolarSwap
, which calls the function we just made.!
silently replaces any existing command of the same name.Now, from within Vim, just run
:SolarSwap
and Vim, tmux, and iTerm2 should all toggle between Solarized Light and Dark. You can, of course, add a mapping to your.vimrc
to make toggling even easier.
1iTerm2 does have a proper AppleScript API of its own, however, I could not locate any means of directly changing the current tab's profile. Just ways to create new tabs, windows, or splits with a specified profile, or get the (read only) name of the current session's profile. Thus the roundabout path of setting up keyboard shortcuts and using them to switch profiles.
I'm struggling with this problem myself. I've landed on an imperfect, hacky semi-solution, but from what I've gathered, taming terminal colors is not exactly a trivial problem.
What I've found is that both tmux and vim borrow the 16 ANSI color values defined in your terminal color scheme (black, red, green, yellow, blue, magenta, cyan, white, and all their ‘bright’ variants). If in your light terminal color scheme, you invert the definitions of ANSI black & white (i.e., set black/brightblack as the light background colors and white/brightwhite as the dark foreground colors), tmux and vim will follow suit. As long as you're only using the light/dark variants of a single colorscheme, it should work decently.
As you can see in this screenshot, it's not perfect (front is terminal, rear is MacVim — note the differences in the airline text, as well as the illegible block of black near the middle, which is supposed to be light text on a dark background), but it gets 99% of the way there without having to touch any vim/tmux/vim-airline settings.
If you're looking for a way to change the scheme from outside of vim and automatically update vim's interface, see :h t_RB
. This answer references an update to the terminal query mechanism in vim version 8.0.1129, 2017.
If you want to use neovim after v0.4.0-239-g36378c33c
you can use a new "Signal" autocmd: autocmd Main Signal * call Solar_swap()
. Then just send neovim instances SIGUSR1
from your shell script to trigger the autocmd. For my use in linux, see my answer here.
I implemented a way to toggle between a light to dark colour scheme for both Vim and Tmux in one go with a simple shortcut (entered into either Vim or Tmux). I use Bash not Zsh though, but hopefully it helps someone. I don't have the rep to show images but I have it all in a blog post here.
In my case I can do either:
From a Tmux pane: 1a) The alias 'ol' switches both Vim and Tmux to light mode. 1b) The alias 'od' switches both Vim and Tmux to dark mode.
From Vim: 2) 'Leader-o' toggles both Vim and Tmux between light and dark colour schemes. dark example light example
The way it works is that I define a Tmux environmental variable that keeps track of whether we have a light or dark colour scheme. Anytime I switch, be it from Vim or a Tmux pane, the variable will be updated. Existing and new Tmux panes and existing and new instances of Vim will check this variable and follow the scheme.
Code implementation
Step 1: Switching from Tmux pane
First, I've configured my ~/.bashrc such that it will automatically launch Tmux and attempt to connect to a session called 'main', or create it if it doesn't exist. I find this works for me, but you may want to alter this step.
Once I've launched Tmux, I query the environment variable called 'THEME', if it's not equal to 'THEME=light' (or just doesn't exist), then we go with the dark theme. This means that when we first launch a Tmux session, we will default to a dark theme:
if command -v tmux>/dev/null; then
[[ ! $TERM =~ screen ]] && [ -z $TMUX ] && tmux new-session -A -s main
# check if we have been switched to light, else go dark
[[ ! $(tmux show-environment | grep THEME) =~ 'THEME=light' ]] &&
tmux set-environment THEME dark
fi
At the beginning of my ~/tmux.conf file, I start by sourcing a secondary Tmux file that contains the dark colours I've chosen. These values may get overridden by a light scheme later:
# source colorscheme
set -g default-terminal 'screen-256color'
source-file ~/.tmux_dark.conf
These are the dark theme colours I have:
# dark colours
# fg = thin line
set -g pane-border-style "bg=colour234 fg=colour244"
set -g pane-active-border-style "bg=colour234 fg=colour208"
# fg = text
set -g window-style 'fg=colour248,bg=colour234'
set -g window-active-style 'fg=colour252,bg=colour235'
# Customize the status line
set -g status-fg colour208
set -g status-bg colour234
In addition to ~/.tmux_dark.conf, I have ~/.tmux_light.conf:
# light colours
# fg = thin line
set -g pane-border-style "bg=colour253 fg=colour244"
set -g pane-active-border-style "bg=colour253 fg=colour208"
# fg = text
set -g window-style 'fg=colour238,bg=colour253'
set -g window-active-style 'fg=colour238,bg=colour231'
# Customize the status line
set -g status-fg colour208
set -g status-bg colour253
If I'm in a Tmux pane, and want to switch colour schemes, I just source the relevant file, and update the THEME variable. I have two aliases in my ~/.bashrc to do this. I remember them as ol for 'ON/Light', and od for 'OFF/Dark':
# switch between light and dark themes
alias ol="tmux source-file ~/.tmux_light.conf; tmux set-environment THEME 'light'"
alias od="tmux source-file ~/.tmux_dark.conf; tmux set-environment THEME 'dark'"
Step 2: Switching from Vim
In my ~/.vimrc, I've defined two functions, one to set the colour scheme and another to handle the reading and updating of the Tmux THEME variable. The colour scheme we pick is dictated by the Tmux THEME variable, which we read with a system call to Tmux. This returns the THEME variable, as well as a message saying 'Press ENTER or type a command to continue'. Obviously we're only interested in the variable. If the variable indicates we should be dark, we choose a dark colour scheme (in my case zenburn), otherwise we go with a light one (in my case seoul256-light). Importantly, since I'm checking a match with 'THEME=dark', we must take only the first 10 characters of message returned by the system call:
function! SetColorScheme()
" check if tmux colorsheme is light or dark, and pick for vim accordingly
if system('tmux show-environment THEME')[0:9] == 'THEME=dark'
colorscheme zenburn
else
colorscheme seoul256-light
endif
endfunction
Toggling the colour scheme between light and dark is done with a call to another function. The function also re-sources the appropriate Tmux colour scheme and updates the THEME variable. Once the THEME variable is updated, we call the SetColorScheme function above to change Vim's colours:
function! Toggle_Light_Dark_Colorscheme()
if system('tmux show-environment THEME')[0:9] == 'THEME=dark'
:silent :!tmux source-file ~/.tmux_light.conf
:silent :!tmux set-environment THEME 'light'
else
:silent :!tmux source-file ~/.tmux_dark.conf
:silent :!tmux set-environment THEME 'dark'
endif
:call SetColorScheme()
endfunction
We can create a mapping (or a command) to quickly toggle the colour scheme like so:
nnoremap <Leader>o :call Toggle_Light_Dark_Colorscheme()<cr>
When we open a new instance of Vim, the Tmux THEME variable will have already been sent, and so we choose the colour scheme using the SetColorScheme function (note that this must come after the SetColorScheme function in your ~/.vimrc):
call SetColorScheme()
We could stop there, but in the case where we have an instance of Vim running, and change the scheme using one of our aliases from withing in a Tmux pane, Vim won't automatically re-run the SetColorScheme function.
toggle in tmux, vim already open
We can use an autocmd to check and reset the colour scheme whenever Vim is re-focused. Unfortunately, this doesn't work for Vim in the terminal, but luckily there is a plugin that resolves it for us:
Plugin 'tmux-plugins/vim-tmux-focus-events'
This plugin requires the following line in your ~/.tmux.conf (or that you've installed the tmux-sensible plugin):
set -g focus-events on
With one of the above options (I just have the line in my ~/.tmux.conf), we can use the FocusGained event in ~/.vimrc:
autocmd FocusGained * :call SetColorScheme()
This will mean that as soon as you return to vim from the Tmux pane, Vim's colour scheme will update automatically:
light example
It would probably be easy to coerce Vim to constantly check the Tmux THEME variable, but I only switch from light to dark in the evening (i.e. once a day) so I don't want to make Vim do a million checks in the background for such a rare event:
One last remark is that I opted to change the colour of my command prompt to be visible under both colour schemes. In my ~/.bashrc I put 35m to specify a purple colour in bold font. How these numbers specify a colour is mysterious to me, so I just googled around a bit:
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;35m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\n\$ '
However, choosing colours in the ~/.tmux.conf file is easy with this code which can be pasted into the terminal to display a colour grid of the 0-255 range:
for i in {0..255} ; do \
printf "\x1b[48;5;%sm%3d\e[0m " "$i" "$i"; \
if (( i == 15 )) || (( i > 15 )) && (( (i-15) % 6 == 0 )); then \
printf "\n"; \
fi; done