之前的vim一直在用这个项目:kvim,但是用习惯了也想有一份自己的vim配置,为了提高效率,于是就学起了vimscript(主要是为了~/.vimrc)。

vi 之大道如我心之禅,
vi 之漫路即为禅修,
vi 之命令禅印于心,
未得此道者视之怪诞,
与之为伴者洞其真谛,
长修此道者巨变人生。
作:reddy@lion.austin.com
译:yangyangwithgnu@yeah.net

学习笔记

打印信息

打印信息有echoechom两个,但是使用echom会把打印的消息存到历史记录,用message可查看。

注释

在编写vimscript脚本(~/.vimrc)的时候,可用"来注释,但是不一定有效,例如

echo "Hello World!" "hellowrld

设置选项

布尔选项

打开关闭布尔选项

可用set来打开布尔选项,例如set number, set nonumber,所有布尔选项都可以用set <name>打开,set no<name>关闭。

切换布尔选项开关

set <name>!可切换开关状态,例如set number!,若当前显示(隐藏)行号可关闭(打开)。

查看当前值

set <name>?可显示当前值,例如set number?,若行号已打开则输出number,否则输出nonumber

键值选项

有些选项不只有onoff两种选项,需要一个值,可通过set <name>=<value>,例如set numberwidth=2

同样可用set <name>?来查看值。

基本映射

map KEY CMD可以映射按键到指定功能,例如map - dd,这时在normal模式下按下-即可删除一行。

需要注意mapnormalvisual模式下有用,在insert模式下无用。

<keyname>可告诉vim一些特殊的按键,比如<space>代表空格键。例如map <c-d> dd表示按下ctrl+d删除一行。

模式映射

前面提到mapnormalvisual模式下有用,在insert模式下无用。

这时可以使用nmap, vmap, imap分别指定仅在normal,visual,insert模式下生效。

这里举个例子,imap <c-d> <esc>ddi可在insert模式下,按下ctrl+d即可删除一行。

删除映射

*unmap KEY能删除映射,例如

nmap - dd
nunmap -

递归

运行命令

nmap dd 0<esc>jddk

这时会进入递归状态永不终止,导致出现一些奇怪的问题。

非递归映射

*map系列存在递归的危险,还有若重定义一个按键不同行为冲突会导致前面映射无效。

所以vim提供了一组非递归映射指令*noremap,在创建映射的时候不会进行递归。所以尽量用*noremap指令,能省很多麻烦。

Leaders

vim强大之处在于可随意映射键位,为了解决重定义键位带来的冲突,这里可用前缀<leader>来触发。

首先定义<leader>,我习惯用,<leader>

let mapleader = ','

在键盘映射的时候,可以这样

nnoremap <leader>d dd

这样可以通过按,d来删除一行。

Local Leader

vim还可以设置<localleader>来针对于某些文件设置映射,使用方法和<leader>一样。

let maplocalleader = ','

编辑/加载vimrc映射

:nnoremap <leader>ev :vsplit $MYVIMRC<cr>
:nnoremap <leader>rv :source $MYVIMRC<cr>

这样可以在编辑文件的时候通过按<leader>ev<leader>rv来打开.vimrc文件进行编辑/加载。
编辑/加载vimrc

Abbreviations(缩写)

vim还有灵活的缩写功能,这里只介绍insert模式下的缩写功能。

iabbrev blog http://www.netcan666.com

insert下输入blog就会替换成http://www.netcan666.com了。

那为啥不用映射呢?

inoremap blog http://www.netcan666.com

因为用映射的话,但你输入myblog就会变成myhttp://www.netcan666.com,而abbrev是不会替换的。

熟悉自定义映射

当你定义了一个快捷键的时候,例如

inoremap jk <esc>

试图在insert模式下快速按下jk来进入normal模式,可是以前都习惯了直接按<esc>进入,这时可以这样:

inoremap <esc> <nop>

<nop>no operation,这样在insert模式下<esc>即无效了。可强迫自己使用自定义快捷键jk

本地缓冲区的映射和选项设置

映射

当设置一个映射后,开一个buffer你会发现会继承之前定义的映射,比如说

nnoremap <buffer> <leader>x dd

当你在这个buffer下设置之后,切换到另一个buffer<leader>x将不生效。

那么这里的<leader>是一个本地缓冲区<leader>,一般使用<localleader>而不是<leader>

这样在你编写一个其他人可能会用到的插件时,用<localleader>可防止覆盖他人的<leader>全局映射。

特性

:nnoremap <buffer> Q x
:nnoremap          Q dd

按下Q将会使用第一个,因为第一个更具体,然而切换buffer之后将使用第二个,因为第一个未定义。

缩写

iabbrev <buffer> --- &mdash;

这里的---缩写将在当前<buffer>中生效。

选项设置

同样的,在当前buffer设置选项可以用setlocal OPTION

自动命令

自动命令可以让Vim自动执行某些指定的命令,这些指定的命令会在某些事件发生的时候执行。

比如

autocmd BufNewFile * :write

将在你创建文件的时候就保存。(PS: 自动命令需要关闭vim才能删除)

自动命令结构

:autocmd 事件1,事件2 * :write
            ^       ^    ^
            |       |    |
            |       |    要执行的命令
            |       |
            |       用于事件过滤的“模式(pattern)”
            |
            要监听的“事件”

