一篇文章吸取 Vim 全部精华(上)

阅读数:2409 2019 年 10 月 18 日 22:10

一篇文章吸取Vim全部精华(上)

本文翻译自“ History and effective use of Vim ”,翻译已获得原作者Joe Nelson授权。

这篇文章研究了 Vim 的发展历史,并吸取了 Vim 用户手册的全部精华。希望文中内容可以帮你发现(或者再次想起)这个编辑器的核心功能,让你可以摆脱安装包中自带的配置文件 vimrc,更有创意地使用各种插件。

一篇文章吸取Vim全部精华(上)

如果想以这篇文章的内容为基础进行进一步研究,我建议准备一份纸质版的用户手册,或者一本比较好的口袋书。我没能找到官方 Vim 用户手册的纸质版。随编辑器发布的$VIMRUNTIME/doc/usr_??.txt系列文件中有个打印出来读得更舒服的 PDF 版,最后我只好通过 printme1.com 把它打印了出来。如果只是想要个方便的命令列表,我推荐《 vi and Vim Editors Pocket Reference 》。

内容列表

  • 历史
  • 配置文件架构
  • 第三方插件
  • 备份与撤销
  • 包含与 path
  • 编辑与编译周期
  • 对比与补丁
  • Buffer I/O
  • 文件类型
  • 不要忘了鼠标
  • 各种编辑

历史

Vi 的诞生

Vi 命令及各种功能的发展要从 QED 编辑器说起,至今已经有 50 多年了。下面是一些关键的时间点:

  • 1966 年:伯克利分时系统中发布了 QED(“Quick EDitor”),即快速编辑器
  • 1969 年 7 月:人类第一次登上月球(仅供对比时间用)
  • 1969 年 8 月:在 AT&T,QED 发展成了 ed
  • 1976 年 2 月:在伦敦玛丽女王大学,ed 发展成了 em(“Editor for Mortals”,普通人的编辑器)
  • 1976 年:在加州大学伯克利分校,em 发展成了 ex(“EXtended”)
  • 1977 年 10 月:ex 有了可视化模式,即 vi

如果你去读读 QED ex 的用户手册,就会发现它们的许多相似之处。它们使用的语法相近,都以行为单位进行各种操作。

QED、ed 和 em 之类的编辑器都是为实体版终端设计的,基本上就是指连接了调制解调器的电子打字机。实体版终端会直接把系统输出打印在纸上。很明显,一旦打印就无法修改了,因此编辑的过程就是用许多用户命令去修改并手动打印一行行的文本。

一篇文章吸取Vim全部精华(上)

实体版终端

在 1976 年出现了 ADM-3A 之类的可视终端。于是 ex 编辑器引入了“开放模式”,支持在可视终端上进行行内编辑。还引入了可视化模式,可以在屏幕上用移动焦点的方式进行编辑。可视化模式用 vi 命令激活,会在屏幕上保持文件的最新视图,屏幕的最下方就是 ex 的命令行。有趣的是,ADM-3A 的 h、j、k、l 键上面也标记了方向箭头,因此 vi 对方向键的选择也是与键盘上的按键布局匹配的。

一篇文章吸取Vim全部精华(上)

要想知道更多关于 ex 和 vi 的故事,可以看看对 Bill Joy 的访谈。他谈到了他是如何创造 ex 和 vi 的,以及对它们还有哪些不满意之处。

最初的 vi 只是 ex 的另一个版本——二进制文件是相同的,用不同的命令运行起来,就会进入 ex 模式或者 vi 模式。从这段发展历史可以看出,ex 和 vi 是在使用的过程中不断演进的,它们需要的系统资源极少,操作所要占用的带宽也极少。它们在大多数系统上都可用,并在 POSIX 上得到了全面推广

从 vi 到 vim

作为 ed 的衍生物,ex 和 vi 编辑器的知识产权都属于 AT&T。要在 Unix 之外的平台上使用 vi,大家只能自己写克隆版。

下面是一些克隆版的列表:

  • nvi:1980 年为 4BSD 设计
  • calvin:1987 年为 DOS 设计
  • vile:1990 年为 DOS 设计
  • stevie:1987 年为 Atari ST 设计
  • elvis:1990 年为 Minix 和 386BSD 设计
  • vim:1991 年为 Amiga 计算机设计
  • viper:1995 年为 Emacs 设计
  • elwin:1995 年为 Windows 设计
  • lemmy:2002 年为 Windows 设计

