Vim 进阶——按键映射与 VimScript 脚本编程

一、按键映射

Vim 中的快捷键绑定可以通过以下命令配置:

  • :imap:只在 Insert 模式下生效的快捷键
  • :cmap:只在 Command-line 模式下生效
  • :nmap:只在 Normal 模式下生效
  • :vmap:只在 Visual 模式下生效
  • :map:在以上所有模式下生效
  • :noremap:包含 :inoremap:nnoremap 等,非递归映射

PS:关于递归映射,如 :map a b:map b c。根据按键映射之间的传递,则有 a -> c 的关系。而 nore 则用于禁止这种递归行为。

做快捷键映射时,各功能按键在 Vim 中的名称如下:

名称 对应按键
退格键
制表键
回车
Escape
空格键
上方向键
下方向键
左方向键
右方向键
- 功能键 F1 到 F12
#1, #2..#9,#0 F1 到 F10
Insert
Delete
Home
End
上翻页
下翻页
示例
" save file (ctrl-s)
:map  :w
" copy selected text (ctrl-c)
:vmap  y
" Paste clipboard contents (ctrl-v)
:imap  P
" cut selected text (ctrl-x)
:vmap  x

:map :w 表示将 Ctrl+S 组合键映射为 :w(保存修改,写入文件)
其中 在 Vim 中即表示 Ctrl+S,类似的用法还有 Alt+S)、Meta+S)、Ctrl+Shift+S)等。
:w 后面的 表示按下回车键。如命令最后没有加上 ,则按下组合键 后只会将对应的 :w 输入到命令栏而不执行。

上面映射的保存文件功能还可以更加细化一些::imap :wa
即该组合键适用于插入模式,按下 Ctrl+S 意味着会依照如下顺序执行命令:

  • :退出插入模式
  • :w:将之前的修改写入文件
  • a:回到插入模式继续编辑文件

对于 Gvim 下的按键映射,还可以调出对话框完成 Opensave-as 功能:

"Open new file dialog (ctrl-n)
:map  :browse confirm e
"Open save-as dialog (ctrl-shift-s)
:map  :browse confirm saveas

对于依次按下多个按键的组合键,如::map $1 :MyFunction1()
当按下 $ 键后,Vim 会等待一秒钟,若一秒钟之内又按下了 1 键,则执行映射的 MyFunction1() 函数;若一秒钟之内没有任何按键按下,则执行 $ 键原本的功能(移动光标到行尾)。

二、Vim 脚本

Vim 脚本是指用 VimScript 编写的为 Vim 添加自定义功能的单独的文件。如下面的 hello.vim 脚本:

" hello.vim
function! SayHello()
    echo 'Hello, World!'
endfunction

command! Hello call SayHello()

nnoremap Q :Hello

其中 function! 用于定义 SayHello() 函数,command! 用于将调用该函数的行为绑定给 Hello 命令,nnoremap Q 则用于将 :Hello 命令的执行绑定给键盘上的 Q 按键。

在 Vim 中使用 :source 命令导入刚刚创建的脚本::source hello.vim
之后执行 :Hello 命令或者按下 Q 按键即可在命令栏输出 Hello, World! 字符串。

变量

变量的赋值使用 :let 命令:

:let mystring = "a string"
:let mynumber = 123
:let mylist = [1, 2, "three", 0x04 ,["five", "six"]]
:let mydict = {1: "one", 2: "two", "others": {3: "three", 4: "four"}}