:help autocmd-events可参看所有绑定的事件。

例如

:autocmd BufNewFile,BufRead *.html setlocal nowrap

这样打开或者创建html文件将设置nowrap选项。

FileType事件

这个可以为不同文件类型创建不同的映射,例如

:autocmd FileType javascript nnoremap <buffer> <localleader>c I//<esc>
:autocmd FileType python     nnoremap <buffer> <localleader>c I#<esc>

buffer打开*.js文件,可按<leader>c注释//一行,而在buffer新开*.py文件,可用#注释一行。

自动命令缩写

autocmd FileType python     :iabbrev <buffer> iff if:<left>
autocmd FileType javascript :iabbrev <buffer> iff if ()<left>

将在不同文件类型采用不同缩写。

同样可以禁用之前的写法,例如

iabbrev <buffer> if "233333333333333"

这样就不得不逼自己使用iff缩写了。

自动命令组

自动命令组主要就是可以为一组命令命名,多组相同的名称可以组合。

例如

augroup testgroup
    autocmd BufWrite * :echom "Foo"
    autocmd BufWrite * :echom "Bar"
augroup END
augroup testgroup
    autocmd BufWrite * :echom "Baz"
augroup END

将结合这三个命令。当你保存文件的时候,就会输出

Foo
Bar
Baz

这里有个重要的用法,就是当你创建的自动命令触发的事件一致的时候,vim并不会覆盖之前的事件,也就是说当你触发事件的时候,会执行所有定义过的自动命令。

例如

    autocmd BufWrite * :sleep 200m
    autocmd BufWrite * :sleep 200m
    autocmd BufWrite * :sleep 200m

保存文件会依次执行三次sleep 200m

用命令组autocmd!可以删除之前定义过的事件,

augroup testgroup
    autocmd BufWrite * :echom "Foo"
    autocmd BufWrite * :echom "Bar"
augroup END
augroup testgroup
"" 用autocmd!即可清除前面同名组的命令
    autocmd!
    autocmd BufWrite * :echom "Baz"
augroup END

保存文件的时候将只输出Baz

Operator-Pending映射

这部分比较难了。

一个Operator(操作)就是一个命令,你可以在这个命令的后面输入一个Movement(移动)命令,然后Vim开始对文本执行前面的操作命令,这个操作命令会从你当前所在的位置开始执行,一直到这个移动命令会把你带到的位置结束。

常用到的Operator有d,y和c。例如:

按键 操作 移动
dw 删除 到下一个单词
ci( 修改 在括号内
yt, 复制 到逗号

Movement映射

Vim允许你创建任何新的movements,这些movements可以跟所有命令一起工作。

例如

onoremap p i(

这样就可以用dp代替di(了,也就是删除括号内的文字。

从当前光标位置开始

当你想搞清楚怎么定义一个新的operator-pending movement的时候,你可以从下面几个步骤来思考:

  1. 在光标所在的位置开始。
  2. 进入可视模式(charwise)。
  3. … 把映射的按键放到这里 …
  4. 所有你想包含在movement中的文字都会被选中。

你所要做的工作就是在第三步中填上合适的按键。

例如

onoremap p :<c-u> normal! vllll<cr>

执行dp后,就会删除当前光标后四个字符。

这里的normal可以看做模拟按键操作(加个!是为了不使用映射),即按了四下llll

<c-u>原文并没指出具体作用,Google了一下好像可以清除命令行,不用理会这个。

改变开始位置

只要移动到指定位置,在visual模式下选中,即可操作选中的文本。

下面两条规则可以让你可以很直观的以多种方式创建operator-pending映射:

  1. 如果你的operator-pending映射以在可视模式下选中文本结束,Vim会操作这些文本。
  2. 否则,Vim会操作从光标的原始位置到一个新位置之间的文本。

实战

这里我将用到前面介绍的Movement映射,来快速修改电子邮件地址。

onoremap in@ :<c-u>execute "normal! /[0-9A-Za-z_-]\\+@\r:nohlsearch\rvt@"<cr>

比如文本中有123456789@xx.xx,当你按下cin@,就会删除123456789部分,并进入insert模式进行修改。

这里又有个新命令execute,为什么不直接用normal呢?

因为normal不能模拟特殊按键,例如:normal! /a<cr>,并没有反应,因为他不能识别<cr>。。。

当execute碰到任何你想让它执行的字符串的时候。它会先替换这个字符串中的所有特殊字符。在这个示例中,\r是一个转义字符,它表示的是”回车符(carriage return)”。两个反斜线也是一个转义字符,它会将一个反斜线当作一般字符放到字符串中。

所以按照这个说法来理解,就可以搞清楚这个映射怎么执行了。

normal! /[0-9A-Za-z_-]\\+@\r:nohlsearch\rvt@
                      ^    ^            ^
                      |    |            |
normal! /[0-9A-Za-z_-]\+@<cr>:nohlsearch<cr>vt@

接着分析normal!部分,和键盘输入一样:

/[0-9A-Za-z_-]\+@
:nohlsearch
vt@
  1. 第一部分正则是匹配电子邮件@前面部分,并跳转到匹配的第一个字符
  2. 然后紧接着清除匹配的字符高亮
  3. 进入visual模式并选中@前面的部分

前面说了,如果你的operator-pending映射以在可视模式下选中文本结束,Vim会操作这些文本。

参考资料