我们只关注中间的 vim。Bram Moolenaar 想在 Amiga 计算机上使用 vi,因此他着手从 Atari 上把 Stevie 移植过来,并继续演进。他把这次移植行动称为“仿制 Vi”(Vi IMitation)。从自由软件杂志对 Bram 的访谈中可以获得更多第一手信息。

到了 1.22 版,Vim 的意义被重新解释成了“改进版 Vi”(Vi IMproved),功能与原版 Vi 相比甚至有所超越。下面是发布了重要功能的一些重大版本列表:

  • 1991 年 11 月 2 日,Vim 1.14 版:首发版
  • 1992 年,Vim 1.22 版:移植回 Unix。现在 Vim 已经可以与 Vi 相提并论了
  • 1994 年 8 月 12 日,Vim 3.0:支持多个缓冲区和窗口
  • 1996 年 5 月 29 日,Vim 4.0:图形用户界面(主要归功于 Robert Webb)
  • 1998 年 2 月 19 日,Vim 5.0:增加了语法着色和高亮显示
  • 2001 年 9 月 26 日,Vim 6.0:折叠、插件和垂直分割
  • 2006 年 5 月 8 日,Vim 7.0:引入了拼写检查、万能补全、分枝撤销、标签页编辑等功能
  • 2016 年 9 月 12 日,Vim 8.0:引入了任务、异步 I/O、原生包等

想了解更多有关各个版本的信息,可以直接查看:help vim8命令的执行结果。想了解未来的规划和已知缺陷等,可以查看:help todo.txt的输出。

竞争对手 NeoVim 的开发者希望可以直接在编辑器中运行其网页脚本语言的调试器和 REPL。在这个压力下,Vim 8.0 发布了异步任务支持功能。

Vim 的可移植性非常好,而且由于支持的平台越来越多,它的可移植性就保持得更好了。它可以运行的平台包括 OS/390、Amiga、BeOS 和 BeBox、Macintosh、Atari MiNT、MS-DOS、OS/2、QNX、RISC-OS、BSD、Linux、OS X、VMS 和 MS-Windows 等。不管你用的是哪种计算机,你都能找到可用的 Vim。

不管怎样演进,最初版的 ex/vi 源码都是在 2002 年基于 BSD 自由软件协议 ex-vi.sourceforge.net 发布的。

接下来我们回到正题。在讨论各种奇技淫巧之前,我们先了解一下 Vim 是如何组织并解析它的配置文件的。

配置文件结构

我以前总是错误地以为,Vim 只从~/.vimrc 这一个文件中读取所有的设置和脚本。随便查看一下各种其它配置文件,你会更加坚定这个想法。人们总喜欢发布一些非常大的.vimrc 配置文件,以控制编辑器的方方面面。这些大配置文件常被叫成“Vim 发行版”。

事实上 Vim 的配置是有简单结构的,.vimrc 文件只是多个输入之一。你也可以查看它到底加载了哪些脚本。你可以随便打开一个源文件,然后运行命令:

复制代码
:scriptnames

花点时间仔细读读输出的列表,猜猜每个脚本的功能,并留意一下它们的存放位置。

列表是不是比你想像中要长?如果你还安装了一些插件的话,编辑器要做的事就更多了。如果你想了解是什么原因造成编辑器启动变慢了,可以运行如下命令,并查看它创建的start.log文件:

复制代码
vim --startuptime start.log name-of-your-file

如果不使用你现有的配置,可以比较一下看 Vim 的启动能有多快:

复制代码
vim --clean --startuptime clean.log name-of-your-file

要确定在启动或加载缓冲区的时候该运行哪些脚本,Vim 会遍历“运行时路径”。这个路径是一个由逗号分割的目录列表,每个目录下面都可以包含一套配置结构。按在列表中出现的顺序,Vim 检查每个目录下面的结构,找到要运行的脚本。

运行以下命令可以得到你的系统里的运行时路径:

复制代码
:set runtimepath

