跳到主要内容

第24章 Vim Rumtime

在前面的章节中,我提到Vim会自动查找一些特殊的路径,比如在~/.vim/ 中的 pack/(第23章) compiler/(第19章)。这些都是Vim runtime路径的例子。

除了上面提到的两个,Vim还有更多runtime路径。在本章,您将学习关于Vim runtime路径的高层次概述。本章的目标是向您展示它们什么时候被调用。知道这些知识能够帮您更进一步理解和定制Vim。

Runtime路径

在一台Unix机器中,其中一个vim runtime路径就是 $HOME/.vim/ (如果您用的是其他操作系统,比如Windows,您的路径可能有所不同)。要查看不同的操作系统有什么样的runtime路径,查阅 :h runtimepath。在本章,我将使用 ~/.vim/ 作为默认的runtime路径。

Plugin脚本

Vim有一个runtime路径 plugin,每次Vim启动时都会执行这个路径中的所有脚本。不要把这个名字 "plugin" 和Vim的外部插件(external plugins,比如NERDTree, fzf.vim, 等)搞混了。

进入 ~/.vim/ 目录,然后创建 plugin/ 子目录。 创建两个文件: donut.vimchocolate.vim

~/.vim/plugin/donut.vim里面:

echo "donut!"

~/.vim/plugin/chocolate.vim里面:

echo "chocolate!"

现在关闭Vim。下次您启动Vim,您将会看到 "donut!":chocolate! 的显示。此 plugin runtime路径可以用来执行一些初始化脚本。

文件类型检测

在开始之前,为保证检测能正常运行,确保在您的vimrc中至少包含了下列的行:

filetype plugin indent on

查阅 :h filetype-overview 了解更多信息。本质上,这条代码开启Vim的文件类型检测。

当您打开一个新的文件,Vim通常知道这个文件是什么类型。如果您有一个文件 hello.rb,运行 :set filetype? 会返回正确的相应 filetype=ruby

Vim知道如何检测 "常见" 的文件类型(Ruby, Python, Javascript, 等)。但如果是一个自定义文件会怎样呢?您需要告诉Vim去检测它,并给它指派一个正确的文件类型。

有两种检测方法:使用文件名和使用文件内容

文件名检测

文件名检测使用一个文件的文件名来检测文件类型。当您打开 hello.rb文件时,Vim依靠扩展名 .rb 知道它是一个Ruby文件。

有两种方法实现文件名检测:一是使用 ftdetect runtime目录,二是使用 filetype.vim runtime文件。我们两个都看一看。

ftdetect/

让我们创建一个古怪(但优雅)的名字,hello.chocodonut。当您打开它后运行 :set filetype? ,因为它的后缀名不是常见的文件名,Vim不知道它是什么类型,会返回 filetype=

您需要指示Vim将所有以 .chocodonut结尾的文件设置为 "chocodonut"类型的文件。在runtime路径根目录(~/.vim/)创建一个子目录,名为 ftdetect/ 。在子目录里面,再创建一个名叫 chocodonut.vim 的文件(~/.vim/ftdetect/chocodonut.vim),在文件里面,添加:

autocmd BufNewFile,BufRead *.chocodonut set filetype=chocodonut

当您创建新buffer或打开新buffer时,事件BufNewFileBufRead 就会被触发。 *.chocodonut 意思是只有当新打开的buffer文件名后缀是 .chocodonut 时事件才会被触发。最后,set filetype=chocodonut 命令将文件类型设置为chocodonut类型。

重启Vim。新建一个 hello.chocodonut 文件然后运行 :set filetype?。它将返回 filetype=chocodonut.

好极了!只要您想,您可以将任意多的文件放置在 ftdetect/ 中。以后,如果您想扩展您的 donut 文件类型,你可以添加 ftdetect/strawberrydonut.vim, ftdetect/plaindonut.vim 等等。

