13

I'm still learning Vim and making small customizing on it. I'm struggling a bit to create a custom style/syntax highlight which would highlight the whole todo line. So in the picture instead of having just the "TODO" colored, I would like the whole line to be colored, or at least everything after the word "TODO".

enter image description here

Now, I made some attempts myself by applying this SO-post answer: https://stackoverflow.com/questions/4097259/in-vim-how-do-i-highlight-todo-and-fixme

and added the answer's rules to my Vim theme's file in .vim/colors/myTheme.vim:

syn match   myTodo   contained   "\<\(TODO\|FIXME\):"
hi def link myTodo Todo

Well this did not highlight the ":" like in the SO was described and neither it did highlight the line when I edited the regex to match the rest of the line.

I also checked that the rule was in place on editor by using :syn list myTodo.

After this experiment I figured that maybe I should add this rule to syntax highlighting files instead of my theme. However, I'm not sure if this is a correct assumption and how to do it. I'd like to highlight TODO lines in every language I work with, be it Tex, Python, JS etc. By inspecting directory /usr/share/vim/vim74/syntax/ it looks like each language needs a separate syntax file.

Is it possible to define the TODO-highlight syntax globally? How and where should I put the definition? Btw, I use Vim from terminal on Linux.

1 Answer 1

15

I'm not sure why the answer you linked to is accepted and so highly voted, because neither of the included solutions will actually work, and while a working solution is alluded to in the explanation, that solution still involves messing with the 'iskeyword' setting and everything that entails, which is neither necessary nor a good idea, just to fix this single issue with syntax highlighting.

There are three problems with the solution you've tried, which can be figured out by digesting the contents of the documentation at :help :syn-contains and :help :syn-priority.

Problem 1: contained

The first problem is that you need your myTodo item to be found inside other syntax items, viz. comments. You might imagine that the contained argument in the suggested :syntax command achieves this, but that's not what that means: instead, the contained keyword declares that your syntax item cannot be found at the top level outside of other syntax items: it doesn't specify where it can be found.

In order to allow your myTodo item to be found inside another syntax item, it must either be specifically listed in a contains argument in that item's definition, or instead the myTodo item must list the other item as a containedin argument. The first of these is not practical—the "comment" syntax item is defined elsewhere— so we must instead use containedin:

:syntax match myTodo /\v<(TODO|FIXME).*/ containedin=.*Comment

N.B. I'm using a "very-magic" search to make the regular expression a bit easier to write/read. See :help \v for details.

This will allow your myTodo item to be contained within any syntax item that ends in Comment. This isn't truly "global", but it probably more closely matches what you want. If you really want this to match anywhere, then you could instead use the special ALL keyword described in :help :syn-contains.

:syntax match myTodo /\v<(TODO|FIXME).*/ containedin=ALL

Problem 2: keyword vs match

You will note that the above still doesn't actually work. This is because the existing TODO highlighting takes precedence. This is generally implemented as a keyword, and you will see in the list of priorities linked above that keywords take priority over matches that begin at the same position.

The way to work around this is to make your match start before the existing keyword, and then adjust the start of the highlighting accordingly:

:syntax match myTodo /\v.<(TODO|FIXME).*/hs=s+1 containedin=.*Comment

(See :help :syn-pattern-offset.)

Note that this will not work at the start of the line. If you need it to, then you could instead use:

:syntax match myTodo /\v\_.<(TODO|FIXME).*/hs=s+1 containedin=.*Comment

(See :help 27.8.)

This still won't work if your TODO item begins at the very start of the file. If you need this, you're going to need to either: delete the existing "todo" keyword syntax items with :syntax clear commands, or use something a bit more complicated:

:syntax keyword myTodo TODO FIXME nextgroup=myRestOfTodo
:syntax match myRestOfTodo /.*/ containedin=NONE contained
:highlight link myTodo Todo
:highlight link myRestOfTodo Todo

This sets up a keyword item for todo items, and a match item that matches everything but can only be found immediately following a myTodo item.

(See :help :syn-nextgroup.)

Problem 3: Where to put the commands

So now we have some commands that will work when run manually in a file.

As a rule of thumb, Vim doesn't really care in what files you place your config. It's not true that syntax commands will only have an effect if they are in a "syntax highlighting file", as you wonder in your question.

However, the reason for all the different locations within .vim that you can place config is that the different files are loaded automatically by Vim at various different times, and in order for our code to work, we ideally want it to be run after the existing syntax has been set up.

As such, the simplest solution that will work globally is probably to use an autocommand in your .vimrc:

augroup myTodo
  autocmd!
  autocmd Syntax * syntax match myTodo /\v\_.<(TODO|FIXME).*/hs=s+1 containedin=.*Comment
augroup END

highlight link myTodo Todo

If you were happy with setting up the matches specifically for each filetype, I'd recommend nixing the autocommand and instead placing the :syntax commands within the relevant files in .vim/after/syntax, e.g. .vim/after/syntax/plaintex.vim

I'll leave it as an exercise to the reader to figure out how to alter the above to use instead the other solution that utilises nextgroup. ;)

1
  • amazing answer, congrats.
    – DrBeco
    Commented Feb 12, 2022 at 18:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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