在我的系统里,运行时路径的默认配置如下。并不是列表中的每个目录都必须存在,但如果存在,就一定会被检查到:

  • ~/.vim:根目录,存放个性化配置
  • /usr/local/share/vim/vimfiles:系统级的 Vim 目录,存放系统管理员的个性化配置
  • /usr/local/share/vim/vim81:即众所周知的 $VIMRUNTIME,存放随 Vim 发布出来的文件
  • /usr/local/share/vim/vimfiles/after:系统级 Vim 目录里面的 after 目录。这是让系统管理员对发行版默认配置进行覆盖和补充的
  • ~/.vim/after:根目录中的 after 目录。用于对发行版进行个性化的覆盖和补充

因为目录是按它们出现在列表中的顺序处理的,因此 after 目录的唯一不同之处仅仅在于它们出现在了列表的末尾。after 这个词并没有什么特殊功能。

处理各个目录时,Vim 也会查找有特定名字的子目录。要了解得多,请查看:help runtimepath。我们对下面这些简单介绍一下。

  • plugin/:编辑任何文件时都会自动加载的脚本,称为“全局插件”
  • autoload/:为了与 plugin 相区别,只有在其它脚本需要时,这个目录下的脚本里包含的功能才会被加载
  • ftdetect/:用于检测文件类型的脚本。可以根据文件扩展名、位置或文件内容的不同,而产生不同行为
  • ftplugin/:编辑已知类型的文件时要运行的脚本
  • compiler/:定义如何运行各种不同的编辑器、静态分析器等,以及如何解析它们的输出。在多个 ftplugin 之间也可以共享。而且它也不是自动应用的,需要通过:compiler调用
  • pack/:存放 Vim 8 原生程序包的地方,是 Pathogen 风格包管理的继任者。原生程包系统不需要依赖任何第三方代码

最后,~/.vimrc是所有通用编辑器设置的大杂烩。用它进行默认配置,配置项会进一步被具体文件类型的配置覆盖。要了解在.vimrc 中支持的所有配置项,请运行:options命令。

第三方插件

插件只是一些 Vim 脚本而已,要想能运行,就要把它们放到运行时路径里的正确位置。安装过程用一句话就可以概括:把文件放入正确目录。但难点在于删除或更新某些插件,因为脚本会在运行时路径的子目录里到处乱放文件,很难辨别哪个文件是属于哪个插件的。

于是各种“插件管理器”就出来救驾了。根据记载,从 2003 年起 Vim.org 就已经有了这种插件注册表了,但直到 2008 年才真正有插件管理器流行起来。

这类工具可以把各个插件的路径加到 Vim 的运行时路径中,并编译出插件文档的帮助标签。大多数插件管理器也可以从互联网上安装或更新插件代码,更新过程有时是并行的,或者有彩色进度条。

下面按时间顺序列出了各种插件管理器的信息。起止时间表示每种插件管理器的最早和最新发布时间。如果找不到正式的发布消息,也可能是最早和最新的代码提交时间。

  • 2006 年 3 月 -2014 年 7 月: Vimball (一种分发格式,并与 Vim 命令相关联)
  • 2008 年 10 月 -2015 年 12 月: Pathogen (出现原生 vim 包之后就废弃了)
  • 2009 年 8 月 -2009 年 12 月: Vimana
  • 2009 年 12 月 -2014 年 12 月: VAM
  • 2010 年 8 月 -2010 年 11 月: Jolt
  • 2010 年 10 月 -2012 年 11 月: tplugin
  • 2010 年 10 月 -2014 年 2 月: Vundle (被 NeoBundle 敲了竹杠之后就没有继续了)
  • 2012 年 3 月 -2018 年 3 月: vim-flavor
  • 2012 年 4 月 -2016 年 3 月: NeoBundle (出现 dein 之后就废弃了)
  • 2013 年 1 月 -2017 年 8 月: infect
  • 2013 年 2 月 -2016 年 8 月: vimogen
  • 2013 年 10 月 -2015 年 1 月: vim-unbundle
  • 2013 年 12 月 -2015 年 7 月: Vizardry
  • 2014 年 2 月 -2018 年 10 月: vim-plug
  • 2015 年 1 月 -2015 年 10 月: enabler
  • 2015 年 8 月 -2016 年 4 月: Vizardry 2
  • 2016 年 1 月 -2018 年 6 月: dein.vim
  • 2016 年 9 月 - 今:native in Vim 8
  • 2017 年 2 月 -2018 年 9 月: minpac
  • 2018 年 3 月 -2018 年 3 月: autopac
  • 2017 年 2 月 -2018 年 6 月: pack
  • 2017 年 3 月 -2017 年 9 月: vim-pck
  • 2017 年 9 月 -2017 年 9 月: vim8-pack
  • 2017 年 9 月 -2019 年 5 月: volt
  • 2018 年 9 月 -2019 年 2 月: vim-packager
  • 2019 年 2 月 -2019 年 2 月: plugpac.vim