在Vim中,实际上有两种方法设置文件类型。其中给一个是您刚刚使用的 set filetype=chocodonut。另一种方法是运行 setfiletype chocodonut。前一个命令 set filetype=chocodonut总是 设置文件类型为chocodonut。 而后者setfiletype chocodonut只有当文件类型尚未设置时,才会将文件类型设置为chocodonut。

文件类型文件

第二种文件类型检测需要你创建一个名为 filetype.vim的文件,并将它放置在根目录(~/.vim/filetype.vim)。在文件内添加一下内容:

autocmd BufNewFile,BufRead *.plaindonut set filetype=plaindonut

创建一个名为 hello.plaindonut 的文件。当你打开它后运行 :set filetype? Vim会显示正确的自定义文件类型 filetype=plaindonut

太好了,修改生效了。另外,如果您仔细看看 filetype.vim ,您会发现当您打开hello.plaindonut时,这个文件文件运行了多次。为防止这一点,您可以添加一个守卫,让主脚本只运行一次。更新 filetype.vim:

if exists("did_load_filetypes")
finish
endif

augroup donutfiletypedetection
autocmd! BufRead,BufNewFile *.plaindonut setfiletype plaindonut
augroup END

finish 是一个Vim命令,用来停止执行剩余的脚本。表达式"did_load_filetypes"不是 一个Vim内置函数。它实际上是$VIMRUNTIME/filetype.vim 中的一个全局变量。如果您好奇,运行:e $VIMRUNTIME/filetype.vim。您将会发现以下内容:

if exists("did_load_filetypes")
finish
endif

let did_load_filetypes = 1

当Vim调用这个文件时,它会定义 did_load_filetypes 变量,并将它设置为 1 。在Vim中,1 表示真。你可以试着读完 filetype.vim 剩余的内容,看看您是否能够理解当Vim调用它时干了什么。

文件类型脚本

让我们学习如何基于文件内容检测文件类型。

假设您有一个无扩展名的文件的集合。这些文件唯一相同的地方是,第一行都是以 "donutify" 开头。您现在想给这些文件指派一个 donut 的文件类型。创建新文件,起名为 sugardonut, glazeddonut, 还有 frieddonut(没有扩展名)。在每个文件中,添加下列内容:

donutify

当您在sugardonut中运行 :set filetype?,Vim无法知道应该给这个文件指派什么文件类型,会返回 filetype=

在runtime根目录,添加一个 scripts.vim 文件(~/.vim/scripts.vim),在文件中,添加一下内容:

if did_filetype()
finish
endif

if getline(1) =~ '^\\<donutify\\>'
setfiletype donut
endif

函数 getline(1) 返回文件第一行的内容。它检查第一行是否以 "donutify" 开头。函数 did_filetype() 是Vim的内置函数,当一个与文件类型相关的事件发生至少一次时,它返回真。它用来做守卫,防止文件类型事件反复运行。

打开文件 sugardonut 然后运行 :set filetype?,Vim现在返回 filetype=donut。如果您打开另外一个donut文件 (glazeddonutfrieddonut),Vim同样会将它们的文件类型定义为 donut 类型。

注意,scripts.vim 仅当Vim打开一个未知文件类型的文件时才会运行。如果Vim打开一个已知文件类型的文件,scripts.vim 将不会运行。

文件类型插件

如果您想让Vim仅当您打开一个 chocodonut 文件时才运行 chocodonut 相关的特殊脚本,而当您打开的是 plaindonut 文件时,Vim就不运行这些脚本。能否做到呢?

您可以使用文件类型插件runtime路径(~/.vim/ftplugin/)来完成这个功能。Vim会在这个目录中查找一个文件,这个文件的文件名与您打开的文件类型一样。创建一个文件,起名为chocodonut.vim (~/.vim/ftplugin/chocodonut.vim):

echo "Calling from chocodonut ftplugin"

创建另一个 ftplugin 文件,起名为plaindonut.vim (~/.vim/ftplugin/plaindonut.vim):

echo "Calling from plaindonut ftplugin"

