共计 2856 个字符,预计需要花费 8 分钟才能阅读完成。
实习之后发现,一切代码活动都局限在一个终端界面了。由于一些安全原因和开发环境的方便,开发都是通过远程 ssh 到开发机上开发,自然也就只有终端界面了。VNC 因为安全原因不让用,所以就别妄想使用 Clion 等 IDE 来开发了。在这样的背景下,人们大多使用 VIM 或者 EMACS 等编辑器来开发。
在调试过程中,服务端日志是一个重要的参考依据。但是这类文本并不是某种编程语言,通常查阅的时候是没有语法高亮的,而且为了对grep 命令友好,通常会将一条日志打在一行里,这就使得日志信息非常密集,分辩关键信息的时候非常不方便。于是我便有了这样一个想法,编写 VIM 插件,对日志中的关键信息如时间戳、代码行号、错误码进行语法高亮。
为了叙述的方便,我们的目标是为下面这段日志进行高亮,将日志级别、时间戳、代码行号标识出来。
[ERROR][2017-10-01 08:08:08][example.go:231]Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sed diam eget risus varius blandit sit amet non magna. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
[DEBUG][2017-10-01 08:08:10][example.go:233]Lorem ipsum dolor sit amet
[INFO][2017-10-01 08:09:09][example.go:2333]Lorem ipsum dolor sit amet
语法高亮插件
语法高亮插件需要两个 .vim
文件。一个是语法检测文件(ftdetect),这是为了让 VIM 能够将指定语法应用于指定后缀的文件。一个是语法文件(syntax),这里定义了高亮的语法和着色方案。
插件的目录结构如下:
/Users/zhuangqh/.vim
├── ftdetect
│ └── log.vim
└── syntax
└── log.vim
这些文件在类 UNIX 系统上要放到 $HOME/.vim
目录下,Windows 系统是 $HOME/vimfiles/
下。
语法检测
当 buffer 读取或创建时,将 .log
后缀的文件类型设置为 log
,之后使用log
类型的语法高亮方案进行着色。
" ftdetect/log.vim
au BufNewFile,BufRead *.log set filetype=log
语法高亮
这是文本的重点,该文件告诉 VIM 该怎么着色。
关键字高亮
syn keyword ${group} ${keyword}
大多数编程语言都有关键字。规则设置的时候,先给他一个组名,后面再接着一些关键字,之后再根据这个组名设置颜色。关键字高亮的匹配优先级是最高的,如果有其它高亮规则匹配上了也会按关键字的规则来高亮。
这个规则对我们这次任务没什么用,因为我们只想高亮日志开头的那个特定的 ERROR
字样,存在上下文,实际上并不是关键字。
匹配字高亮
syn match ${name} ${pattern}
这个命令提供了一种强大的匹配方法,用正则表达式来匹配。我们可以用来匹配我们的时间戳,如:syn match logDate '\d\{4}-\d\d-\d\d'
高亮嵌套
对某个匹配的字符串高亮之后,对子字符应用不同的规则。
比如上述日志中的代码行号 example2.go:233
,我们先整体匹配了这个模式,然后希望行号能有不一样的颜色。这可以理解成匹配的上下文,规则只在指定上下文中有效。
syn match logFile '\w*\.go:\d*' contains=logLineNum
syn match logLineNum '\d*' contained
contains
告诉 VIM 这个 token 会包含其他哪些 token。contained
告诉 VIM,只有在被其他 token 包含时,该规则才有效。
匹配偏移
在高亮行号时,\d*
规则会将所有的数字高亮,而事实上,只有冒号右边的数字才是行号,这就要用到匹配偏移的规则了。
syn match logLineNum ':\d*'ms=s+1 contained
匹配偏移用来调整实际匹配的值。ms(me)
表示的是实际匹配的起始(终止)下标,s(e)
表示的是原匹配字符的起始(终止)下标。我们用 :\d*
匹配后,将下标向右调整一位即可。
记得偏移命令要紧跟模式项,否则会报错。
区域高亮
syn region ${name} start=${pattern} end=${pattern} skip=${pattern}
区域匹配最常见的是匹配一个字符串,用引号包裹的字符串,可以通过 skip 来跳过转义字符如\"
。
在我们的日志高亮任务里,想匹配的是包含特定 token 的中括号,我们只高亮中括号,其他的交由其他规则来匹配。
syn region logBlock matchgroup=logParen start=/\[/ end=/\]/ fold
配色
hi ${name} ctermfg=${color}
为前面定义的语法 token 设定着色样式,ctermfg 是彩色终端的前景色,其他选项详见:highlight
结果
把所有规则集结起来如下:
if exists("b:current_syntax")
finish
endif
syn match logLevelError 'ERROR' contained
syn match logLevelDebug 'DEBUG' contained
syn match logLevelInfo 'INFO' contained
syn match logFile '\w*\.go:\d*' contains=logLineNum
syn match logLineNum ':\d*'ms=s+1 contained
syn match logDate '\d\{4}-\d\d-\d\d' contained
syn match logTime '\d\d:\d\d:\d\d' contained
syn region logBlock matchgroup=logParen start=/\[/ end=/\]/ fold
\ contains=logLevelError,logLevelDebug,logLevelInfo,logFile,logDate,logTime
hi logLevelError ctermfg=red
hi logLevelDebug ctermfg=yellow
hi logLevelInfo ctermfg=green
hi logFile ctermfg=yellow
hi logLineNum ctermfg=blue
hi logDate ctermfg=yellow
hi logTime ctermfg=blue
hi logBlock ctermfg=white
hi logParen ctermfg=grey
let b:current_syntax = "log"
参考资料
- Creating your own syntax files
- Vim 自定义语法高亮
- :highlight