Vim 脚本中支持以下类型的变量:

  • String:字符串,如 "this is a string"
  • Number:数字,包含十进制(123)、十六进制(0x8A)和八进制(012
  • List:有序列表,列表项支持多种数据类型混合
  • Dictionary:字典,无序键值对
  • Funcref:函数引用

PS:不同进制的数字之间可以直接进行算术运算
由单引号(')包裹的字符串不会转义 \n 等转义字符

关于字符串和数字之间的自动类型转换,参考以下规则:

Input (type) Result (type)
"hello" . "world" "hello world" (string)
"number" . 123 "number 123" (string)
"123" + 10 133 (number)
"123" - 10 . "hits" "113 hits" (string)
"123" - 10 + "hits" 113 (number)
变量作用域

Vim 中变量的作用域通过不同的前缀来指定:

  • v:Vim 预定义的全局作用域
  • g:全局变量前缀
  • b:Buffer 范围内生效的变量
  • t:Tab 范围内生效的变量
  • w:Window 范围内生效的变量
  • l:即 local,Function 范围内生效
  • s:即 source,通过 :source 载入的当前脚本内生效
  • a:即 argument,用来修饰函数的参数

当变量没有任何作用域前缀修饰时,默认为全局变量(除非该变量在函数内部定义)。

变量作用域前缀的使用参考如下脚本:

let g:sum = 10
function SumNumbers(num1, num2)
        let l:sum = a:num1 + a:num2
        if g:sum < l:sum
                let g:sum = l:sum
        endif
        return l:sum
endfunction

echo SumNumbers(3, 4)
echo g:sum
条件语句

Vim 脚本中的 if-else 语句语法格式如下:

if condition1
    code-to-execute-if-condition1-is-true
elseif condition2
    code-to-execute-if-condition2-is-true
endif

其中判断条件 condition 可以有以下几种形式:

  • val1 == val2
  • val1 != val2
  • val1 > val1
  • val1 < val2
  • val1 <= val2
  • val1 >= val2
  • str1 =~ str2
  • str1 !~ str2

字符串比较中的 str2 可以是某个模式,支持正则表达式。

if-else 语句的使用可以参考如下脚本,根据当前时间切换不同的配色:

" note addition of zero
" this guarantees return from function is numeric
let currentHour = strftime ("%H")
echo "currentHour is " currentHour
if currentHour < 6 + 0
        colorscheme darkblue
        echo "setting colorscheme to darkblue"
elseif currentHour < 12 + 0
        colorscheme morning
        echo "setting colorscheme to morning"
elseif currentHour < 18 + 0
        colorscheme shine
        echo "setting colorscheme to shine"
else
        colorscheme evening
        echo "setting colorscheme to evening"
endif
循环

For 循环的语法格式如下:

for var in range
    do-something
endfor

for var in list
    do-somthing
endfor

代码示例

" range
for myvar in range(1,10)
    echo myvar
endfor

" list
let mylist = ['a','b','c','d','e','f','g','h','i','j','k']
for itemvar in mylist
    echo itemvar
endfor

" dictionary
let mydict = {"a": "apple", "b":"banana", "c": "citrus" }
for keyvar in keys(mydict)
    echo mydict[keyvar]
endfor

While 循环的语法格式:

while condition
    execute-this-code
endwhile

代码示例:

let x=0
while x <= 5
    echo "x is now " x
    let x+=1
endwhile
列表与字典

关于 Vim 中列表与字典的操作,可以参考如下代码:

let mylist = [1, 2, "three"]
echo mylist[2]
" => three

let mylist2 = [[1, 2, 3], ["four", "five", "six"]]
echo mylist2[1][0]
" => four
echo mylist2[0][-1]
" => 3

let mylist3 = [1, 2, 3, 4]
call add(mylist3, [5, 6])
echo mylist3
" => [1, 2, 3, 4, [5, 6]]

let mylist4 = [1, 2, 3, 4]
let mylist4 = mylist4 + [5, 6]
echo mylist4
" => [1, 2, 3, 4, 5, 6]

let mylist5 = [1, 2, 3, 4]
call remove(mylist5, 3)
echo mylist5
" => [1, 2, 3]

let mydict = {'banana': 'yellow', 'apple': 'green'}
echo mydict['banana']
" => yellow
echo mydict.apple
" => green

get map split join

可以对列表或字典中的值应用某个函数(如 joinmap 等)以完成特定的需求,常见示例如下:

let a = split("one two")
echo a
" => one

let mylist = ["one", "two", "three"]
call map(mylist, '"<" . v:val . ">"')
echo mylist
" => ['', '', '']

let mylist2 = ["one", "two", "three"]
echo get(mylist2, 2, "none")
" => three
echo get(mylist2, 3, "none")
" => none

let mylist3 = ["one", "two", "three"]
let mystring = join(mylist3, "+")
echo mystring
" => one+two+three

更复杂的示例(好神奇,没看懂。。。):

let mynumbers = {0:'zero', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five', 6:'six', 7:'seven', 8:'eight', 9:'nine'}

function mynumbers.convert(numb) dict
        return join(map(split(a:numb, '\zs'), 'get(self, v:val, "unknown")'))
endfunction

echo mynumbers.convert(12345)
" => one two three four five
函数

Vim 中定义函数的语法如下:

function Name(arg1, arg2,...argN) keyword
    code-to-execute-when-function-is-called
endfunction

所有在函数体中定义的变量只在该函数内部可见,即作用域为 local。如果需要使用函数外部的变量,可以将其作为参数传递给函数,或者直接调用(需要在该变量名前加上全局作用域前缀 g:)。
函数定义代码中使用由参数传递的变量时,需要加上 a: 作用域前缀。

参数列表

参考如下代码:

function PrintSum(num1, num2, ...)
        let sum = a:num1 + a:num2
        let argnum = 1
        while argnum <= a:0
                let sum += a:{argnum}
                let argnum += 1
        endwhile
        echo "The sum is" sum
        return sum
endfunction

let sum = PrintSum(1, 2, 3, 4)
" => The sum is 10

注意代码中 a:0(不定参数的长度)和 a:{argnum}(第 argnum 个额外参数)的用法。

此外还可以通过 a:000 以列表的方式获取所有额外的参数:

function PrintSum(num1, num2, ...)
        let sum = a:num1 + a:num2
        for arg in a:000
                let sum += arg
        endfor
        echo "The sum is" sum
        return sum
endfunction

let sum = PrintSum(1, 2, 3, 4)
" => The sum is 10
综合示例

根据当前时间自动切换 Vim 配色的脚本:

let g:Favcolorschemes = ["darkblue", "morning", "shine", "evening"]

function SetTimeOfDayColors()
        " currentHour will be 0, 1, 2 or 3
        let CurrentHour = (strftime("%H") + 0) / 6
        execute "colorscheme " . g:Favcolorschemes[CurrentHour]
        echo "set color scheme to " . g:Favcolorschemes[CurrentHour]
endfunction

call SetTimeOfDayColors()

三、Autocommands

Autocommands 即在特定条件下自动执行的命令,这些命令包括所有合法的 Vim 命令。
Vim 定义了一些事件event)作为触发命令自动执行的开关,常见的 event 如下:

  • BufNewFile:在开始编辑一个新的文件时触发
  • BufReadPre:在 Vim 移动到一个新的 buffer 前触发
  • BufRead, BufReadPost:开始编辑新的 buffer 时,读取文件之后触发
  • BufWrite, BufWritePre:在将 buffer 内容写入文件之前触发
  • FileType:在确定了文件类型(filetype)之后触发
  • VimResized:更改 Vim 的窗口大小后触发
  • WinEnter, WinLeave:进入或离开某个 Vim 窗口时触发
  • CursorMoved, CursorMovedI:Normal 或 Insert 模式下,光标移动后触发
Autocommands 代码示例
augroup demo
        autocmd!
        autocmd BufReadPost * echo 'Reading: ' . expand('')
        autocmd BufWritePost * echo 'Writing: ' . expand('')
augroup END

上述脚本会添加如下功能:
打开文件时 Vim 命令栏输出 "Reading: ";在使用 :w 等命令保存文件时,命令栏输出 "Writing: "。

又如根据源文件类型设置不同的缩进风格:

filetype on
autocmd FileType ruby setlocal tabstop=2 softtabstop=2 shiftwidth=2 expandtab
autocmd FileType javascript setlocal ts=4 sts=4 sw=4 noet

参考资料

Hacking Vim 7.2
Modern Vim

你可能感兴趣的:(Vim 进阶——按键映射与 VimScript 脚本编程)