现在,每次您打开一个 chocodonut 类型的文件时,Vim会运行 ~/.vim/ftplugin/chocodonut.vim中的脚本。每次您打开 plaindonut 类型的文件时,Vim会运行 ~/.vim/ftplugin/plaindonut.vim 中的脚本。

一个警告:每当一个buffer的文件类型被设置时(比如,set filetype=chocodonut),上述脚本就会运行一次。如果您打开3个不同的 chocodonut 文件,该脚本将运行 总共 3次。

缩进文件

Vim有一个 缩进runtime路径,其工作方式与ftplugin类似,Vim也会在这个目录中查找一个与打开的文件类型名字一样的文件。缩进runtime路径的目的是存储缩进相关的代码。如果您有文件 ~/.vim/indent/chocodonut.vim,它仅当您打开一个 chocodonut 类型的文件时执行。您可以将 chocodonut 文件中缩进相关的代码存储在这里。

颜色

Vim 有一个颜色runtime路径 (~/.vim/colors/) ,用来存储颜色主题。这个目录中的任何文件都会在命令行命令 :color 中显示出来。

如果您有一个文件 ~/.vim/colors/beautifulprettycolors.vim,当您运行 :color 然后按 Tab,您将会看到 beautifulprettycolors 出现在颜色选项中。 如果您想添加自己的颜色主题,就放在这个地方。

如果您想看其他人做的颜色主题,有一个好地方值得推荐:vimcolors

语法高亮

Vim有一个语法runtime路径 (~/.vim/syntax/),用来定义语法高亮。

假设您有一个文件 hello.chocodonut,在文件里面有以下内容:

(donut "tasty")
(donut "savory")

虽然Vim现在知道了正确的文件类型,但所有的文本都是相同的颜色。让我们添加语法高亮规则,使 "donut" 关键词高亮显示。创建一个新的 chocodonut 语法文件 ~/.vim/syntax/chocodonut.vim,在文件中添加:

syntax keyword donutKeyword donut

highlight link donutKeyword Keyword

现在重新打开 hello.chocodonut 文件,关键词 donut 已经高亮显示了。

本章不会详细介绍语法高亮。它是一个庞大的主题。如果您感兴趣,可以查阅 :h syntax.txt

vim-polyglot 插件非常的棒,它提供了很多流行的编程语言的语法高亮。

文档

如果您写了一个插件,您还得创建一个您自己的文档。您可以使用文档runtime路径完成这个。

让我们为 chocodonut 和 plaindonut 关键字创建一个基本文档。创建文件 donut.txt (~/.vim/doc/donut.txt)。在文件中,添加一下内容:

*chocodonut* Delicious chocolate donut

*plaindonut* No choco goodness but still delicious nonetheless

如果您试着搜索 chocodonutplaindonut (:h chocodonut:h plaindonut),您找不到任何东西。

首先,你需要运行 :helptags来创建新的帮助入口。运行 :helptags ~/.vim/doc/

现在,如果您运行 :h chocodonut:h plaindonut,您将找到上面那些新的帮助入口。注意,现在文件是只读的,而且类型是 "help"。

延时加载脚本

到现在,本章您学到的所有runtime路径都是自动运行的。如果您想手动加载一个脚本,可使用 autoload runtime路径。

创建一个目录名为 autoload(~/.vim/autoload/)。在目录中,创建一个新文件,起名为 tasty.vim (~/.vim/autoload/tasty.vim)。在文件中:

echo "tasty.vim global"

function tasty#donut()
echo "tasty#donut"
endfunction

注意,函数名是 tasty#donut 而不是 donut()。要想使用autoload功能,井号(#)是必须的。在使用autoload功能时,函数的命名惯例是:

function fileName#functionName()
...
endfunction

在本例中,文件名是 tasty.vim,而函数名是donut

要调用一个函数,可以使用 call 命令。让我们call这个函数 :call tasty#donut()

您第一次调用这个函数时,您应当会 同时 看到两条信息 ("tasty.vim global" 和 "tasty#donut") 。后面再调用 tasty#donut 函数,将只会显示 "testy#donut"。