大家首先可以注意到的就是这些工具之间的千差万别,其次是每种工具平均活跃 4 年,然后就慢慢退出舞台。

使用 Vim 8 内置的插件管理功能是最稳定的,而且不需要任何第三方代码。下面我们来了解一下。

首先在你的运行时路径下的 pack 目录里,创建 opt 和 start 两个目录。

复制代码
mkdir -p ~/.vim/pack/foobar/{opt,start}

请注意占位符“foobar”,这个名字完全由你决定,它用来将里面的包分类。许多人直接把所有插件都归入一个没有描述的分类中,这样也是可以的。你喜欢什么名字,就用什么名字,我接下来会继续使用 foobar。理论上你也可以创建多个分类,比如~/.vim/pack/navigation 和~/.vim/pack/linting。注意 Vim 并不会对分类进行去重检查,如果有重复的,它只会直接再加载一次。

“start”目录中的包会被自动加载,而“opt”目录中的包只会在 Vim 中用:packadd命令请求之后才会加载。对于使用频率较低的包,放在 opt 里是个不错的选择,这样 Vim 就可以不运行不必要的脚本,从而运行得更快。请注意没有:packadd的逆操作,即无法取消对包的加载。

下面举个例子,我们把模糊查询插件“ctrlp”加到 opt 里。用下面的命令下载并解压最新版:

复制代码
curl -L https://github.com/kien/ctrlp.vim/archive/1.79.tar.gz \
| tar zx -C ~/.vim/pack/foobar/opt

这个命令会创建一个~/.vim/pack/foobar/opt/ctrlp.vim-1.79 目录,然后这个包就可用了。回到 Vim 中,为新的包创建一个帮助标签索引:

复制代码
:helptags ~/.vim/pack/foobar/opt/ctrlp.vim-1.79/doc

这条命令会在包的 doc 目录中创建一个名为“tags”的文件,这样在 Vim 的内部帮助系统中浏览时就可以看到相应的主题了。你也可以在包被加载后运行:helptags ALL命令,它会处理运行时目录中的所有文档。

当你想要使用这个包时,直接加载就可以了。另外请记住插件名是可以用 tab 键自动补齐的,所以不必打出完整的名字:

复制代码
:packadd ctrlp.vim-1.79

packadd 会在运行时路径中包含包的根目录,并加载插件和 ftdetect 脚本。加载完毕后,敲下 Ctrl+P 按键,就会弹出模糊查找匹配器了。

有些人会对~/.vim 目录做版本控制,并用 git 来管理各个包。我习惯于直接把 tar 包解压到自己的仓库中。如果你使用的包比较稳定,就不需要经常升级,而且脚本通常很小,不要把 git 的修改历史搞得太长。

备份与撤销

用户可以进行设置,防备四种类型的数据丢失:

  1. 两次保存之间,编辑过程中的崩溃:Vim 可以周期性地将未保存的修改写入一个交换文件,来防止这类数据丢失。
  2. 打开的两个 Vim 实例编辑了相同的文件,彼此之间的改动相互覆盖:交换文件也可以防止这类数据丢失。
  3. 在保存的过程中崩溃,即在目标文件已经清空而新的内容还没有完全写入时崩溃:Vim 用“写备份”的方式进行保护,即先写入一个新文件,在成功之后再与旧文件相切换,这要依赖“backupcopy”选项。
  4. 已经保存了新内容,但又想把旧内容找回来:Vim 会在写入之后保存旧文件的副本,以此来支持这项功能。

在查看更多重要设置之前,要不要先来点好玩的放松一下?下面是 GitHub 上对 vimrc 文件的一些留言:

  • 别创建什么交换文件了,用版本控制来管理吧。
  • 还搞什么备份啊,用版本控制不就得了。
  • 赶紧 TMD 用版本控制!
  • 我们生活在充满版本控制的世界里,所以别用交换文件和备份了。
  • 别写备份文件了,版本控制够用了。
  • 我到现在都没真的用过 Vim 的备份文件……用版本控制吧。
  • 反正大多数东西都用上版本控制了。
  • 禁用掉备份文件,你就用上版本控制系统了。:)
  • 版本控制驾到,Git 来拯救大家了!
  • 禁用交换文件和备份,一直使用版本控制,一直!
  • 关掉备份!什么东西我都用版本控制来管理。

