Vim 技巧

用法

自定义映射

正确使用option/Alt键

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)

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 模式下,如果要用搜狗输入法,从英文切换回中文比较麻烦。