开关

在最早的某个章节中,我们讨论过在 Vim 中如何设置选项。 针对布尔值的选项,我们可以用 set someoption! 来“开关”选项。 这对于我们创建映射是特别有用的。

运行以下命令:

:nnoremap <leader>N :setlocal number!<cr>

试试在普通模式下按下 <leader>N。Vim 会开关当前窗口的的行号。 创建这样的“开关”映射确实很方便,因为我们不需要分别用两个按键来开和关了。

遗憾的是这只对布尔值选项有效。如果我们想开关一个非布尔值选项,那就要更复杂一些了。

开关选项

让我们从创建一个可以开关选项的函数开始,并且创建一个映射来调用它。 把以下代码放进你的 ~/.vimrc 文件(或者也可以是 ~/.vim/plugin 中的一个独立文件):

nnoremap <leader>f :call FoldColumnToggle()<cr>

function! FoldColumnToggle()
    echom &foldcolumn
endfunction

保存并载入执行文件,然后试试按下 <leader>f,Vim 会显示 foldcolumn 选项当前的值。 如果你不熟悉这个选项,就去阅读 :help foldcolumn

让我们把正真开关功能的代码加进去。把代码编辑成下面这样:

nnoremap <leader>f :call FoldColumnToggle()<cr>

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

保存并载入执行文件,然后试试看。每次你按下它,Vim 都会显示或隐藏折叠的列。

if 语句只是检查 &foldcolumn 是否为真(记住,Vim 把整数0当作假,而其他的数字当作真)。 如果是真,把它设为0(这会隐藏它),否则把它设为4。 非常的简单。

你可以用这样一个简单的函数来开关任何选项,0 表示“关”,而任何其他的数字表示“开”。

开关其他东西

选项不是唯一想要开关的东西。其中一个特别有用的映射就是 quickfix 窗口。 让我们从之前相同的框架开始。把以下代码加到文件中:

nnoremap <leader>q :call QuickfixToggle()<cr>

function! QuickfixToggle()
    return
endfunction

这个映射还什么都没做。让我们稍微把它变得更有用些(但还没有完全完成)。 把代码修改成以下:

nnoremap <leader>q :call QuickfixToggle()<cr>

function! QuickfixToggle()
    copen
endfunction

保存并载入执行文件。现在试试这个映射,你会看到仅仅是打开了 quickfix 窗口。

为了达到想要的“开关”目的,我们会用一种快速、有污染的解决方法:全局变量。 把代码修改成如下:

nnoremap <leader>q :call QuickfixToggle()<cr>

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

我们所做的很简单 —— 每当调用函数时,存储一个全局变量来表示 quickfix 窗口的开关状态。

保存并载入执行文件,并试试运行映射。Vim 会抗诉变量还未定义! 我们通过初始化来修复这个问题:

nnoremap <leader>q :call QuickfixToggle()<cr>

let g:quickfix_is_open = 0

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

保存并载入执行文件,再试试这个映射。能跑通了!

改进

这个开关函数能运行了,但是还有一些问题。

第一个问题就是如果用户手动用 :copen:cclose 打关窗口,我们定义的全局变量是不会更新的。 实际上这并不能算一个很大的问题,因为大多数情况下用户都会用映射来开关窗口,即使没有,也可以再按一次来更新。

这说明了编写 Vimscript 代码时很重要的一点:如果你试图控制所有的边界情况,将会陷入泥潭而永远完成不了工作。

使某些事在大部分情况下能正常工作(在不能正常工作时也不会崩溃)并继续编程,通常都好过花费大量时间使它百分百完美。 除非你准备写一个给其他人用的插件。 在这种情况下,最好多花点时间来保证它能刀枪不入,这样才能使你的用户感到舒心,并减少问题报告。

恢复窗口/缓冲

这个函数的另外一个问题就是,如果用户已经打开了 quickfix 窗口,然后再运行映射, Vim 会关闭它,并回到最后一个分割窗口,而不是之前所在的。 如果你只是想快速检查下 quickfix 窗口,然后回到工作中,那就很烦人了。

为了解决这个问题,我们将介绍一个编写 Vim 插件时的习惯,这迟早能派上用场。 编辑代码成如下:

nnoremap <leader>q :call QuickfixToggle()<cr>

let g:quickfix_is_open = 0

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

我们在映射中新添了两行。 其中一行(在 else 子句中)设置了另外一个全局变量,来保存运行 :copen 时所在的窗口序号。

另一行(在 if 子句中)执行 wincmd w,之前的序号作为前缀,来告诉 Vim 跳转到那个窗口。

但我们的解决办法仍然不是刀枪不入的,因为用户可能在运行多次映射之间开关新的分割窗口。 即便如此,它也涵盖了大多数情况,目前足够使用了。

手动保存全局状态的策略在大多数重要的程序中是不被赞成的,但是在一些微小的 Vimscript 函数中, 这种快速、有污染的方式可以让大多数代码正常运行,并让你的生活继续。

练习

阅读 :help foldcolumn

阅读 :help winnr()

阅读 :help ctrl-w_w

阅读 :help wincmd

在必要的地方给函数添加 s:<SID> 使其处于当前命名空间内。

原文地址:http://learnvimscriptthehardway.stevelosh.com/chapters/38.html