Vim 技巧

警告
本文最后更新于 2019-02-18,文中内容可能已过时。

option键在macOS下作为组合按键之一,对应于PC、Linux上的Alt键,但是功能不太一样。Mac上字母及一些标点符号与option组合会输出unicode字符。 原因在于按下按下option键发送的不是Escape Sequence。因此在vim,emacs等终端软件中,没法直接使用Alt键定义映射。

找到原因就好办,解决方法分为两步:

  1. 在终端中修改option/Alt键的行为
  2. vim,emacs等运行于终端下的软件中进行可能必要的设置

以终端程序kittyalacritty为例,具体讨论解决方法中的第一步如何操作。

  • kitty
    • 打开配置文件kitty.conf
    • 找到选项macos_option_as_alt no,将no改为yes
  • alacrity
    • 打开配置文件alacrity.yml
    • 定位到key_bindings
    • 添加需要的键位设置,例如 - { key: x, mods: Alt, chars: "\x1bx" }

以运行在终端下的vim为例,给出第二步中需要的具体操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
function! Terminal_MetaMode(mode)
    set ttimeout
    if $TMUX != ''
        set ttimeoutlen=30
    elseif &ttimeoutlen > 80 || &ttimeoutlen <= 0
        set ttimeoutlen=80
    endif
    if has('nvim') || has('gui_running') || $TERM_PROGRAM =~? 'iTerm2'
        return 
    endif
    " 👆表示只有某些终端下的vim需要👇的设置
    function! s:metacode(mode, key)
        " 这个函数的作用是告诉vim,<M-x>的键盘序列码是多少
        " 这样vim将按照ttimeoutlen的设置来检查是否超时
        " 如果用 `noremap <ESC>x <M-x>` 然后 `noremap <M-x> ...`的方式,
        " 则会使用timeoutlen来检查是否超时
        " 一般timeoutlen设置的相对更大,如果用它更容易误操作,降低使用体验
        if a:mode == 0
            exec "set <M-".a:key.">=\e".a:key
        else
            "👇这条需要对终端进行更针对性的设置,写本节时只想到在alacrity中设置会容易些
            exec "set <M-".a:key.">=\e]{0}".a:key."~"
        endif
    endfunction
    " 针对alacrity,还需设置几个功能键
    if $TERM_PROGRAM =~? 'alacritty'
        exec "set <F1>=\eOP"
        exec "set <F2>=\eOQ"
        exec "set <F3>=\eOR"
        exec "set <F4>=\eOS"
    endif
    for i in range(10)
        call s:metacode(a:mode, nr2char(char2nr('0') + i))
    endfor
    for i in range(26)
        call s:metacode(a:mode, nr2char(char2nr('a') + i))
        call s:metacode(a:mode, nr2char(char2nr('A') + i))
    endfor
    if a:mode != 0
        for c in [',', '.', '/'. ';', '[', ']', '{', '}']
            call s:metacode(a:mode, c)
        endfor
        for c in ['?', ':', '-', '_']
            call s:metacode(a:mode, c)
        endfor
    else
        for c in [',', '.', '/', ';', '{', '}']
            call s:metacode(a:mode, c)
        endfor
        for c in ['?', ':', '-', '_']
            call s:metacode(a:mode, c)
        endfor
    endif
endfunction

" 设置用户自定义命令
command! -nargs=0 -bang VimMetaInit call Terminal_MetaMode(<bang>0)
" buffer 读入后自动进行设置
augroup alt_key
    autocmd!
    autocmd BufReadPost * :VimMetaInit
augroup END

自定义了切换透明和背景的函数,并绑定了快捷键。同时主题栏使用的是lightline。 我为darklight两种背景选取不同的lightline主题,为了使背景切换之后,主题栏的切换也生效,这里使用用户自定义事件来解决问题。 当然这不是唯一的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function! ToggleBackground()
    " A lot of stuff is happening here.
    " 定义一个自定义事件
    doautocmd User ToggleBackgroundExit
endfunction

