第一章 VimL 语言主要特点
1.2 同源 ex 命令行
那么,VimL 到底是种什么样的语言。这里先说结论吧,VimL 就是富有程序流程控制的 ex
命令。用个式子来表示就是:
VimL = ex 命令 + 流程控制
VimL 源于 ex ,基于 ex,即使它后来加了很多功能,也始终兼容 ex 命令。
然则什么是 ex 命令,这不好准确定义,形象地说,就是可以在 Vim 底部命令行输入并 执行的语句。什么是流程控制,这也不好定义呢,类比地说,就是像其他大多语言支持的 选择、循环分支,还有函数,因为函数调用也是种流程跳转。
下面,还是用些例子来阐述。
第一个脚本:vimrc
为了说明 vimrc
先假设你没有 vimrc
。这可以通过以下参数启动 vim:
$ cd ~/.vim/vimllearn/
$ vim -u NONE
这样启动的 vim 不会加载任何配置文件,可以假装自己是个只会用裸装 vim 的萌新。同 时也保证以下示例中所遇命令没有被重映射,始终能产生相同的结果。
Vim 主要功能是要用来编辑一些东西的,所以我们需要一些语料文本。这也可以用 vim 的普通命令生成,请在普通模式下依次输入以下按键(命令):
20aHello World!<ESC>
yy
99p
: w helloworld.txt<CR>
其中输入的按键不包括换行符,上面分几行显示,只为方便分清楚几个步骤的命令。
首先是个 a
命令,进入插件模式,输入字符串“Hello World!”,然后按 <ESC>
键 返回普通模式(这里<ESC>
表示那个众所周知的特殊键,不是五个字符啦)。a
之前 的 20
是还在普通模式下输入的数字参数,(它不会显示在光标所在的当前行,而是临时 显示在右下角,然而一般不必关注)这表示后来的命令重复执行多少次。所以结果是在当 前行插入了 20 个 “Hello World!”,也就是新文件的第一行。
接着命令 yy
是复制当前行,99p
是再粘贴 99 行。于是总共得到 100 行 “Hello World!” ——满屏尽是 Hello World!,应该相当壮观。
最后的命令是用冒号 :
进入 ex 命令行,保存文件,<CR>
表示回车,ex 命令需要 回车确认执行。
现在,我们已经在用 vim 编辑一个名为 helloworld.txt
的文件了。看着有点素是不 是?可以用下面的 ex 命令设置行号选项:
: set number
如此就会在文本窗口左则增加几列特殊列,为文件中的每行编号,确认一下是不是恰好 100 行,用 G
普通命令翻到最后一行。
还有,是不是觉得每一行太长了,超过了窗口右边界。(如果你用的是超大显示屏,Vim 的窗口足够大还没超过,那么在一开始的示例中,把数字参数 20
调大吧)如果想让 vim 折行显示,则用如下命令设置选项:
: set wrap
可以看到,长行都折行显示了,但是行编号并没有改变。也就是说文件中仍是只有 100 行,只有太长的行,vim 自动分几行显示在屏幕窗口上了。
你可以继续输入些设置命令让 vim 的外观更好看些,或让其操作方式更贴心些。但是等 等,这样通过冒号一行行输入实在是太低效,应该把它保存到一个 vim 脚本文件中。
按冒号进入命令行后,再按 <Ctrl-F>
将打开一个命令行窗口,里面记录着刚才输入的 ex 历史命令。这个命令窗口的设计用意是为了便于重复执行一条历史命令,或在某条历 史命令的基础上小修后再执行。不过现在我们要做的是将刚才输入的两条命令保存到一个 文件中,比如就叫 vimrc.vim
,整个按键序列是:
: <Ctrl-F>
Vk
: '<, '> w vimrc.vim<CR>
: q<CR>
解释一下:进入命令窗口后光标自动在最后一行,V
表示进入行选择模式,k
上移一 行,即选择了最后两行。在选择模式下按 :
进入命令行,会自动添加 '<, '>
,这是 特殊的行地址标记法,表示选区,然后用 :w
命令将这两行写入 vimrc.vim
文件( 注意当前目录应在 ~/.vim/vimllearn
中)。最后的 :q
命令只是退出命令窗口,但 vim 仍处于编辑 helloworld.txt
状态中。
你需要再输入一个 :q
退出 vim,然后用刚才保存的脚本当作启动配置文件重新打开文 件,看看效果:
:q<CR>
$ vim -u vimrc.vim helloworld.txt
可见,重新打开 helloworld.txt
文件后也自动设置了行号与折行。你可以换这个参 数启动 vim 对比下效果,确认是 vimrc.vim
的功效:
$ vim -u NONE helloworld.txt
可以手动编辑 vimrc.vim
增加更多配置命令:
: e vimrc.vim
这样就切换到编辑 'vimrc.vim' 状态了,里面已经有了两行,用普通命令 Go
在末尾 打开新行进入插入模式,加入如下两行(还可自行添加一些注释):
: nnoremap j gj
: nnoremap k gk
按 <ECS>
回普通模式再用 :w
保存文件。可以退出 vim 后重新用 $ vim -u vimrc.vim helloworld.txt
参数启动 vim 打开文件观察效果。也可以在当 前的 vim 环境中重新加载 vimrc.vim
令其生效:
: source %
: e #
其中,:e #
或快捷键 <Ctrl-^>
表示切换到最近编辑的另一个文件,这里就是 helloworld.txt
文件啦,在这个文件上移动 j
k
键,看看是否有什么不同体验了 。
不过,表演到些为止吧。这段演示示例主要想说明几点:
- VimL 语言没什么神秘,把一些 ex 命令保存到文件中就是 vim 脚本了。
- vimrc 配置文件是 vim 启动时执行的第一个脚本,也应是大多数 Vim 初学者编写的 第一个实用脚本。
关于 vim “命令” 这个名词,还有一点要区分。普通模式下的按键也叫“命令”,可称之为 “普通命令”,但由于普通模式是 Vim 的主模式,所以“普通命令”也往往简称为“命令”了 。通过冒号开始输入而用回车结束输入的,叫 "ex 命令",vim 脚本文件不外是记录 “ex 命令”集。(注:宏大多是记录普通命令)
默认的 vimrc 位置
正常使用 vim 时不会带 -u
启动参数,它会从默认的位置去找配置文件。这可以在 shell 中执行这个命令来查看:
$ vim --version
或者在任一已启动的 vim 中用这个 ex 命令 :version
也是一样的输出。在中间一段 应该有类似这样几行:
系统 vimrc 文件: "$VIM/vimrc"
用户 vimrc 文件: "$HOME/.vimrc"
第二用户 vimrc 文件: "~/.vim/vimrc"
用户 exrc 文件: "$HOME/.exrc"
efaults file: "$VIMRUNTIME/defaults.vim"
它告诉了我们 vim 搜索 vimrc 的位置与顺序。主要是这两个地方,~/.vimrc
, ~/.vim/vimrc
。用户可用其中一个做为自定义配置,强烈建议用第二个 ~/.vim/vimrc
。 因为配置可能渐渐变得很复杂,将所有配置放在一个目录下管理会更方便。不过有些低版 本的 vim 可能不支持 ~/.vim/vimrc
配置文件,在 unix/linux 系统下可将其软链接 为 ~/.vimrc
即可。
需要注意的是,vimrc
是个特殊的脚本,习惯上没有 .vim
后缀。
如何配置 vimrc 属于使用 Vim 的知识(或经验)范畴,不是本 VimL 教程的重点。不过 为了说明 VimL 的特点,也给出一个简单的示例框架如下:
" File: ~/.vim/vimrc
let $VIMHOME = $HOME . '/.vim'
if has('win32') || has ('win64')
let $VIMHOME = $VIM . '/vimfiles'
endif
source $VIMHOME/setting.vim
source $VIMHOME/remap.vim
source $VIMHOME/plug.vim
if has('gui')
" source ...
endif
finish
let $USER = 'vimer'
echo 'Hello ' . $USER '! Working on: ' . strftime("%Y-%m-%d %T")
一般地,一份 vimrc 配置包括选项设置,快捷键映射,插件加载等几部分,每部分都可 能变得复杂起来,为方便管理,可以分别写在不同的 vim 脚本中,然后在主 vimrc 脚本 中用 :source
命令调用。这就涉及脚本路径全名问题了,若期望能跨平台,就可创建 一个变量,根据运行平台设置不同路径,这就用到了 :if
分支命令了。
最后两行打印个欢迎词。你可以将自己的大名赋给变量 $USER
。如果,你觉得这很傻很 天真,可以移到 finish 之后就不生效了。
在 vimrc 中,选择分支可能很常见,根据不同环境加载合适的配置嘛。但循环就很少见 了。因为 vim 向来还有个追求是小巧,启动快,那么你在启动脚本中写个循环是几个意 思啊,万一写个死循环BUG还启不起来了。
流程控制语句也是 ex 命令
在 VimL 中,每一行都是 ex 命令
。作为一门脚本语言,最常见的,创建变量要用 :let
命令,调用函数要用 :call
命令。初学者最易犯与迷惑的错误,就是忘了 let
或 'call',裸用变量,裸调函数,比如:
i = -1
abs(-1)
用过其他语言的可能会觉得这很自然,但在 VimL 中是个错误,因为它要求第一个词是钦 定的 ex 命令
!正确的写法是:
let i = -1
call abs(-1)
从这个意义上讲,VimL 与 shell 脚本很类似的,把命令行语句保存到文件中就成了脚本 。每一行都以可执行命令开始,后面的都算作该命令的参数。
在 VimL 中,:if
:for
:while
也当作扩展的 ex 命令
处理,在 vim 脚本中, 这些“关键词”前面,可以像 :set
一样加个可选的冒号。同时,也可以像其他 ex 命令 一样在无歧义时任意简写。比如:
:endif
可简写为en
或end
或endi
或endif
:endfor
可简写为endfo
:endfunction
可简写为endf
,后面补上unction
任意前几个字符也可以。
这套缩写规则,就与替换命令 :substitute
简写为 :s
,设置命令 :set
简写为 :se
一样一样的。但是,在写脚本时,强烈建议都写命令全称。命令简写只为在命令行 中快速输入,而在脚本中只要输入一次,一劳永逸,就应以可读性为重了。
当有了这个意识,VimL 的一些奇怪语法约定,也就显得容易理解多了。比如:
- ex 命令以回车结束,所以 VimL 语句也按行结束,不要在末尾加分号,加了反而是语 法错误,在 Vim 中每个符号都往往有奇葩意义。
- VimL 的续行符
\
写在下一行的开始,其他一些语言是把\
写在上一行结束,表 示转义掉换行符,合为一行。但在 VimL 中,每一行都需要一个命令,你可以把\
想象为一个特殊命令,意思是“合并到上一行”。 - 在 VimL 中,不推荐在一行写多个语句,要写也可以,把反斜杠
\
扶正为竖线|
表示语句分隔吧。这在 vim 下临时手输单行命令时可能较为常见,减少额外按回车与 冒号。在很多键盘布局中,|
(与\
)恰好在回车键上面。
关键命令列表
Vim 的 ex 命令
集是个很大的集合,比绝大多数的语言的关键字都多一个数量级。幸运 的是,我们写 VimL 语言的脚本,并不需要掌握或记住这所有的命令,只要记住一些主要 的关键命令就可以完成大部分需求了。
我从 VimL 语言的角度,按常用度与重要度并结合功能性将那些主要的命令分类如下, 仅供参考:
- let call (unlet)
- if for while function try (endif, endfor, end...)
- break continue return finish
- echo echomsg echoerr
- execute normal source
- set map command
- augroup autocmd
- wincmd tabnext
- 其他着重于编辑功能的命令
其中,第 5 类恰好是个分界线,之上的是形成 VimL 语言的关键命令,之下是作为 Vim 编辑器的重要命令。没有后面的编辑器命令,纯 VimL 语言也可以写脚本,作为一种(没 有什么优势的)通用脚本而已;只有利用后面的编辑器命令,才可以调控 Vim。
后面的编辑器命令也可以单独使用,所以 Vim 高手也未必一定需要会 VimL。不过有了 VimL 语言命令的助力,那些编辑器命令可变得更高效与灵活。不过最后一大类纯编辑命 令,可能较少出现在 VimL 语言脚本中。因为按 Vim 可视化编辑的理念,是需要使用者 对这些编辑结果作出及时反馈的。同时,很多编辑命令也有相应的函数,在 VimL 中调用 函数,可能更显得像脚本语言。所以,VimL 中不仅有“库函数”的概念,还有“库命令”呢 。
总之,语句与命令,是联结 VimL 与 Vim 的重要纽带。这是 VimL 语言的重要特点,也 是初学者的一大疑难点。尤其是对有其他语言编程经验的,可能还需要一定的思维转换过 程吧。