Vimscript编程指南

高级代码折叠

在上一个章节里,我们用Vim的indent折叠来给我的Potion代码添加了一些方便的但是不完美的折叠方式。

打开factorial.pn文件,然后使用zM来折叠所有的代码。现在你的文件应该看起来和下面相似:


     factorial = (n):
     +---     5 lines: total = 1

     10 times (i)
     +--     4 lines: i  string print

     打开第一个折叠区,文件看起来如下所示:

     factorial = (n):
          total = 1
          n to 1 (i):
     +--     2  lines: # Multiply the running total.
          total.

     10 times (i):
     +--     4 lines: i  string print

这样看起来也不错,不过我个人更加喜欢用一个代码块的第一行来折叠它所有的内容。这个章节里我们会写一些自定义的折叠代码,我们完成后,上面的代码就会折叠成下面的样子:


     factorial = (n):
          total = 1
     +---  3 lines: n to 1 (i):
          total.

     +--     5 lines:10 times (i):

这样代码看起来就更加紧凑了,也就更加容易阅读了。如果你更加喜欢indent方法的折叠的话,也是可以的。不过,还是要学习本章的内容来实践一下Vim的折叠表达式。

折叠理论

知道vim是怎么来“考虑”折叠的,对于编写自定义代码折叠是很有作用的。下面是对这个的简单介绍:

文件的每一行文本都对应一个折叠等级。这个等级要么是0要么是一个正整数。

折叠等级为0的行不会被折叠

相邻的文本,如果等级相同的话,会被折叠在一起

如果一个等级为X的折叠是关闭的话,它后面所有的折叠等级大于或者等于X的文本行都会被折叠,直到遇到折叠等级大于X的文本行为止。

下面我们用一个简单的例子来说明这个。打开一个vim窗口,把下面的文本粘贴进去:


     a
          b
          c
               d
               e
          f
     g

通过下面的命令来打开indent折叠:


     :setlocal foldmethod = indent

试试这个折叠方式,看看它是怎么进行折叠的。

现在通过下面的命令来看看第一行的折叠等级:


     :echom foldlevel(1)

Vim会输出0。现在再看看第二行的折叠等级:


     :echom foldlevel(2)

Vim会输出1.在看看第三行:


     :echom foldlevel(3)

Vim会输出1。这就意味着第二和第三行都是等级为1的折叠。

下面显示了每一行的折叠等级:



      a               0
          b           1
          c           1
               d      2
               e      2
          f            1
     g                 0

再次阅读前面的内容。打开和关闭每个折叠,看看对应的折叠等级,确保你明白了折叠的原理。

一旦你对于每行文本的折叠等级对于创建不同的折叠的作用,继续阅读下一章节。

第一步:创建一个计划

在我们开始写代码之前,我们先对我们的折叠定义一些规则。

首先,缩进一致的文本要一起折叠,同时前一行文本要和它们放在一个折叠里,所以,类似于下面的文本:


     hello = (name):
          'Hello, ' print
          name print.

会被折叠成:

     
     +--     3 lines: hello = (name)

空行和它下面一行的等级保持一致,所以文本后面的空行都不会被包含进来。也就意味着下面的文本:


     hello = (name):
          'Hello, ' print
          name print.

     hello('Steve')

会被折叠成:


     +--     3 lines: hello =():

     hello('Steve')

而不是:


     +--     4 lines: hello=()
     hello('Steve')

这些规则都是个人的喜好,不过现在这些规则是我们要实现的折叠方式。

开始

在编写自定义折叠代码之前,我们先打开两个split。一个打开ftplugin/potion/folding.vim文件,一个打开我们的样本文件factorial.pn。

在之前的章节里,我们都是通过打开再关闭的方式来使得我们的folding.vim文件生效,但是实际上还有一种更加简单的方式。

记得之前讲过,无论什么一个缓存里的文件的filetype被设置成potion,所有在ftplugin/potion/里的文件都会被运行。这就意味着,你只需要在factorial.pn的split里运行:set ft=potion来让Vim重新加载折叠代码。

这样就比关闭再打开文件方便多了。唯一需要注意的就是你要保持folding.vim文件,否则没有保存的内容是不会生效的。

Expr Folding

我们接下来要用Vim的expr folding 来让我们的代码折叠变得更加灵活。

我们还需要把folding.vim里的foldignore去掉,因为它只会在indent folding下生效。同样的,我们还需要告诉vim去使用expr folding,所以我们把folding.vim的内容改变成如下所示:


     setlocal foldmethod=expr
     setlocal foldexpr=GetPotionFold(v:lnum)

     function! GetPotionFold(lnum)
          return '0'
     endfunction

第一行的代码是告诉vim使用expr folding。

第二行代码指定了Vim用来取得当前行的foldlevel的函数。当vim运行这段代码的时候,它会把想要知道foldlevel的文本的行号作为参数传给这个表达式。然后这个表达式会把用这个参数来调用一个自定义的函数。

最后,我们定义了一个简单的函数,它会对所有的文本行返回'0'。注意这里返回的是一个字符串,而不是一个整数,后面我们会讲到这个的。

接下来,保存folding.vim,然后通过在factorial.pn文件里运行:set ft=potion来重新加载折叠代码。这次,Vim不会做任何折叠,因为我们的函数对于所有的文本行都返回'0'。

空行

我们首先来看看空行这个特殊的文本。修改GetPotionFold函数,使得它看起来如下所示:


     function! GetPotionFold(lnum)
          if  getline(a:lnum) =~? '\v^\s*$'
               return '-1'
          endif

          return '0'
     endfunction

我们在代码里添加了一个if语句来处理空行,它是怎么工作的呢?

首先,我们使用getline(a:lnum)来去等当前行的内容。

我们把取得的内容和正则'\v^\s*$'进行匹配。记住\v会打开'very magic'模式。这个正则会匹配“从文本行开始到结束全部都是空格”的文本。

这里的比较用的是大小写敏感的比较符=~?。从技术的角度来讲,在批评空格的时候是没必要注意大小写的,但是我比较喜欢在比较字符串的时候显示写清楚。如果你喜欢的话,也可以用~=代替。

如果你需要复习Vim里正则表达式的使用的话,你可以重新阅读“基本正则表达式”那一章节,以及“Grep 操作符”的章节。

如果当前行包含非空格字符的话,就不会匹配成功,这样就返回‘0’。

如果当前行匹配成功的话(例如,它是一个空行或者只包含空格),我们就会返回字符串‘-1’。

前面我们讲过,一个文本行的foldlevel可以是0或者正整数,那么这里又是怎样的情况呢?

特殊的折叠等级

你自定义的折叠表达式可以直接返回文本行的折叠等级,也可以返回一个特殊的字符串来告诉vim来特殊处理。

‘-1’就是其中的一个特殊字符串。它告诉Vim当前行的折叠等级是未定义的。Vim会这样解释它“这一行文本的折叠等级和它前面一行以及后面一行中,折叠等级较低的一个保持一致”。

Vim可以把这些未定义的文本行链接起来,如果你有两个未定义折叠等级的文本行在一个等级为1的文本行前面的话,那么vim就会设置最后一个未定义的文本行的等级为1,然后倒数第二个未定义的文本行的等级为1,然后第一个为1.