美文网首页
Vim 进阶——按键映射与 VimScript 脚本编程

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

作者: rollingstarky | 来源:发表于2019-12-10 00:53 被阅读0次

    一、按键映射

    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 中的名称如下:

    名称 对应按键
    <BS> 退格键
    <Tab> 制表键
    <CR>或<Enter>或<Return> 回车
    <Esc> Escape
    <Space> 空格键
    <Up> 上方向键
    <Down> 下方向键
    <Left> 左方向键
    <Right> 右方向键
    <F1> - <F12> 功能键 F1 到 F12
    #1, #2..#9,#0 F1 到 F10
    <Insert> Insert
    <Del> Delete
    <Home> Home
    <End> End
    <PageUp> 上翻页
    <PageDown> 下翻页
    示例
    " save file (ctrl-s)
    :map <C-s> :w<cr>
    " copy selected text (ctrl-c)
    :vmap <C-c> y
    " Paste clipboard contents (ctrl-v)
    :imap <C-v> <esc>P
    " cut selected text (ctrl-x)
    :vmap <C-x> x
    

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

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

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

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

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

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

    二、Vim 脚本

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

    " hello.vim
    function! SayHello()
        echo 'Hello, World!'
    endfunction
    
    command! Hello call SayHello()
    
    nnoremap Q :Hello<CR>
    

    其中 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
    " => ['<one>', '<two>', '<three>']
    
    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('<afile>')
            autocmd BufWritePost * echo 'Writing: ' . expand('<afile>')
    augroup END
    

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

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

    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 脚本编程

          本文链接:https://www.haomeiwen.com/subject/kpxmgctx.html