vim 最佳实践
简单的那些「set xxx」之类的命令就不提了,先从 map 开始吧。
map 命令的用途是把一组键映射为其他的命令。
例如我想在按「;」键的时候,自动变成「:」键进入命令行模式,就可以这样设置:
map ; :
Vim 的搜索模式太古怪,我比较习惯 Python 的风格,所以可以把「/」替换成「/\v」,变成 very magic 模式:
map / /\v
还有设置了 hlsearch 后,搜索结果就一直高亮了,切换它又比较麻烦。还好我习惯没事多按几次 Esc 键,所以可以在按 Esc 键时清空搜索寄存器,这样就不会再高亮搜索结果了:
map 这样设置后,虽然效果达到了,可是引入了一个副作用:方向键的行为变得很诡异了。搜了下原因,好像是方向键也会触发 Esc 键,而且好像没法解决。于是只好换个快捷键了: let mapleader="," mapleader 是自定义命令的起始键,一般都定义成逗号。因为「/」离「,」比较近,而且表示搜索的意思,所以我就把快捷键设为「,/」了。如果为了方便的话,「,,」或「, 你会发现这个设置在普通模式和可视模式下都可用,但是插入模式下并不会生效。 imap 然而你在使用时会遇到问题,按 Esc 键从插入模式返回普通模式时,会让光标左移一位,再按 b 有可能就定位错误了。这种情况下,就可以用 Ctrl + o 来临时切换到普通模式,在执行完 b 命令后,会自动回到插入模式: imap 如果想让 Esc 键不让光标左移,可以这样设置: imap 另外,回退一个单词用这个快捷键也是有效的: imap 不过我直接按 Shift + ← 并没反应,原因是大部分终端模拟器并不能区分 Shift 和不可见字符的组合。另外,CMD 键也是不可用的。如果非要使用这二者的话,只能用 MacVim。 imap 接着又出现了一个问题,假设我把 Home 键设置成回到页首: imap 然后我按 Ctrl + a 时,也会回到页首,而不是行首了,这种不可控的事是程序员不愿意遇到的。 inoremap 如果要问什么时候该加 nore,答案是如果你没把握,那就加上。 目前为止,一切看上去都很正常,但是好像又有什么不对:为什么我在普通模式替换的是普通按键,而在插入模式替换的却是快捷键? inoremap "" 只要快速按 2 下引号,大部分时候确实是能用的;但如果我只是想输入一个引号,就会看到等一秒后光标位置才会右移(虽然并不妨碍我继续输入后面的字符)。 inoremap ( () 而在命令行模式下,一般都要敲入回车才真正执行一条命令,但 cmap 会让匹配的命令立刻执行,不需要敲入回车,这也显得比较诡异。 另外,map 还能加一些特殊参数: 其他不是很常用,就不说了。 说完 map 后,再回到之前未解决的一个问题:命令行模式下,如何自定义命令? command W :w !sudo tee % 无论怎样,它至少是能用的,但是保存时会出现一个确认界面,有点难看,于是这样去掉: command W :silent w !sudo tee % 可如果设置了检查文件是否改动,保存完后还会提示你文件被修改了。于是可以用 execute 命令来执行,执行完后再用 :e! 来编辑: command W :execute 'silent w !sudo tee %' | :e! 还有个不爽之处是命令行仍然会闪动一下,输出一片内容然后很快消失掉,可以将输出重定向到 /dev/null 来解决: command W :execute 'silent w !sudo tee % > /dev/null' | :e! 再把 :WQ 给加上: command WQ :execute 'silent w !sudo tee % > /dev/null' | :q! 或者偷下懒,在 :W 命令后再执行 :q: command WQ :execute 'W' | :q 最后,如果设置了自动重新载入 .vimrc 文件,重复加载时会报命令已经定义过的错误。把 command 改成 command! 即可解决。 接着再来说说自动执行的问题。 autocmd FileType python set expandtab 当检测到文件类型为 python 时,就会设置 expandtab,把 Tab 展成空格了。 augroup python_indent augroup END 其中,autocmd! 会先清空这个组,而后面的 autocmd 会重新绑定事件。 另一种做法是直接使用 autocmd! 来绑定事件,和 command! 一样,它也会替换之前绑定的事件。 autocmd! BufNewFile *.py call append(0, "test") 只有后一句会生效。 顺带一提,自动重新加载 .vimrc 文件也可以用 autocmd 来实现的: augroup reload_vim_config augroup END 再来一个比较复杂点的,保存某些类型的文件时,自动去掉行尾的空格: augroup strip_traling_spaces augroup END 如果想让读取时也去掉空格,可以把第三行改成 autocmd BufRead,BufWritePre .css,.js,*.py %s/\s+$//e 也就是说,这后面跟的多个事件是「或」的关系。 autocmd FileType css,javascript,python autocmd BufWritePre 还能加上 if 来判断: autocmd BufWritePre * if index(['css', 'javascript', 'python'], &filetype) >= 0 | %s/\s+$//e 其中,&filetype 或 &ft 是当前文件的类型,index 函数可以用来检查是否在列表中。 虽然列出了那么多种方法,但其实都没能解决一个问题:删除空格后,光标位置变了。 function! endfunction augroup END 其中,function 后的叹号和 command! 的作用一样,重复定义时覆盖之前的定义。 然后再聊聊复制粘贴的问题。 set clipboard+=unnamed 要注意的是,剪贴板与 Vim 共用后,Vim 中所有的复制操作都会修改剪贴板内容。而且 Vim 的删除命令也有复制的副作用,所以很可能一不小心就把剪贴板弄乱了。如果要将一个单词替换成剪贴板的内容,ce if has("mac") endif has("mac") 可以判断是不是 OS X,:r !pbpaste 则是读取 pbpaste 这个外部命令的输出,并插入到光标位置。 还有一点美中不足的是,如果贴进来的代码和现有代码使用的缩进方式不一致(比如一个用空格,一个用 Tab),仍然会有问题。 inoremap 然而故事的结尾,我还是用回了 PyCharm。 vnoremap < 即选择一个区域后,可以连续调整缩进;否则需要精确计算缩进次数,用 n> 和 n< 来替代。 nnoremap
map
如果要针对某个模式的话,需要加上这个模式的前缀,常用的有 nmap、vmap、imap 和 cmap 等,分别适用于普通、可视、插入和命令行模式。
例如我在插入模式的时候,想回到上一个单词,一般需要按下 Esc 进入普通模式,再按 b 到上一个单词,然后按 i 进入插入模式。于是可以把这些操作绑定到 Ctrl + b 快捷键上:
由于 MBP 上没有 Home 键,因此这样回到行首也是可行的:
所以要加上 nore 这个前缀,以确保替换成的命令是不会因其他设置而改动的:
前半个问题很好回答:因为没这个必要,在普通模式下,我只需要像普通的命令一样连续按键就行了,为什么要用快捷键这种需要同时按住多个按键的方式?
后半个问题则主要是识别问题:插入模式下,按下按键需要立刻输出到屏幕上,如果要判断是否是一个命令,就得等待一段时间,确认不是命令后才能输出,导致输入体验不佳。
以下面的例子为例,假设我想按 2 下引号,就在当前单词的前后各插入一个引号:
不过,如果只有一个按键,不需要等待匹配下一个,那就不会影响输入体验了。例如自动补全括号和引号:
inoremap [ []
inoremap { {}
inoremap " ""
inoremap ' ''
所以大部分情况下,插入模式和命令行模式都只定义一些快捷键的 map。<buffer> 表示只对当前文件有效。
<nowait> 表示不等待范围更大的组合匹配。例如同时定义这两组:
nnoremap <Leader>wd dw
nnoremap <buffer> <nowait> <Leader>w w
如果没有加「<nowait>」,按下「,w」后,要等一段时间确认之后你接下来要按的不是「d」,才会执行。加了之后就不会等待,而是直接执行了,缺点就是「,wd」会失效。
<silent> 表示不在命令行显示输入的命令。
这就该轮到 command 出场了。
以保存文件为例,有时候打开了一些系统文件,编辑完后却发现不能保存,要输入一段很长的代码才能用 sudo 来保存。
现在就用 :W 命令(必须以大写字母开头)来简化这个操作:
大部分的编程语言里都用制表符来缩进,而 Python 通常使用 4 个空格。这种情况下,就可以用 autocmd 来解决:
不过再打开其他文件时,仍然是用空格来缩进,于是需要把 set 改成 setlocal,让它只针对当前 buffer 和 window 有效。
另外,和 command 一样,重新加载 .vimrc 文件时,这个事件会再次被绑定,于是会重复执行两次。解决办法是把它加入一个组:autocmd!
autocmd FileType python setlocal expandtab
例如这样的设置:
autocmd! BufNewFile .py call append(0, "# -- coding: utf-8 -*-")autocmd!
autocmd BufWritePost $MYVIMRC source $MYVIMRC
autocmd!
autocmd BufWritePre *.py,*.js,*.css %s/\s\+$//e
如果需要「与」的关系,可以用多个 autocmd:
在编写 Vimscript 时,不带来副作用也是很重要的一点。而为了解决这个问题,就不得不保存原位置,修改完后再恢复了:let l = line(".")
let c = col(".")
%s/\s\+$//e
call cursor(l, c)
augroup strip_traling_spacesautocmd!
autocmd FileType css,javascript,python autocmd BufWritePre <buffer> call <SID>StripTrailingSpaces()
另外,如果函数名没有加范围前缀(例如 s: 表示本地函数,g: 表示全局函数),则必须大写。
用 Vim 的人应该都有过把代码粘贴到 Vim 里后,缩进变得乱七八糟的经历。
原因就是直接用 CMD + v 粘贴时,会模拟成用户的输入,而在设置了自动缩进的情况下,缩进就变乱了。
解决起来其实很简单,一种办法是 :set paste 后再粘贴,贴完后再 :set nopaste;另一种办法是直接用 p 来粘贴,但这要求 Vim 能访问系统剪贴板;此外,OS X 上还能调用 pbpaste 这个外部命令来粘贴。
第一种办法因为我没法在 MBP 上绑定 CMD + v 快捷键,所以就不管了。
第二种办法需要 Vim 在编译时带上了 +clipboard。可以用 vim --version | grep clipboard 来查看,包含 +clipboard 则表示没问题,-clipboard 则表示不支持。OS X 自带的 Vim 是不支持的,用 brew 安装,或者 MacVim 是带了的。确认支持后,这样设置即可:
inoremap
第三种办法则这样实现:inoremap <C-p> <C-o>:r !pbpaste<CR>
这个办法不跨平台,而且要调用外部命令,所以相对而言还是第二种比较好。但
好在 Vim 提供了 retab 这个命令,只要在粘贴后,再执行下它就行了:
nnoremap
原因是 Vim 下最强大的代码补全插件 YouCompleteMe,也无法满足我的需求:from xxx import yyy 中,yyy 部分是无法补全的。
回到 PyCharm 后,我的感觉是配置 Vim 的过程像是自己在实现编辑器的各个功能,而 PyCharm 则是大部分都配好了,稍微改改就行了,很多时候比 Vim 更好用(例如随便一个编辑器都几乎不用为粘贴头疼)。当然,后者的缺点就是很多地方无法定制(虽然 Vim 也不是完全可定制的),例如我想在保存文件时删除末尾的空格,但是不处理 markdown 文件;还有复制粘贴时,按照当前文件的类型,自动重新格式化 Tab 和 Space。
为了尽量追上 Vim 的速度,还不得不再记一些快捷键(例如 Shift + CMD + o 和 CMD + e 可以快速打开文件),禁用没用的插件,隐藏编辑区和 Tab 区以外的区域等。
此外还发现 PyCharm 的 Vim 插件可以读取 ~/.ideavimrc 配置文件,不过基本只能定义 map,其中最需要加上的是设置是: