VIM-Lint-Front插件开发笔记(1)

187 阅读3分钟

插件设计

该插件的功能为协助进行nlint/Verilator-lint或其他lint工具对RTL的检查,插件打开后,功能分区如下:

2022-06-28-22-48-29-image.png

在侧边栏区域,有以下快捷键:

  • o(open):打开/折叠文件的错误列表
  • m(mark):标记为重要,底色变红,用于标记需要解决的lint报错
  • i(ignore):标记为忽略,底色变灰,用于标记无需解决的lint报错
  • c(clear):清除所有标记
  • d(dump):生成重要、忽略和无标记列表

技术路线

使用Python进行lint报告的识别,并转为一个统一的格式,由vimrc脚本进行显示控制,如下所示:

2022-06-28-23-00-02-image.png

类似插件研究(bufexplorer)

bufexplorer是一个可以打开页面显示当前缓冲区的插件,可以从该插件中获取创建窗口、设置窗口属性并编辑内容和映射快捷键的方法

创建窗口

bufexplorer中创建窗口的代码如下所示:

        if _size <= 0
            execute 'keepalt ' . s:splitMode "splitMode 为sp或vsp
        else
            execute 'keepalt ' . _size . s:splitMode
        endif

这里涉及两个指令,分别是:

  • execute:等效于Python中的eval,用于将字符串作为指令执行
  • keepalt:保持当前文件指向

这里一种组合出的命令为execute keepalt sp,表示进行水平分屏,并保持当前所选的文件指向

设置窗口属性

bufexplorer创建了一个窗口,并在这个窗口中打印buffer信息,其中buffer信息窗口是不可写的,且不对应任何缓冲区,这段代码如下所示:

    setlocal buftype=nofile
    setlocal modifiable
    setlocal noreadonly
    setlocal noswapfile
    setlocal nowrap

    ......

    call setline(1, s:CreateHelp())
    call s:BuildBufferList()
    call cursor(s:firstBufferLine, 1)

    ......

    setlocal nomodifiable

这里使用setlocal参数设置这个窗口的相关属性,这里设置的属性包括:

  • buftype:设置为nofile,表示这个窗口不对应任何缓冲区
  • modifiable:设置为True,表示这个窗口是可以修改的(修改完成后设置为False)
  • noreadonly:设置readonly为False,表示这个窗口非只读,可以写入文件(但是本窗口无对应的缓冲区)
  • noswapfile:设置swapfile为False,表示这个窗口不生成swap文件
  • nowrap:设置wrap为False,表示当一行内容超出窗口长度时,不会折行显示

中间调用内置的setlinecursor函数写入内容并设置光标位置,最后写入完成后,将属性设置为不可修改(nomodifiable),完成窗口内容写入和锁定

专用快捷键

在bufexplorer的窗口中,有专用的快捷键映射(和VIM默认映射不同),代码如下所示:

    nnoremap <script> <silent> <nowait> <buffer> <2-leftmouse> :call <SID>SelectBuffer()<CR>
    nnoremap <script> <silent> <nowait> <buffer> <CR>          :call <SID>SelectBuffer()<CR>
    nnoremap <script> <silent> <nowait> <buffer> <F1>          :call <SID>ToggleHelp()<CR>
    nnoremap <script> <silent> <nowait> <buffer> <s-cr>        :call <SID>SelectBuffer("tab")<CR>
    nnoremap <script> <silent> <nowait> <buffer> a             :call <SID>ToggleFindActive()<CR>
    nnoremap <script> <silent> <nowait> <buffer> b             :call <SID>SelectBuffer("ask")<CR>
    nnoremap <script> <silent> <nowait> <buffer> d             :call <SID>RemoveBuffer("delete")<CR>

这里使用了映射命令nnoremap,表示这个映射针对普通模式,尖括号中的是这个映射命令的选项,冒号后是映射的函数,选项包括:

  • <script>:用于避免外部映射(没看太懂)
  • <silent>:表示静默执行,不打印
  • <nowait>:表示立即执行,不等待持续匹配(例如映射为a,另一映射为aa,使用该选项后键入a立刻执行a,不会等待aa输入)
  • <buffer>:表示只针对本窗口

关键技术

窗口创建

创建窗口的示例函数如下图所示:

function! NewWindowsVSP(windows_name) abort
    keepalt vsp
    execute "silent keepjumps hide edit ".a:windows_name
    call AddContent(a:windows_name)
endfunction

首先使用vsp进行分屏,分屏后打开一个名为windows_name的界面,随后设置属性并向其中添加内容,函数AddContent如下所示:

function AddContent(content) abort
    setlocal buftype=nofile
    setlocal modifiable
    setlocal noreadonly
    setlocal noswapfile
    setlocal nowrap
    call setline(1, [a:content." line 1",a:content." line 2"]) "写入内容
    setlocal nomodifiable
endfunction

开头几行设置状态,setline那一行写入内容,最后将属性设置为不可更改,这个函数运行前后的截图如下所示:

2022-07-13-23-07-08-image.png

窗口联动

窗口联动可以以这个方式进行:记录光标位置->移动光标到需要操作的窗口->进行操作->移动回光标。首先考虑光标操作,这里的光标操作主要有两个,分别为:

  • winline()wincol():分别获取光标在当前窗口中的行号和列号,另外还有line()col()获取当前文件中的行号列号,screenrow()screencol()获取当前整个vim窗口中的行号和列号
  • getpos()直接获取光标的位置,返回值是个4元列表,包括缓冲区编号、行、列和偏移
  • setpos()设置光标位置,传入数据类型和getpos()的输出类型相同,也是一个4元列表

随后考虑窗口的跳转,窗口跳转由两种方式,分别是使用位置关系跳转和使用编号跳转:

  • 位置关系跳转:使用命令wincmd h/j/k/l从当前窗口向对应方向跳转
  • 编号跳转:使用命令[count]wincmd w跳转到编号为count的窗口,使用winnr()可以获取当前窗口编号,根据当前窗口编号和创建顺序(vim的窗口编号是按窗口产生顺序的)直接跳转到对应窗口

这里以跳转到当前窗口上一个创建的窗口写入一段字符的方式测试窗口联动,代码如下所示:

function! AddPreLine() abort
    let this_win_num = winnr()
    let pre_win_num = this_win_num - 1
    let pos = getcurpos() "save pos
    execute pre_win_num . "wincmd w"
    call setline(1, ["add by AddPreLine()"])
    execute this_win_num . "wincmd w"
    call setpos('.',pos)
endfunctionnction

运行效果如下所示:

2022-07-17-22-19-58-image.png