当您在Vim中打开一个文件,不像前面说的runtime路径,autoload脚本不会被自动加载。仅当您显式地调用 tasty#donut(),Vim才会查找文件tasty.vim,然后加载文件中的内容,包括函数 tasty#donut()。有些函数会占用大量资源,但我们又不常用,这时候 Autoload runtime路径就是最佳的解决方案。

您可以在autoload目录任意添加嵌套的目录。如果您有一个runtime路径 ~/.vim/autoload/one/two/three/tasty.vim,您可以使用:call one#two#three#tasty#donut()来调用函数。

After脚本

Vim有一个 after runtime路径 (~/.vim/after/) ,它的结构是 ~/.vim/的镜像。在此目录中的任何脚本都会最后执行,所以开发者通常使用这个路径来重载脚本。

比如,如果您想重载 plugin/chocolate.vim 中的脚本,您可以创建~/.vim/after/plugin/chocolate.vim来放置重载脚本。Vim将会先运行 ~/.vim/plugin/chocolate.vim然后运行 ~/.vim/after/plugin/chocolate.vim

$VIMRUNTIME

Vim有一个环境变量 $VIMRUNTIME 用来加载默认脚本和支持文件。您可以运行 :e $VIMRUNTIME查看。

它的结构应该看起来很熟悉。它包含的很多runtime路径都是我们本章前面学过的。

回想第22章,当您打开Vim时,它会在6个不同的位置查找vimrc文件。当时我说最后一个位置就是 $VIMRUNTIME/default.vim,如果Vim在前5个位置查找用户vimrc文件失败,就会使用default.vim 作为vimrc。

不知您是否尝试过,运行Vim是不加载比如vim-polyglot之类的语法插件,但您的文件依然有语法高亮?这是因为当Vim在runtime路径查找语法文件失败时,会从$VIMRUNTIME 的语法目录中查找语法文件。

查阅 :h $VIMRUNTIME了解更多信息。

Runtimepath选项

运行 :set runtimepath?,可以查看您的runtime路径。

如果您使用 Vim-Plug 或其他流行的第三方插件管理器,它应该会显示一个目录列表。比如,我的显示如下:

runtimepath=~/.vim,~/.vim/plugged/vim-signify,~/.vim/plugged/base16-vim,~/.vim/plugged/fzf.vim,~/.vim/plugged/fzf,~/.vim/plugged/vim-gutentags,~/.vim/plugged/tcomment_vim,~/.vim/plugged/emmet-vim,~/.vim/plugged/vim-fugitive,~/.vim/plugged/vim-sensible,~/.vim/plugged/lightline.vim, ...

插件管理器做了一件事,就是将每个插件添加到runtime路径中。每个runtime路径都有一个类似 ~/.vim/的目录结构。

如果您有一个目录 ~/box/of/donuts/,然后您想将这个目录添加到您的runtime路径中,您可以在vimrc中添加以下内容:

set rtp+=$HOME/box/of/donuts/

如果在 ~/box/of/donuts/ 里面,您有一个plugin目录 (~/box/of/donuts/plugin/hello.vim) 以及ftplugin目录 (~/box/of/donuts/ftplugin/chocodonut.vim),当您打开Vim时,Vim将会运行 plugin/hello.vim 中所有脚本。同样,当您打开一个 chocodonut 文件时,Vim 将会运行 ftplugin/chocodonut.vim

自己试着做一下:创建一个任意目录,然后将它添加到您的 runtimepath中。添加一些我们本章学到的runtime路径。确保它们按预期工作。

聪明地学习Runtime

花点时间阅读本章,还有认真研究一下这些runtime路径。看一下真实环境下runtime路径是如何使用的。浏览一下您最喜欢的Vim插件仓库,仔细研究一下它的目录结构,您应该能够理解它们中的绝大部分。试着领会重点并跟着做。现在您已经理解了Vim的目录结构,您可以准备学习Vimscript了。