本文介绍如果让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;有了自己的高亮。

其他参考

(完)