本文介绍如果让Vim在C++的语法高亮中嵌入SQL的语法高亮。
C++11开始支持rawstring,比如:
const char* zSql = R"sql(
select * from phantom;
)sql";
上面的例子中,字符串的值是一个SQL语句select * from phantom;
,它被sql(
和sql)
这两个自定义标签包围。所以,问题是是否可以通过这两个自定义标签来让Vim高亮其中的SQL语句呢?
从stackoverflow的这个帖子得知可以通过下面这种方式在Vim中嵌套语法高亮:
syntax include @Sql syntax/sql.vim
syntax region cppSql start="sql(" end="sql)" contains=@Sql
上面的第一行语句读取SQL的语法,并放置在@Sql
这个命名空间下。第二行语句定义了一个叫做cppSql
的语法区域,匹配sql(
和sql)
这两个标签,并在其中应用@Sql
命名空间中的语法。
但是有一个问题,Vim的C++语法文件定义了下面的语法:
syn region cppRawString matchgroup=cppRawStringDelimiter start=+\%(u8\|[uLU]\)\=R"\z([[:alnum:]_{}[\]#<>%:;.?*\+\-/\^&|~!=,"']\{,16}\)(+ end=+)\z1"+ contains=@Spell
因为c++语法文件中的cppRawString优先匹配了目标字符串,所以我们自定义的cppSql匹配失败。
解决方法
Vim里面支持把文件的类型设置成嵌套的,比如set ft=html.css
,这会让Vim先载入html语法,再载入css语法。所以Vim可以显示在html中嵌套的css。
如法炮制,我们可以set ft=cpp.sql
,让Vim先载入cpp语法,然后再载入sql语法。由于Vim现有的sql语法设计的时候没有考虑嵌套的问题,以及cpp语法设计时也没有考虑会有其他语法嵌套进来,所以直接执行set ft=cpp.sql
并不会达到想要的效果。我们需要自己写一个自定义的sql语法:
if exists('b:current_syntax')
if b:current_syntax != 'cpp'
finish
else
let b:main_syntax = 'cpp'
unlet b:current_syntax
endif
endif
syn include @cppSql $VIMRUNTIME/syntax/sql.vim
syntax region cppRawString matchgroup=cppRawDelimiter start=@\%(u8\|[uLU]\)\=R"\z([[:alnum:]_{}[\]#<>%:;.?*\+\-/\^&|~!=,"']\{,16}\)(@ end=/)\z1"/ contains=@cppSql
if exists('b:main_syntax')
if b:main_syntax == 'cpp'
let b:current_syntax = 'cpp'
endif
endif
上面的脚本工作原理是这样的:
- 判断当前的语法
b:current_syntax
是否是cpp,因为cpp会被先载入,所以b:current_syntax
的值会被设为cpp,如果不是,则说明我们的场景并不是cpp.sql
,此时应该推出 - 载入sql语法,放置在命名空间@cppSql之下
- 修改cpp语法中的cppRawString规则,使其包含@cppSql
- 最后恢复一下现场,把
b:current_syntax
改回cpp
把上面的文件保存到.vim/after/syntax/sql.vim
。然后执行set ft=cpp.sql
,就可以看到select * from phantom;
有了自己的高亮。
其他参考
- vim的帮助 :h syn-include:
- https://stackoverflow.com/questions/4777366/recommended-vim-plugins-for-javascript-coding
- https://stackoverflow.com/questions/39119407/extend-syntax-match-region
(完)