Table of Contents
- Vim进阶索引[2]::折叠
- 1 折叠有什么用途?
- 2 折叠的生成
- 2.1 折叠层级
- 2.2 手工规则(manual)
- 2.3 缩进规则(indent)
- 2.4 标记规则(marker)
- 3 expr规则
- 4 实例演示
- 4.1 唐诗
- 4.2 笔记
- 4.3 邮件
- 5 使用提示
- 6 小结
- Appendix A
Vim进阶索引[2]::折叠
折叠对我来说原本不是什么必不可少的功能。但现在却越来越感觉到这实在是个体贴/方便的功能,我经常要保存一些资料但我不喜欢零散的一堆文本文件所以同样的某些特定类型的资料我将它们放在同一个文件中,通过折叠用我可以在同一个文件中管理管理多个文档而不至于混乱。与Vim的其他功能相比折叠是很容易掌握的(至少manual、indent和marker规则是这样的),如果你还没用上这项功能的话赶快往下看吧……
这一篇我们将要讲的是折叠。什么是折叠呢?复制一段文本到一新建文件中,在Vim中输入`:set foldmethod=manual‘。输入`:3,8fo‘,看第三行到第八行是不是折叠起来了呢?这就是Vim的折叠功能,很容易理解吧。先记住相关的帮助命令:
:help folding :help foldmethod :help fold-methods
在Vim中输入`:help folding‘,可以看到Vim中折叠的相关文档。往下翻页你可以看到这里面的内容非常多,尤其是以z开头的那一部分指令。好消息是要用好折叠并不要求全部掌握这些命令。当然至少要掌握这几个命令:zM zR zo zc zf。折叠的用法的精髓在该页的第一部分`fold methods'。它决定Vim进行折叠所依据的规则。本文的目的是使用户对创建折叠有一个了解。并学会按自己想法来制定折叠规则。
1 折叠有什么用途?
如果你用过字处理软件Word的话那你可能会知道Word有一个大纲视图将“标题一”“标题二”作为大纲显示出来而相应的正文则呈现折叠状态。双击一个标题会展开对应的正文。通过这个视图用户可以对文档的结构有一个全局的了解,更可以有效地组织文本。折叠作用与大纲视图相近,同样让你可以有效的管理文档结构;方便地在文档的不同部分移动;让文档更清爽。此外折叠可以隐藏信息将不需要修改或不想看内容折叠起来。另外结合modeline、filetype或autocmd我们可以在更多的场合使用折叠。
Note:请注意我们可以折叠任意行,所以折叠不等同于显示大纲。另外与Word大纲依靠手工套用样式生成不同,折叠可以用手工生成也可以是用户指定的规则生成。通过指定合适的规则我们使折叠成了一种脑力劳动。
折叠让自己的文档看上去更整洁。同时可以方便地选取感兴趣的内容或屏蔽(如果不能删除的话)不想要的内容。如果你的文档有10行的话你可以不想这做,但如果是成百上千行呢?
使用折叠通常要求用户对文档进行某种方式的格式化,这使用户的文档既使在其他文本编辑器上也有较好的可读性。
当然,没有人规定折叠该怎么用,所以发挥你的创意吧。
2 折叠的生成
首先要了解foldmethod设置项,它指定了折叠产生的方式。详细的文档请参考这两个命令:
:help 'foldmethod' :help fold-methods
其中的manual、indent、expr、syntax、diff、marker规则分别表示根据手工设置、缩进、表达式、语法、diff、标记的方式生成折叠。在了解Vim如何生成折叠之前我们要先了解一个概念:折叠层级
2.1 折叠层级
折叠层级是用数字表示的折叠标志,Vim根据折叠层级来决定是否折叠及怎样折叠某一行或某几行。当我们下折叠命令时(zM)Vim对每一行计算折叠层级,然后将折叠层级大于或等于`1‘(默认情况下)的行折叠起来。如果低折叠层级的几行中有高折叠层级的行时就形成折叠的嵌套。
那这个折叠层级是如何计算出来的呢?折叠层级的计算方式取决于我们设定的`foldmethod‘。下面是不同折叠规则对应的计算方式:
- ` manual‘
- 手工规则下,折叠层级由折叠区域的嵌套关系计算。当我们手工指定一个折叠的区域后,Vim对这个区域的开始行和结束行做记号,多个区域的开始行和结束行形成了嵌套关系。如果一个折叠区域不包含在其他区域之中,则其折叠层级为1;当这个区域直接包含于另一个区域时则其为折叠层级为另一个区域的层级加1;依些类推。
- ` indent‘
- 行的缩进宽度除以` shiftwidth‘,并向下取整得到每一行的折叠层级。同一折叠层级及更高折叠层级的连续行形成折叠。而其中的更高折叠层级的行——如果有的话,形成嵌套的折叠。
- ` marker‘
- 当使用 标记规则折叠时,层级的计算跟手工规则相似。除了它是根据文件中的 标记来划分一个折叠区域而不是手工指定。然后根据这些区域间的嵌套关系计算折叠层级。具体使用的 标记通过` foldmarker‘设置。默认是使用'{{{,}}}'。
- ` syntax‘
- 跟` marker‘差不多,只是所用的 标记是在语法文件中定义的,而不是通过` foldmarker‘设置。
- ` diff‘
- 除了差异行及其前后三行 1外,其余行折叠(层级为1)。
- ` expr‘
- 由用户指定折叠层级的计算方式。方法是对` foldexpr‘进行设置。具体用法稍后说明。
2.2 手工规则(manual)
首先设置设置foldmethod为manual或者marker(没错marker也行)。然后高亮选择可折叠的行,输入指令`zf‘。就这么简单你已经折叠了文本。还可以在命令行用:fold用法跟其他Vim指令一样,如`:1,.fo‘表示将第一行到当前行都折叠起来。需要注意的是如果使用marker折叠规则的话新建折叠时Vim会为指定范围的开始和结束行添加标记(marker)。
注意:如果只折叠一行的话,你可能不会立即看到效果。
2.3 缩进规则(indent)
首先,设置规则`:set foldmethod=indent‘。然后在编辑过程中依需要进行缩进2。
缩进挺容易理解的,将下面的例子复制到文件中保存关闭再用Vim打开看看:
开始 第一行 折叠层级为1,无嵌套 第二行 第三行 折叠层级为2,第1层嵌套 第四行 第五行 折叠层级为3,第2层嵌套 第六行 第七行 折叠层级为2,第1层嵌套 第八行 折叠层级为1,无嵌套 第九行 折叠层级为3,无嵌套 第五行 结束 vim: shiftwidth=4:foldmethod=indent
注意:正如前面提到的折叠的层级并不仅仅取决于你按了几个空格或制表符还与`shiftwidth‘有关。改变上面例子中的`shiftwidth‘看有什么不一样。
2.4 标记规则(marker)
见手工规则。
3 expr规则
expr要比前面的几种方式复杂点所以我们在这里单独讨论。使用expr时,我们需要对一设置项进行设置——`foldexpr‘。它是用来保存我们设定的表达式,也就是我们指定的计算折叠层级的公式。我们将设置项设为特定的表达式 -> 然后Vim通过`foldexpr‘这个项得到我们所指定的表达式 -> Vim逐行处理,并对每一行使用这个表达式计算出一个数值。这个数值就是该行的折叠层级 -> 根据层级进行折叠。举个例子:假设我们在Vim中使用了如下命令:
:set foldmethod=expr :set foldexpr=1
所有的行都会被折叠(如果没有的话再输入指令`zM‘)。Vim在每一行用foldexpr指定的表达式计算结果。这个例子中我们的表达式是1,所以每行得到的折叠层级都是1,于是所有行折叠成一行。同理如果你将它赋于2的话则所有行的折叠层级为2,所有行被折叠成一行。当我们的表达式最后返回一个数值时这个数值就是折叠层级。为了让这表达式更灵活,更能满足我们的需要我们需要补足一些Vim脚本知识3,这里是三个在折叠的表达式比较常用几点:
- ` v:lnum‘
- 内置变量,表示是“当前行的行号”。:help v:var查看更多内置变量。
- ` getline()‘
- 函数用以返回指定行的内容。
- ` ?:‘
- 三元条件语句。见:help expr1
另外如果需要构造复杂的表达式,我们可以在自定义的函数中定义。但这又是另一篇教程了。
看一下现在我们能构造出怎么样的表达式:
:set foldexpr=v:lnum
指定每行的行号为折叠层级,这样所有行会折叠成一行。试试看用指令`zo‘逐层打开折叠,打开20层之后就没有折叠了——而不是我们想的“嵌套数=行数”。这是因为Vim对折叠的嵌套数是有限制的,默认最深可以20层。但我们可以用foldnestmax这个项来进行自定义设置,如`set foldnestmax=10‘。
注意:Vim最多支持20层的嵌套,所以设置超过20的值会被当成20。
将第8至第20行折叠。也就是让expr在第8至20行时返回数值1(也可以是2、3、4……),其他时候为0。我们知道Vim是逐行处理的,所以如果我们知道Vim正在处理的行的行号我们就可以进行比较了。当然你已经知道了,我们需要的就是`v:lnum‘:
v:lnum>=8&&v:lnum<=20?1:0 v:lnum>=8 && v:lnum<=20 ? 1:0
下面是用expr规则模拟indent规则的例子。基本上没有任何实用价值,但至少能让我们又expr的强大有个认识:
" source this.vim set foldmethod=expr set foldexpr=Myindent(v:lnum) " 用expr模拟indent规则 func! Myindent(lnum) " 用indent()函数得到当前缩进 let s:idt=indent(a:lnum) " 用&操作符得到一个设置项shiftwidth的值 let s:sw=&shiftwidth " 如果有缩进宽度超过阈值(shiftwidth) if s:idt>=s:sw " 则计算折叠层级 return (s:idt-s:idt%s:sw)/s:sw else return 0 endf
除了可以向foldexpr返回数值外还可以返回一些特殊的值,分别是=, a, s, (详细说明见`:help fold-expr‘,这里不现重复)。Vim的文档不推荐使用=, a, s。
这里再给两个简单的例子:
" 如果一行以@samp{#}开始,折叠。 :set foldexpr=getline(v:lnum)=~/^#/?1:0
" 以每5行为一组折叠 set v:lnum%5-1?1:'>1'
4 实例演示
折叠在写程序时是比较常用的比如python、C语言之类可以将规则设置为`indent‘进行折叠。但我说过了折叠可以有很多不同的用法。下面简单的举几个例子演示一下折叠还能怎么用。由于这里面有些内容需要用到Vim脚本的知识,我不会在这里详细解说,有不明白的地方可以发邮件给我。
4.1 唐诗
这里我借唐诗的例子演示一下expr规则和`foldtext‘设置项的用法。下面是一个文件的内容,这是一个有80首诗的文件每首诗之间有一空行。我的阅读的习惯是看一下目录然后挑自己感兴趣的部分。所以我决定用折叠来模拟目录的效果。
《感遇其一》 作者:张九龄 兰叶春葳蕤,桂华秋皎洁。 欣欣此生意,自尔为佳节。 谁知林栖者,闻风坐相悦。 草木有本心,何求美人折? 《感遇其二》 作者:张九龄 江南有丹桔,经冬犹绿林。 岂伊地气暖,自有岁寒心。 可以荐佳客,奈何阻重深。 运命唯所遇,循环不可寻。 徒言树桃李,此木岂无阴。 《下终南山过斛斯山人宿置酒》 作者:李白 暮从碧山下,山月随人归。 却顾所来径,苍苍横翠微。 相携及田家,童稚开荆扉。 绿竹入幽径,青萝拂行衣。 欢言得所憩,美酒聊共挥。 长歌吟松风,曲尽河星稀。 我醉君复乐,陶然共忘机。
首先,确定使用的折叠规则。大概的看一下前面的几种规则,看来我们只能用`expr‘规则了。因为我们要将空行以外的所有部分折叠起来。所以我们可以构造这样的表达式:
" 用getline(v:lnum)得到当前行 " 用正则表达式@samp{.}判断当前行是否含有文字。 set foldexpr=getline(v:lnum)=~'.'?1:0
将上面的唐诗复制到一文件中,用Vim打开,设置折叠。是不是看到所有的诗折叠了。不过还有一个问题,在折叠文本(`foldtext‘)中没有注明诗的作者这样的话这个目录似乎有点美中不足。不过我们可以定义折叠文本(:help 'foldtext')的:
" 想显示诗人? set foldtext=foldtext().v:folddashes.getline(v:foldstart+1)
通过设置`foldcolumn‘,我们还可以使用鼠标来打开或折叠一首诗。最后我们可以在该文件的最后加入一模式行这样我们每次打开都有折叠的效果了:
vim: ro: fdm=expr: fde=getline(v:lnum)=~'.'?1:0: foldtext=foldtext().v:folddashes.getline(v:foldstart+1): foldcolumn=2
上面的折叠表达式中不同的折叠(诗)之间有一空行,如果希望显示效果更紧凑一点的话试一下这个表达式:
set foldexpr=getline(v:lnum)=~'S'&&getline(v:lnum-1)!~'S'?'>1':'='
4.2 笔记
在阅读文本文件或者多人共同创作说明文档时,经常需要作一些笔记。这些笔记可以是读书心得、体会,也可以是创作说明或者作为以后扩充内容的占位符。如果有很多的笔记的话会影响原文档的可读性,通过折叠笔记我们可以在保留这些笔记的前提下,保证文档的可读性。
- 使用模式行vim: se fdm=marker
- 选择合适的地方写下自已的想法或注释。
- 使用`zf‘或`:fo‘进行折叠。
这样自己做笔记的地方总是高亮显示。
同时可以使用
:folddoclosed .w! >>笔记.txt
来导出笔记。
注意::folddoclosed4只对当前关闭的折叠有效,所以如果要导出所有折叠可以先使用指令`zR‘。如果要删除所有的marker,`:g/{{{/norm zD‘
4.3 邮件
我习惯在本地保存一份邮件的副本但有时候在翻以前的邮件时很不方便。因为邮件头通常很长尤其是邮件列表(maillist)中的邮件,有时要看正文得先按好几次CTRL-F。当然在学了折叠后我们当然有更好的办法。
我们以下面这封精简过邮件头并作了适当的修改(防垃圾邮件:()的邮件为例:
Received: from sino.cmm (unknown [202.108.xx.230]) Received: (qmail 25748 invoked by uid 99); 30 Dec 2004 04:42:47 -0000 Message-ID: <[email protected]> From: chxxxx To: [email protected] Subject: RE: XX报告 MIME-Version: 1.0 Date: Thu, 30 Dec 2004 12:42:47 +0800 X-Priority: 3 hqxxe: 你的报告已经收到,谢谢! 陈 ----- Original Message ----- > ……………… > blah blah >> blah blah >> blah blah
在'.vimrc'(windows中是'_vimrc')中加入,下面的内容:
" 根据邮件的后缀名进行相关的设置。如果打开的文件后缀名是'.eml',则当成邮件处理。 autocmd! BufReadPre *.eml se fdm=expr fde=v:lnum==1?1:getline(v:lnum)=~'^$'?0:'=' fdt=Mailfdt(v:foldstart,v:foldend) ft=mail | syn on " 定义函数,用来返回折叠的标题。 " 以折叠的第一和最后一行的行号为参数 func! Mailfdt(fst,fen) let fst=a:fst " 保存邮件的标题和发信人 let hfrom='' let hsub='' let tline='' while a:fen!=fst let tline=getline(fst) " 判断当前行是否是我们感兴趣的行 " 如果是则保存 if tline=~'^From: ' let hfrom=tline elseif tline=~'^Subject: ' let hsub=tline endif let fst=fst+1 endwhile " 返回相关信息 if strlen(hfrom) || strlen(hsub) return hsub . "ttt" . hfrom else return getline(a:fst) endif endfunc
在加入上面的内容后,我们现在用Vim打开邮件(实际是以.eml作后缀名的文件)看看,是不是清爽多了!
5 使用提示
使用manual一般是临时性的折叠。如果每次编辑特定文件都需要做同样折叠时时建议结合modeline使用其他折叠规则。如果不得不使用manual方式时,你可以用:mksession保存包括折叠在内的一切当前编辑设置或者用:mkview保存当前窗口。具体说明见文档。
indent和marker的折叠规则可以用于程序文件或格式文本。一般可以配合modeline使用。
syntax通常用于程序或特定格式的文本。并且由于是在语法文件中定义的所以一般与autocommand一起使用。:help filetype :help command
diff用于比较文件内容后对照修改时使用。可用:mkview或:mksession保存设置。
expr在上面几种方式无法满足需要时我们要使用expr方式。expr可以在模式行中保存设置,也可以保存在Vim的脚本中。相关命令:mkvimrc :mksession
6 小结
这一篇中我们简单的介绍了Vim中进行折叠的原理,及几种相关的折叠规则及其使用方法。expr规则实际是一种自定义规则,在学习了Vim脚本后我们还能构造出更复杂的规则。但就现在而言用户掌握了manual、indent和marker就行了(虽然在前面我们举了许多expr的例子,但在实际运用中expr用得比较少,因为通常用户不会绞尽脑汁只为了将文本折叠起来——包括我自己。当然你想要成为进阶用户这是一定要掌握的!)syntax和diff方式会另外讲解,敬请期待。
下一篇我们将开始讨论与编辑相关的一些内容。下次见。
Appendix A
这是比较不常用但又可能比较有用的内容。使用:help查看相关信息。
v:foldstart | 内置变量 | 只读变量记录只前所在折叠的起始行号 |
v:foldend | 内置变量 | 结束行号,其余同上 |
foldlevel() | 函数 | 返回指定行的折叠层级 |
'foldlevel' | 设置项 | 只有高于这个值的折叠层级才会进行折叠 |
'foldnestmax' | 设置项 | 指定最深的嵌套数 |
'foldignore' | 设置项 | 在indent规则中以这个值开始的行的将根据前后行的值来设定折叠层级 |
:folddoclose | 命令 | 对当前闭合的行运行命令 |
:folddoopen | 命令 | 对未折叠的行或定义了折叠但未闭合的行运行命令 |
Footnotes
[1] 具体行数可通过`diffopt‘选项变更
[2] 可以试试自动缩进的功能`:se ai‘——如果你还没用上的话
[3] 更多内容参考`:help vim-script‘
[4] :help :folddoopen :help :folddoclosed
转自http://blah.blogsome.com/2006/04/13/vim_tut_folding/