Skip to content

Instantly share code, notes, and snippets.

@romainl
Last active November 9, 2024 21:05
Show Gist options
  • Save romainl/379904f91fa40533175dfaec4c833f2f to your computer and use it in GitHub Desktop.
Save romainl/379904f91fa40533175dfaec4c833f2f to your computer and use it in GitHub Desktop.
The right way to override any highlighting if you don't want to edit the colorscheme file directly

The right way to override any highlighting if you don't want to edit the colorscheme file directly

Generalities first

Suppose you have weird taste and you absolutely want:

  • your visual selection to always have a green background and black foreground,
  • your active statusline to always have a white background and red foreground,
  • your very own deep blue background.

Your first reflex is probably to put those lines somewhere in your vimrc:

highlight Visual cterm=NONE ctermbg=76 ctermfg=16 gui=NONE guibg=#5fd700 guifg=#000000
highlight StatusLine cterm=NONE ctermbg=231 ctermfg=160 gui=NONE guibg=#ffffff guifg=#d70000
highlight Normal cterm=NONE ctermbg=17 gui=NONE guibg=#00005f
highlight NonText cterm=NONE ctermbg=17 gui=NONE guibg=#00005f

But it quickly appears that those lines have no effect if you source a colorscheme later in your vimrc so you move them below that colorscheme command:

colorscheme foobar

highlight Visual cterm=NONE ctermbg=76 ctermfg=16 gui=NONE guibg=#5fd700 guifg=#000000
highlight StatusLine cterm=NONE ctermbg=231 ctermfg=160 gui=NONE guibg=#ffffff guifg=#d70000
highlight Normal cterm=NONE ctermbg=17 gui=NONE guibg=#00005f
highlight NonText cterm=NONE ctermbg=17 gui=NONE guibg=#00005f

which gives the desired outcome. Everything is fine. In principle.

But you like to try new colorschemes, or you prefer to have different colorschemes for different filetypes or time of the day, and your overrides don't carry over when you change your colorscheme.

This is because colorschemes usually reset all highlighting, including your own, when they are sourced.

The solution is to override the desired highlights in an autocommand that's executed whenever any colorscheme is sourced:

augroup MyColors
    autocmd!
    autocmd ColorScheme * highlight Visual cterm=NONE ctermbg=76 ctermfg=16 gui=NONE guibg=#5fd700 guifg=#000000
                      \ | highlight StatusLine cterm=NONE ctermbg=231 ctermfg=160 gui=NONE guibg=#ffffff guifg=#d70000
                      \ | highlight Normal cterm=NONE ctermbg=17 gui=NONE guibg=#00005f
                      \ | highlight NonText cterm=NONE ctermbg=17 gui=NONE guibg=#00005f
augroup END

colorscheme foobar

Note that the autocommand will have no effect on previously sourced colorschemes so it must be added before any colorscheme is sourced.

Of course, we can go one step further and put all our highlights in a neat little function for maximum readability/managability/maintainability:

function! MyHighlights() abort
    highlight Visual     cterm=NONE ctermbg=76  ctermfg=16  gui=NONE guibg=#5fd700 guifg=#000000
    highlight StatusLine cterm=NONE ctermbg=231 ctermfg=160 gui=NONE guibg=#ffffff guifg=#d70000
    highlight Normal     cterm=NONE ctermbg=17              gui=NONE guibg=#00005f
    highlight NonText    cterm=NONE ctermbg=17              gui=NONE guibg=#00005f
endfunction

augroup MyColors
    autocmd!
    autocmd ColorScheme * call MyHighlights()
augroup END

colorscheme foobar

But I only want that override for a single colorscheme

The autocommands above use the glob * in order to override any colorscheme. If, say… you really like the colorscheme foobar but you can't stand its Visual highlight, you will want your autocommand to target that colorscheme specifically. To this end, you can use the name of that colorscheme instead of *:

autocommand ColorScheme foobar call MyHighlight()

or, if you want to target several colorschemes:

autocommand ColorScheme foobar,barbaz call MyHighlight()

But this doesn't work when I "auto-source" my vimrc

This won't work if you have an autocommand like the following in your vimrc because autocommands don't nest by default:

[...]
autocmd BufWritePost $MYVIMRC source $MYVIMRC
[...]

By default, autocommands don't trigger other autocommands. In this classic example, an autocommand is set to :source $MYVIMRC automatically when you write it. The problem, here, is that you are very likely to have a line like this one somewhere in your vimrc:

colorscheme whatever

which, when it is executed as part of the re-sourcing of your vimrc, won't trigger the ColorScheme autocommand because of that "no nesting" rule.

The solution is to add the nested keyword to that autocommand to allow other autocommands to be triggered by that event:

[...]
autocmd BufWritePost $MYVIMRC nested source $MYVIMRC
[...]

Reference

For all things concerning autocommands, see the aptly named :help autocommands. For all things concerning syntax highlighting, see :help syntax.


My Vim-related gists.

@openjck
Copy link

openjck commented Apr 11, 2020

Can you say more about nesting? I'm not sure I understand why it's needed here. The Vim documentation says that it's needed when an autocmd calls :e or :w.

@romainl
Copy link
Author

romainl commented Apr 11, 2020

@openjck, by default, autocommands don't trigger other autocommands. In the classic example I use in the "article", an autocommand is set to :source $MYVIMRC automatically when you write it. The problem, here, is that you are very likely to have a line like this one somewhere in your vimrc:

