Vimscript编程指南

Toggling

在之前的章节里,我们讲了怎么在vim里设置选项。对于布尔型选项,我们可以用set someoption!来切换选项的值。当我们对这个命令设置一个快捷键的话,它就变的非常有用了。

运行下面的命令:


     :nnoremap N :set local number!

在normal模式下输入N来试试上面的命令。Vim会对当前窗口的行号选项进行切换。创建一个这样的切换映射是非常有用的,因为我们没必要对于一个选项的开和关使用不同的两个按键。

不幸的是,这只对布尔型选项有效。如果你想切换一个非布尔型的选项的话,我们需要一些额外的工作。

切换选项

我们从创建一个用来切换选项的函数开始,然后建立一个映射来调用这个函数。把下面的代码放到你的~/.vimrc文件里(或者一个单独的文件放在~/.vim/plugin目录下):


     nnoremap  :call FoldColumnToggle()

     function! FoldColumnToggle()
          echom &foldcolumn
     endfunction

写入文件并且加载,然后通过来试试上面的映射。vim会输出当前的foldcolumn选项的值。如果你对foldcolumn这个选项的作用不是很明白的话,你可以先阅读一下:help foldcolumn。

下面我们加入实际的功能。编辑代码,修改成如下内容:

     
      nnoremap  :call FoldColumnToggle()

     function! FoldColumnToggle()
          if &foldcolumn
               setlocal foldcolumn=0
          else
               setlocal foldcolumn=4
          endif
     endfunction

写入文件,并加载。每次你输入的时候vim会显示或者隐藏折叠列。

这里的if语句只是检查foldcolumn的值是否为真值(在vim里整数0代表假,其他的数字代表真值)。所以,如果是假值的话,就会把它设置成0,这样就会隐藏它。否则的话就把它的值设置为4。很简单。

你可以用类似的简单函数来操作任何的0值代表关闭,其他值代表打开的选项。

切换其他功能

选项并不是我们想要进行切换的唯一功能。一个很有用的功能就是对快速补全窗口设置快捷键。我们先像之前一样先写一个简单的框架。把下面的代码加入到你的文件里:

     
     nnoremap  :call QuickfixToggle()

     function! QuickfixToggle()
          return
     endfunction

这个映射并不会做任何事情。我们来把改变它,是它变得有用起来(不过并没有完全完成)。修改代码如下所示:


     nnoremap  :call QuickfixToggle()

     function! QuickfixToggle()
          copen
     endfunction

写入并且加载这个文件。如果你再试试这个映射的话,你会看到它只是打开了一个快速补全窗口。

为了达到我们要切换的目的,我们先用一个简单,但是很不好的方法:全局变量。修改代码如下所示:


      nnoremap  :call QuickfixToggle()

     function! QuickfixToggle()
             if g:quickfix_is_open:
                    cclose
                    let g:quick_fix_is_open = 0
             else
                    copen
                    let g:quick_fix_is_open = 1
              endif
     endfunction

我们所做的很简单——我们用一个全局变量来表示快速补全窗口的状态,每次改变状态的时候,都把当前的状态存入到这个变量里。

写入,并且加载这个文件,试着运行这个映射。vim会抱怨说这个变量没有定义!我们来修复这个问题,对它进行一下初始化:

     
     nnoremap  :call QuickfixToggle()

     let g:quick_fix_is_open = 0

     function! QuickfixToggle()
             if g:quickfix_is_open:
                    cclose
                    let g:quick_fix_is_open = 0
             else
                    copen
                    let g:quick_fix_is_open = 1
              endif
     endfunction

写入并且加载这个文件,再试试这个映射,它起作用了。

提高

我们的切换功能起作用了,但是它还是存在一些问题。

第一个问题是,如果用户手动用:copen和:cclose来打开和关闭这个窗口的话,那么我们的变量的值就没法相应地进行更新了。这个问题并不是很严重,因为大部分情况下用户都会用映射来打开这个窗口的,如果不是的话,他们只需要多按一次映射键就可以了。

这个也说明了一个道理:当你在写vimscript代码的时候,如果你想要把所有的边界条件都覆盖到的话,你会陷入困境,并且很难完成你的任务的。

让一个功能大部分情况下都能正常工作(并不是说在不工作的时候爆发出一大堆问题)以及继续编码会比花大量的时间来让它达到100%的完美更好。不过一个例外就是,当你在写一个插件供很多人使用的时候。这个情况下,你最好多花点时间,让它能够处理绝大多数的场景,这样会让你用户开心,同时也会减少bug报告。

还原Windows/Buffers

我们的函数的另外一个缺陷是,如果用户使用映射的时候他已经在快速补全窗口中了,那么vim会关闭它,并且把它切换到上一个窗口里,而不是把它放到它之前所在的地方。这个在你想很快检查一下快速补全窗口里的选项并且继续工作时,比较让人讨厌。

为了解决这个问题,我们引入一个写vim插件会迟早会用到的功能。修改你的代码如下所示:



     let g:quick_fix_is_open = 0

     function! QuickfixToggle()
             if g:quickfix_is_open:
                    cclose
                    let g:quick_fix_is_open = 0
                    execute g:quickfix_return_to_window . "window w"
             else
                    let g:quickfix_return_to_window = winnr()
                    copen
                    let g:quick_fix_is_open = 1
              endif
     endfunction

我们在函数的代码里添加了新的两行。第一个(在else语句里),会在运行:copen之前,把当前窗口的编号存入到另一个全局变量里。

第二行(在if语句里)执行wincmd,并且用窗口的编号作为前缀,来告诉vim跳转到对应的窗口。

再次说明一下,我们的方案并不是完美的。因为我们的用户可能在运行映射的时候打开或者关闭一个新的split。即使是这样,这个功能对于现在而言已经足够好了。

这种手动保存全局变量的方法在一些很严肃的程序里是不可取的,但是对于简单的vimscript函数而言这是一个快速但是恶心的可以让你的功能很快完成,并且继续你的生活的方式。。

练习

  • 阅读:help foldcolumn
  • 阅读:help winnr()
  • 阅读:help ctrl-w_w
  • 阅读:help wincmd。
  • 阅读:help ctrl-q和:help ctrl-f来看看,你的这些映射覆盖了什么
  • 如果需要的话通过s:和来加上命名空间