在上面谈到的四种情况里,这些留言其实只与第四种有关(第三种勉强相关)。笔者本人一般也是禁用交换文件的,对前两种情况不设防。

我推荐使用下面的配置,来保证可以安全地进行编辑:

复制代码
" Protect changes between writes. Default values of
" updatecount (200 keystrokes) and updatetime
" (4 seconds) are fine
set swapfile
set directory^=~/.vim/swap//
" protect against crash-during-write
set writebackup
" but do not persist backup after successful write
set nobackup
" use rename-and-write-new method whenever safe
set backupcopy=auto
" patch required to honor double slash at end
if has("patch-8.1.0251")
" consolidate the writebackups -- not a big
" deal either way, since they usually get deleted
set backupdir^=~/.vim/backup//
end
" persist the undo tree for each file
set undofile
set undodir^=~/.vim/undo//

这些设置开启了写入过程中的备份,但在写入成功之后并不会保留备份文件,因为有版本控制。注意你要自己创建目录mkdir ~/.vim/{swap,undodir,backup},不然 Vim 就会去偏好列表里找下一个可用的目录了。你也可以考虑修改目录权限,让里面的内容对外不可见,因为交换文件和撤销历史里可能会包含着敏感信息。

有一点要注意的是,配置文件中的路径是以两个斜杠结尾的。这样就启用了一个功能,可以让交换和备份文件与其它目录中的同名文件相区别。比如/foo/bar的交换文件会被保存成~/.vim/swap/%foo%bar.swp(斜杠转义成了百分号)。Vim 有个缺陷,用双斜线来表示备份目录会有问题,这个缺陷直到最近的补丁才修复,而用上面的方法就可以避开这个缺陷。

Vim 也保留了每个文件的撤销历史,这样即使关闭了文件,当你再次打开时,仍然可以继续使用撤销功能。尽管听起来好像和交换文件的功能有些冗余,但实际上撤销历史是交换文件的补充,因为仅当文件被写入时,才会同时写入撤销历史。为什么要保持和文件写入相同的频率,而不是更频繁呢?原因在于发生崩溃时,撤销历史的状态和磁盘上文件的状态很可能不匹配,这不是 Vim 期望的。

说起撤销,Vim 实际上维护了完整的编辑历史。这意味着你可以修改一下,再撤销修改,再做一次别的修改,这三个状态都是可恢复的。通过:undolist命令可以看到修改的次数和重要性,但从这个列表中很难将树形结构用可视化的方式表达出来。你可以跳到这个列表中的某些具体修改的位置;也可以用:earlier:later命令带上 5m 这类表示时间的参数,来按时间单位跳转;或者用文件的保存次数跳转,比如 3f。不过我觉得,展示撤销历史树这个功能应该由 undotree 之类插件来完成。

打开灾难恢复设置可以让你获得心理上的安全感。以前我编辑了一阵之后,或者从电脑前走开时,总是会习惯性地保存一下。但现在做了这些设置之后,我可以一连编辑几个小时也不保存了,因为我知道交换文件的工作机制了。

最后要提醒的一点是,请留意这些灾难恢复文件,它们可能会堆满你的.vim 目录,慢慢地耗尽磁盘空间。而且在磁盘空间剩余不多又要保存大文件时,就有必要设置 nowritebackup,否则 Vim 有可能会把整个文件临时再拷贝一份。默认设置中的“backupskip”选项会禁止在系统 temp 目录下备份任何东西。

Vim 的“patchmode”是个与备份相关的功能。如果你的目录还没有做版本管理,就可以用用它。比如你想下载个源码包,修改一下形成一个补丁,再通过电子邮件发送出去,而不是通过 git 提交。那么就运行:set patchmod=.orig命令,这样在你修改任何文件之前,原文件都会被加上.orig 后缀备份起来。然后,你就可以在命令行上创建.orig 文件和新文件之间的补丁了。

请继续阅读“Vim 发展历史及高级用法(下)”来了解本文剩余内容。

原文链接
History and effective use of Vim

评论

发布