1.2 同源 ex 命令行

优质
小牛编辑
114浏览
2023-12-01

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 键,看看是否有什么不同体验了 。

不过,表演到些为止吧。这段演示示例主要想说明几点:

  1. VimL 语言没什么神秘,把一些 ex 命令保存到文件中就是 vim 脚本了。
  2. 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 可简写为 enendendiendif
  • :endfor 可简写为 endfo
  • :endfunction 可简写为 endf,后面补上 unction 任意前几个字符也可以。

这套缩写规则,就与替换命令 :substitute 简写为 :s,设置命令 :set 简写为 :se 一样一样的。但是,在写脚本时,强烈建议都写命令全称。命令简写只为在命令行 中快速输入,而在脚本中只要输入一次,一劳永逸,就应以可读性为重了。

当有了这个意识,VimL 的一些奇怪语法约定,也就显得容易理解多了。比如:

  • ex 命令以回车结束,所以 VimL 语句也按行结束,不要在末尾加分号,加了反而是语 法错误,在 Vim 中每个符号都往往有奇葩意义。
  • VimL 的续行符 \ 写在下一行的开始,其他一些语言是把 \ 写在上一行结束,表 示转义掉换行符,合为一行。但在 VimL 中,每一行都需要一个命令,你可以把 \ 想象为一个特殊命令,意思是“合并到上一行”。
  • 在 VimL 中,不推荐在一行写多个语句,要写也可以,把反斜杠 \ 扶正为竖线 | 表示语句分隔吧。这在 vim 下临时手输单行命令时可能较为常见,减少额外按回车与 冒号。在很多键盘布局中,|(与\)恰好在回车键上面。

关键命令列表

Vim 的 ex 命令集是个很大的集合,比绝大多数的语言的关键字都多一个数量级。幸运 的是,我们写 VimL 语言的脚本,并不需要掌握或记住这所有的命令,只要记住一些主要 的关键命令就可以完成大部分需求了。

我从 VimL 语言的角度,按常用度与重要度并结合功能性将那些主要的命令分类如下, 仅供参考:

  1. let call (unlet)
  2. if for while function try (endif, endfor, end...)
  3. break continue return finish
  4. echo echomsg echoerr
  5. execute normal source
  6. set map command
  7. augroup autocmd
  8. wincmd tabnext
  9. 其他着重于编辑功能的命令

其中,第 5 类恰好是个分界线,之上的是形成 VimL 语言的关键命令,之下是作为 Vim 编辑器的重要命令。没有后面的编辑器命令,纯 VimL 语言也可以写脚本,作为一种(没 有什么优势的)通用脚本而已;只有利用后面的编辑器命令,才可以调控 Vim。

后面的编辑器命令也可以单独使用,所以 Vim 高手也未必一定需要会 VimL。不过有了 VimL 语言命令的助力,那些编辑器命令可变得更高效与灵活。不过最后一大类纯编辑命 令,可能较少出现在 VimL 语言脚本中。因为按 Vim 可视化编辑的理念,是需要使用者 对这些编辑结果作出及时反馈的。同时,很多编辑命令也有相应的函数,在 VimL 中调用 函数,可能更显得像脚本语言。所以,VimL 中不仅有“库函数”的概念,还有“库命令”呢 。

总之,语句与命令,是联结 VimL 与 Vim 的重要纽带。这是 VimL 语言的重要特点,也 是初学者的一大疑难点。尤其是对有其他语言编程经验的,可能还需要一定的思维转换过 程吧。