johca
johca

Reputation: 53

Curly braces marked as syntax error in Vim

I am using vim 8.1 and while writing C code, I am noticing all my curly braces are using red background with white foreground. After some digging, I found this setting in my theme: call s:hi('Error', { 'fg': s:white, 'bg': s:red }) I have confirmed this is where the color is coming from, by modifying the color here.

Since this is noted as a syntax error and i have syntax on in my vimrc I am confused why my curly braces are showing as a syntax error. This happens even in very simple code such as:

int main(int argc, char **argv) 
{
    return 0;
}

I am confused why this is showing as an error. This is happening if I put the opening curly brace at the end of the main definition or after it. If i run clist vim tells me there are no errors. vimrc is viewable at: https://pastebin.com/LukL8MBg

EDIT 1: On further digging, the issue appears to be with the colortheme I am using. There is a file that contains this for syntax:

syn keyword cDeclarationOverwrite var const type 
syn match cBraces       "[{}\[\]]"
syn match cParens       "[()]"
syn match cOpSymbols    "=\{1,2}\|!=\|<\|>\|>=\|<=\|++\|+=\|--\|-="
syn match cEndColons    "[,]"
syn match cLogicSymbols "\(&&\)\|\(||\)\|\(!\)"

EDIT 2: On recommendation of user @filbranden i ran :scriptnames and see the following:

1: /usr/share/vim/vimrc
  2: ~/.vimrc
  3: ~/.vim/autoload/plug.vim
  4: /usr/share/vim/vim81/filetype.vim
  5: /usr/share/vim/vim81/ftplugin.vim
  6: /usr/share/vim/vim81/indent.vim
  7: /usr/share/vim/vim81/syntax/syntax.vim
  8: /usr/share/vim/vim81/syntax/synload.vim
  9: /usr/share/vim/vim81/syntax/syncolor.vim
 10: ~/.vim/plugins/purify/vim/colors/purify.vim
 11: ~/.vim/plugins/purify/vim/autoload/purify.vim
 12: /usr/share/vim/vim81/plugin/getscriptPlugin.vim
 13: /usr/share/vim/vim81/plugin/gzip.vim
 14: /usr/share/vim/vim81/plugin/logiPat.vim
 15: /usr/share/vim/vim81/plugin/manpager.vim
 16: /usr/share/vim/vim81/plugin/matchparen.vim
 17: /usr/share/vim/vim81/plugin/netrwPlugin.vim
 18: /usr/share/vim/vim81/plugin/rrhelper.vim
 19: /usr/share/vim/vim81/plugin/spellfile.vim
 20: /usr/share/vim/vim81/plugin/tarPlugin.vim
 21: /usr/share/vim/vim81/plugin/tohtml.vim
 22: /usr/share/vim/vim81/plugin/vimballPlugin.vim
 23: /usr/share/vim/vim81/plugin/zipPlugin.vim
 24: /usr/share/vim/vim81/autoload/dist/ft.vim
 25: /usr/share/vim/vim81/ftplugin/c.vim
 26: /usr/share/vim/vim81/indent/c.vim
 27: ~/.vim/plugins/purify/vim/syntax/c.vim
 28: /usr/share/vim/vim81/syntax/c.vim
Press ENTER or type command to continue

From the looks of things here, c.vim from the purify plugin is loaded before vim's syntax/c.vim which is being loaded last.

output of :set rtp? shows:

runtimepath=~/.vim,~/.vim/plugins/purify/vim,/usr/share/vim/vimfiles,/usr/share/vim/vim81,/usr/share/vim/vimfiles/after,~/.vim/after

Can anyone explain why this is happening ?

Upvotes: 1

Views: 1037

Answers (1)

filbranden
filbranden

Reputation: 8908

So it seems to me that this is a bug in the syntax rules set by the kyoz/purify plugin, in particular regarding the ordering of the rules in relation to the rules from the Vim runtimes.

When the a plugin manager added a plugin, it will add that plugin's directory to 'runtimepath', and it will add it before the Vim runtime directory. This is done so that plugins can override files shipped in the Vim runtimes, by having a chance to run earlier.