colorscheme whatever

which, when it is executed as part of the re-sourcing of your vimrc, won't trigger the autocommand described in the article because of that "no nesting" rule. Adding the nested keyword allows autocommands to trigger other autocommands and thus our ColorScheme autocommand to be triggered by the BufWritePost autocommand.

@openjck
Copy link

openjck commented Apr 12, 2020

@romainl That makes sense. Thank you for the detailed response!

@Ulver56
Copy link

Ulver56 commented Jun 16, 2020

Thank you!

@justrajdeep
Copy link

Thanks

@woobhurk
Copy link

Well, it works!
My highlight settings always override by other unknown settings, which makes me annoyed. Thanks very much!

@llinfeng
Copy link

llinfeng commented Nov 28, 2020

I find this solution shall fail to tune the TabLine colors when vim-airline is at play. Without offloading vim-airline altogether, specifying hi! link airline_tabfill VertSplit (or other highlight groups) serves as a variable way to tune the color for the TabLine highlight group.

@romainl
Copy link
Author

romainl commented Nov 29, 2020

@llinfeng I would see that as a sign of bad design on vim-airline's side, not as a failure of this method.

@llinfeng
Copy link

@romainl, I enjoyed reading about catching the ColorScheme triggering condition and reloading customized highlight groups when the color scheme changes. Yet, as with the TabLine highlight group (or the airline_tabfill from vim-airline), there are other cases where highlight groups are administrated by a third-party plugin. I also agree with your comment on the "bad design" aspect.

@YashKarthik
Copy link

thank you 🙏

@ri-cisse
Copy link

ri-cisse commented May 6, 2021

Thanks!!!

@Shfty
Copy link

Shfty commented Jun 26, 2021

Thanks!

@tornaria
Copy link

tornaria commented Jul 2, 2021

It seems

[...]
autocmd BufWritePost $MYVIMRC nested source $MYVIMRC
[...]

will add source $MYVIMRC to this autocmd each time this is sourced. This has exponential growth so it will get very slow very quickly.

I think it's better to do:

autocmd! BufWritePost $MYVIMRC
autocmd BufWritePost $MYVIMRC nested source $MYVIMRC

@romainl
Copy link
Author

romainl commented Jul 2, 2021

@tornaria, this gist is not really about autocommands or about that "trick" for re-sourcing one's vimrc (that I personally find silly) so I wrote it assuming the reader already handles autocommands properly in their vimrc, hence the [...]. The proper way would be:

augroup somename
autocmd!
autocmd BufWritePost $MYVIMRC nested source $MYVIMRC
augroup END

@kdarkhan
Copy link

If you cannot control the order of definition of your autocmd, and a colorscheme has already been loaded,
you can exec :doautocmd Colorscheme to retrigger the event.

I am using this piece of Lua code in Neovim, but should be possible with Vim as well (not sure about the proper way to do it with Vimscript):

        -- If colorscheme has already been loaded, trigger autocmds again
        if vim.g.colors_name == 'your-current-colorscheme' then
          vim.cmd('doautocmd ColorScheme')
        end

@romainl
Copy link
Author

romainl commented Dec 29, 2021

@kdarkhan, your snippet is sadly useless for Vim users. Consider posting an alternative that works in Vim. Also, consider explaining how your scenario could happen.

@kdarkhan
Copy link

Sure. I spent some time investigating this with vimscript. It will look like this

if exists("colors_name")
  doautocmd ColorScheme
endif

This is useful if you cannot ensure that your autocmd Colorscheme definitions are executed before colorscheme {your-colorscheme} is executed. In my case, I am trying to modularize my config, and there is a plugin which adds some custom highlights using autocmd Colorscheme.

I am keeping each plugin configuration independent from each other to make it more maintainable. That way, for example, if I decide to remove this custom highlight plugin, I will do the change only in a single place.

@romainl
Copy link
Author

romainl commented Dec 29, 2021

OK, thanks for your input. You are free to do what you want with your config but FWIW, modularisation of one's Vim/Neovim is rarely worthwhile. The fact that you end up in a situation that requires such a hack is kind of telling.

@kdarkhan
Copy link

Yeah of course, just wanted to share, maybe somebody will find this useful.

@adxl
Copy link

adxl commented Jul 22, 2022

Thanks good approach ! 🙏

@Arnie97
Copy link

Arnie97 commented Dec 17, 2022

Vimscript equivalent for @kdarkhan's Lua snippet:

if g:colors_name == 'your-current-colorscheme'
    doautocmd ColorScheme
endif

@unphased
Copy link

Just a note, even the autocommand may not work right on colorschemes like this one that do async stuff. For this one the quick way I solved it was to resort to forking it, editing the stuff that it messes up, and just pointing my plugin loader at my fork.

@wdog
Copy link

wdog commented May 15, 2023

thanks!

@xiaoqixian
Copy link

How does this work if I don't set any colorscheme? My use the colorscheme of my terminal.

@romainl
Copy link
Author

romainl commented Jul 31, 2023

@xiaoqixian, if you don't explicitly set a colorscheme, say with colorscheme malotru, then Vim uses its default colorscheme called default but the ColorScheme event is not triggered. Since this gist is based on autocommands and events, it can't work if the ColorScheme event is not triggered.

The solution is to set the default colorscheme explicitly:

colorscheme default

@dehidehidehi
Copy link

Thank you very much

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment