20

I am looking for a possible solution for applying two different background colors inside a single vim buffer depending on context like in this sublime text example.

One use case of that is to color code snippets inside markup files differently so they stand out more.

However I have never seen an example with a setup like that.

Is this at all possible in vim?

9

Because I was curious how well this would work, I've hacked something together that sort of works.

enter image description here

As mentioned in the comments on the earlier answer, the only way to do this is to fill up regions with spaces; which is exactly what we do; before write, we remove these spaces, so you shouldn't be bothering anyone else with it.

Note that this example is very specific for the markdown filetype!

Side effects:

  • Copying text will also copy a lot of spaces
  • Using $ and End no longer work as expected (it goes to column 80), and keys such as j and k also behave different.
  • The background is not displayed when 'list' is enabled
  • Completely blank lines don't work, you need to manually add a tab or 4 spaces
  • ... Perhaps more?

First, you need to add this to your ~/.vim/after/syntax/markdown.vim:

syn clear markdownCodeBlock                                                 
syn region markdownCodeBlock start="    \|\t" end="$"                       
hi def markdownCodeBlock ctermbg=230 guibg=lightyellow                      

You can adjust the colours to your liking, of course ;-)

Then, add this to your vimrc:

fun! MarkdownBlocks()                                                       
    fun! s:fill(line)                                                       
        " Remove all trailing whitespace                                    
        let l:line = substitute(a:line, " *$", "", "")                      

        " Add trailing whitespace up to 'textwidth' length                  
        return l:line . repeat(' ', (&tw > 0 ? &tw : 80) - strdisplaywidth(l:line))
    endfun                                                                  

    " Get all lines in a list                                               
    let l:lines = getline(1, line('$'))                                     

    " Map s:fill() to the lines that are a code block                       
    call map(l:lines, 'v:val[0] == "\t" || v:val[:3] == "    " ? s:fill(v:val) : v:val')

    " Reset the buffer to the lines                                         
    call setline(1, l:lines)                                                
endfun                                                                      

" Remove all the trailing spaces                                            
fun! MarkdownBlocksClean()                                                  
    let l:save_cursor = getpos(".")                                         
    silent %s/^\(    \|\t\)\(.\{-}\)\( *\)$/\1\2/e                          
    call setpos('.', l:save_cursor)                                         
endfun                                                                      
au BufWritePre *.markdown call MarkdownBlocksClean()                        

" Set spaces on loading the file, leaving insert mode, and after writing it 
au FileType markdown call MarkdownBlocks()                                  
au InsertLeave *.markdown call MarkdownBlocks()                             
au BufWritePost *.markdown call MarkdownBlocks()                            

I'm not going to explain the code line-by-line, the comments should make the general gist of it clear ;-)

  • Thanks for this answer. I accepted it instead of the initial answer given by @Rich because it seemed more complete. I will try it out but not sure if I will be using it because of all the side effects so no promise here :) Big Thank You for all the effort! – Karolis Koncevičius Feb 25 '15 at 17:43
  • 1
    @KarolisKoncevičius Thanks. I added it to my vimrc aswell. It seems nice :-) Let me know if you encounter problems. – Martin Tournoij Feb 25 '15 at 18:00
  • Hahaha. Well I think this qualifies as hacky, but it's not as horrible as I expected. Nice job. Of, course, now that you've gone this far, don't you feel compelled to fix the problems with j, k, $, etc. by remapping them, and to remove the white space before yanking? ;) – Rich Feb 25 '15 at 21:32
  • 2
    @Rich Yeah, it's a hack. That made it fun to do ;-) Fixing some of the keys (somehow) crossed my mind as well, but I already spent quite a bit of time on this, and want to first see if it actually works at all, and if I even like it, before spending even more time on it :-) – Martin Tournoij Feb 25 '15 at 21:34
  • 2
    @Carpetsmoker Well the last time I vaguely hinted that something might be possible you went away and made it, so I look forward to seeing your robust multi-language solution in another couple of weeks. – Rich Feb 26 '15 at 8:14
13

It's certainly possible to use a different background colour for syntax-highlighted elements. Just define the guibg and ctermbg colours in your hilight command. The former sets the background colour for GUI Vim, and the latter for terminal Vim.

However, this has the important limitation that it can only set the background colours for characters that actually exist in the file.

The result of this is that the background colour cannot extend past the end of the text on a line to the edge of the window, so the colouring of the code blocks shown in your example is not possible:

An example of the background colouring possible in Vim

You can also change the background colour of entire lines using the Sign feature. (See linehl in :help sign.txt)

An example of colouring the background with signs

However, note that:

  1. Doing so requires writing code to place signs on each line that needs to be coloured and keep these updated as the file's contents change,

  2. By default, when placing a sign, the sign column will be displayed at the left of the window. It is possible to change the colouring of the sign column with the SignColumn highlight group, and in more recent versions of Vim it can be removed entirely. (See :help 'signcolumn'.)

For example, to adapt Carpetsmoker's solution to use the (more robust) sign mechanism, you could do the following:

" Define a highlight group and a sign that uses it
highlight default markdownCodeBlock ctermbg=230 guibg=lightyellow
sign define codeblock linehl=markdownCodeBlock

" Use signs to highlight code blocks
function! MarkdownBlocks()
    function! s:applySign(idx, val)
        if a:val[0] == "\t" || a:val[:3] == "    "
            let l = a:idx + 1
            execute "sign place " . l . " line=" . l . " name=codeblock file=" . expand("%:p")
        endif
    endfunction

    " Remove old signs
    execute "sign unplace * file=" . expand("%:p")

    " Get all lines in a list
    let l:lines = getline(1, line('$'))
    " Add new signs
    call map(l:lines, function('s:applySign'))
endfunction

" Set signs on loading the file, leaving insert mode, and after writing it
au FileType markdown call MarkdownBlocks()
au InsertLeave *.markdown call MarkdownBlocks()
au BufWritePost *.markdown call MarkdownBlocks()

This simplifies the code somewhat, and has fewer caveats than Carpetsmoker's version.

@ChristianBrabandt's DynamicSigns plugin makes using the Sign feature for this purpose easier: he describes how use it to do so in this answer.

  • 1
    +1 for an answer. However (and correct me if I am wrong) but guibg only works for gvim, is that right? – Karolis Koncevičius Feb 5 '15 at 14:35
  • 1
    @Karolis Yes. For terminal Vim you need to use ctermbg, as stated. – Rich Feb 5 '15 at 14:38
  • 1
    If you use folds, then you get what you want: The folds have a different bg colour, however, this is accomplished by filling the background with space characters which have a different bg colour set... This causes some side-effects: copy/paste won't properly work anymore (as it will also copy a lot of space characters, this is also how you can verify this for yourself). – Martin Tournoij Feb 5 '15 at 15:11
  • @Rich, thanks for the answer. While it is not a perfect solution (in term of highlighting blocks) it fully answers the question demonstrating that this is possible. Answer Accepted. – Karolis Koncevičius Feb 6 '15 at 13:32
  • 3
    Someone would have to be really crazy to write a script like that. – joeytwiddle Feb 26 '15 at 8:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.