In particular, this means that the syntax rules in the kyoz/purity plugin will be added before the ones from the Vim runtimes when a file of type C is loaded.

Now, it turns out that this triggers a conflict in how the rules interact with each other.

Let's for example examine the interaction between cParen (from Vim runtimes) and cParens (from kyoz/purify.) For simplicity, let's assume we're using let g:c_no_curly_error = 1, just so we don't have to discuss having two end= groups, but the same rationale also applies for the case where that global variable is unset.

With the current ordering, we end up with cParens from kyoz/purify coming before cParen from the Vim runtime:

" From kyoz/purify:
syn match cParens       "[()]"
" From Vim runtime:
syn region  cParen      transparent start='(' end=')' contains=ALLBUT,...

Now the first thing about syntax rules is that the rule that comes after wins. So when Vim encounters (, which could match both (either cParens which matches either, or cParen's start which matches ( exactly), it will pick cParen (from Vim runtime), since that comes later.

That's all fine... But then why do we have a problem at the )?

Since cParen is a "region", Vim will keep going trying to match the end part of it. While doing so, it will also try to match the groups in contains. But here, contains starts with ALLBUT, which means it will try to match every group except the ones in the explicit list. Which means it will also match cParens inside that group.

Now, this is what causes the problem, because if cParens is matching the ), then that means the ) will not be recognized as the end of cParen. This is actually described under :help :syn-keepend:

By default, a contained match can obscure a match for the end pattern. This is useful for nesting. For example, a region that starts with { and ends with }, can contain another region. An encountered } will then end the contained region, but not the outer region.

If you don't want this, the keepend argument will make the matching of an end pattern of the outer region also end any contained item. This makes it impossible to nest the same region, but allows for contained items to highlight parts of the end pattern, without causing that to skip the match with the end pattern.

So if we had keepend on the region rule from Vim runtime, this would actually end the cParen rule, which would not cause the issue. But since that's not the case, having cParens match the ) means the cParen will run off until the end of the file, which will cause the curly braces to trigger errors (since in C they're not allowed inside parentheses.)

If we did have keepend, the situation would be slightly better in that the cParen would see its end. But it would still not be ideal, since kyov/purify's cParens would only match the ) and would not really match the (. (You would need something like matchgroup to also match the delimiters, but that's another topic.)

So that should explain why this happens.


So what can we do about it?

It seems to me that loading the rules in the opposite order would be better here.

" From Vim runtime:
syn region  cParen      transparent start='(' end=')' contains=ALLBUT,...
" From kyoz/purify:
syn match cParens       "[()]"

Since, when there are multiple matches, Vim will use the rule that comes last, this will always only trigger cParens for a (, so we don't have the problem with the runaway group. ) will only match cParens as well (in that case that's not a conflict with cParen anymore, since that would only match the end of a group.)

This will make the rules from kyov/purify work as expected.

But it does have a side-effect! That the cParen rule from Vim runtimes will be completely shadowed by that plugin. (And also similar rules that match on the same characters, for example, cBlock.

Is that a problem? Maybe. It seems most of those rules are marked transparent (they don't affect highlighting directly) and are mostly used to flag situations such as marking unbalanced or unmatched braces, flagging them as Error (similar to what you were seeing, but for actual syntax problems.)

If you think the use case from kyov/purify (which is to color the braces) is preferrable to the one in the Vim runtimes, then you might be fine with shadowing the rules in the Vim runtimes.

This can be accomplished by moving the syntax rules to under an after/ subdirectory. Plugin managers will recognize an after/ directory in a plugin, and they will add it after the Vim runtimes. That's the established mechanism to allow plugins to decide whether they want their rules to come before the ones from Vim runtimes (which is the most common case), or come later.

Since I believe this is a bug in kyov/purify, I opened a pull request that moves the rules for C and C++ to an after/ directory. Testing it locally, it seemed to fix the issue you reported above, without really introducing any adverse issues. (Pull request was promptly merged, so it seems the author agreed with this being a bug there.)

Upvotes: 2

Related Questions