function! ToggleTransparent()
    " A lot of stuff is happening here.
    " 定义一个自定义事件
    doautocmd User ToggleTransparentExit
endfunction

现在可以在切换背景和透明度执行完成后做任何想做的事:

1
autocmd User ToggleBackgroundExit call lightline#enable()

vim-plug是一款异步插件管理器。具有安装速度快,延迟加载等特性。 这里要讨论的是刚发现的一个特性,可以让插件按照依赖关系进行加载。

栗子🌰:我有三款用于markdown的插件,分别是

原本可以在每个插件后面,用{'for':'markdown'}使其仅在编辑.md文件时才加载。但我发现 这种方法对第二款插件无效。因此想到这里的办法。

vim-plug在调用函数plug#load()加载插件之后会以插件名定义一个用户自定义事件。可以利用这一事件触发对其有依赖的插件的调用。如下为具体的使用案例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
" 以文件类型作为加载的依据
Plug 'iamcco/markdown-preview.nvim', {'for': 'markdown', 'do': 'cd app & yarn install'}
  " 下面的插件无法按照文件类型进行加载
  " 故通过vim-plug 定义的用户自定义事件进行触发
  Plug 'plasticboy/vim-markdown', {'on' : []}
  Plug 'jszakmeister/markdown2ctags', {'on': []}
  augroup vimplug_load_for_markdown
    autocmd!
    autocmd User markdown-preview.nvim plug#load(
                \ 'vim-markdown',
                \ 'markdown2ctags',
                \ )
                \ | autocmd! vimplug_load_for_markdown
  augroup END

进一步,针对一个插件的所有配置可以做到这样:在插件被

  • 加载前,不载入
  • 加载后,自动载入

可以这样实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
" 仍然以上面的插件为例
" 在载入 vim-markdown之后自动加载相关配置。所有配置在函数SetVimmarkdown()之中
function! SetVimmarkdown()
    " all configurations for vim-markdown are at here
endfunction

augroup load_for_vimmarkdown
    autocmd!
    autocmd User vim-markdown call SetVimmarkdown() | autocmd!  load_for_vimmarkdown
augroup END

vim 的模式切换与中文输入法并不总能够和谐共处。

英文 中文
normal 🙂 🙁
insert 🙂 🙂

解决这个问题的方式大体上有两种,1、vim 提供中文输入法,那么这个问题就留给vim及其插件的开发者们去解决了;2、仍然使用外部中文输入法,如搜狗等, 那么关键在于以哪种方式自动切换输入法!

这里沿用第二种思路。尝试过如下几种方式:

  1. 通过 vim 内置函数模拟按键操作来切换输入法。没有成功,但不排除可行性。
  2. 调用外部程序进行输入法切换。成功,但是并不完美。

所以最终采取的方案是通过调用外部程序在进入和离开insert模式时只能切换输入法。实现方式具体看下面的vim配置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
" 定义缓冲区变量,使得可以对每个缓冲区单独监控输入法状态
au BufEnter * let b:im=0
" 为了尽量不影响体验,通过异步机制来进行处理
" 下面的函数用于在 job 进行过程中处理返回信息
fun! IMHandler(channel, msg)
    if a:msg !=? 'com.apple.keylayout.ABC'
        " 切换到英文输入法之前保存当前状态
        let b:im = 1
        call job_start(['issw', 'com.apple.keylayout.ABC'])
    endif
endfun
fun! Lang2en()
    let job = job_start(['issw'], {"out_cb": "IMHandler"})
endfun
" 这里中文输入法的切换用了自己写的脚本,虽然也可以用上面的 issw 命令。
fun! Lang2zh()
    if b:im == 1
        call job_start(['switchim'])
    endif
endfun
autocmd InsertEnter * call Lang2zh()
autocmd BufEnter,InsertLeave * call Lang2en()

因为不想用破解软件来修改按键映射及快捷键,导致仍有不足之处:

  • 调用的外部程序切换输入法不够快,仍然有一定几率产生困扰。
  • insert 模式下,如果要用搜狗输入法,从英文切换回中文比较麻烦。

相关内容