拔掉鼠标系列(一) 高效的vim

编辑器界有个有名的段子: 如何产生一段真随机数?只需要让一个新手尝试退出vim…
vim是公认的上手困难,学习曲线陡峭的编辑器。 克服这些困难最终带来的回报是非常丰厚的, 我在此分享一些高效使用vim的方式, 希望大家能有所收获。

vim高效的核心

vim高效有以下原因:

  • 精准
  • 定制化
  • 自动化

精准

vim完全依赖键盘完成所有操作, 任何操作都能使用确定的一系列键盘操作完成, 因此vim中操作的精准性由键盘击键的准确性保证。 与此对比的是或多或少需要鼠标介入的编辑器, 鼠标作为屏幕定位设备,为保证准确性, 需要在鼠标指针接近目标点时降低移动速度。 键盘击键的频率和准确性远远超过鼠标, 在鼠标和键盘两种输入设备之间切换也会极大降低编辑效率, 这也是依赖鼠标介入编辑操作的编辑器也尽力依靠快捷键减少鼠标操作的原因。

定制化

vim非常容易根据个人需要进行定制化, 所有的定制化可以通过编辑vimrc文件实现。
vim的插件发布基于远程git仓库, git仓库可以是github这类的公开仓库, 也可以是自己搭建的私有仓库,不需要一个集中式的仓库中心, 这使得编写、发布、同步、更换插件非常方便, 当使用的github公开插件不能满足需求时,fork并修改即可。 其他一些编辑器出于易用性的考虑往往只提供集中式插件管理中心, 其好处是开箱即用,但是个人用户对插件做定制化修改代价很高, 需要按照集中式插件中心的要求执行很多和插件本身完全无关的操作, 间接降低了插件的定制化能力。

自动化

自动化的前提是精准定制化
精准使得任何vim中的编辑操作都可以自动化,而对于需要鼠标介入操作的编辑器, 鼠标操作本身的自动化是不可能完成的任务,这极大的限制了这类编辑器自动化的能力。
‘write once use everywhere’是vim的设计哲学, 最突出的一个体现就是插件是不区分窗口的。在绝大多数其他编辑器中, 插件几乎仅针对文本编辑窗口, 其他的窗口(如目录树窗口)几乎没有可用的插件。
vim提供了多种方式来实现高效的重复操作: 单次编辑操作,使用重复快捷键.;简单的短序列操作, 使用vim的脚本录制功能录制操作序列,并在随后重放; 常用的高频操作,可以在vimrc中添加函数,方便后续调用; 方便的定制化使得自动化工作的成果能够逐渐的累积, 进一步增强vim的自动化能力。
在使用vim的过程中要养成自动化的思想,每当开始执行重复的操作时, 都要想办法自动化,消除重复。 这类似于写代码, 每当ctrl-c ctrl-v时,都需要考虑是否封装城单独函数。

配置vim

vim上手难有两个主要的原因:

  1. 不同于习惯中编辑器的操作方式。
  2. 没有开箱即用的默认配置。

本文主要针对第二点给出建议,列举出一些最出色的vim插件,展示这些插件的作用, 并给出推荐的插件配置及快捷键设置。 完整推荐配置参见vimrc
效果图:
效果图

配置文件

vim默认的配置文件路径是~/.vimrc,vim在启动时首先加载vimrc文件中的所有配置, 配置文件的内容是vim script脚本,这是一种比bash脚本功能更加强大的脚本。 配置文件中不仅可以配置vim及插件的选项, 也可以自定义函数,这些函数除了执行vim的命令, 还能执行所有的操作系统命令。 例如在推荐配置中利用curl命令自动安装需要的字体和插件管理器。

内置配置

vim内置了一些配置选项,这些配置通过在vimrc文件中通过set命令配置。 一些重要的配置如下:

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
" 自动识别文件类型,使用文件类型plugin脚本,使用缩进定义文件
filetype plugin indent on
" 打开语法加亮
syntax on
" session中不保存当前目录,这样使用Session.vim文件恢复时,Session.vim文件所在目录自动变成当前目录
set sessionoptions-=curdir
set sessionoptions+=sesdir
" 用啥鼠标
set mouse-=a
" 文件修改之后自动载入
set autoread
" 开启正则表达式的magic模式,例如使用\w匹配单词
set magic
" 设置不兼容vi, 只使用增强模式
set nocompatible
" 设置可以删除行首空格,断行, 进入Insert模式之前的位置
set backspace=indent,eol,start
" 新行使用设置自动缩进,使用上一行的缩进方式
set autoindent
" 设置智能缩进
set smartindent
" 设置命令历史列表的长度
set history=50
" 将TAB替换为空格
set expandtab
" 设置TAB宽度
set tabstop=4
" 和ignorecase配合实现智能大小写匹配
set smartcase
set ignorecase
" 在80个字符处设置锚线
set colorcolumn=80
" 补全内容不以分割子窗口形式出现,只显示补全列表
set completeopt=longest,menu,preview
" 使路径包含当前目录下的所有子目录
set path+=**

更多的内置配置可以参见推荐配置

快捷键设置

<leader>

vim作为纯键盘操作的编辑器,自身占用了大量的键位作为快捷键, 为了避免快捷键的冲突问题,vim引入了前缀键的概念<leader>。 <leader>键允许使用字符序列作为快捷键组合, 例如<leader>a<leader>gf<leader><leader>f等。 因此<leader>是vim中使用最高频的按键之一,vim中的默认<leader>键是\, 是键盘上最难按到的按键之一,每个vim用户都不会使用这个默认的设置, 根据自身的习惯设置成更好按的键位, 我推荐设置为空格键,vim中空格键的功能和向右的移动键l是重复的, 按压空格键的是左右手拇指之一,是手上最强壮的手指,并且在键盘上, 拇指除了空格以外不需要按压其他任何按键, 是最适合承担繁重的<leader>按键的键位。
推荐配置:

1
let mapleader = "\<Space>"

模式和快捷键

vim有六种快捷键模式:

  • Normal: 正常模式,用于输入命令,不能直接编辑文本,是进入vim的默认模式。
  • Visual: 可视模式,选中的区域会高亮,按vV进入。
  • Select: 选择模式,通过set selectmode配置, 该模式和记事本类似,需要鼠标介入操作,没人用,可忽略。
  • Operator-pending: 操作符未决模式,当键入一个未决操作符(d, y, c, 等等)进入该状态。
  • Insert: 插入模式,包含了Replace模式,可直接编辑或替换文本,按i或R等键进入
  • Command-line: 命令行模式,按:/命令进入。 每种快捷键模式有各自不同的快捷键设置,设置对应模式的快捷键命令如下:
递归映射 非递归映射 取消映射 模式
map noremap unmap Normal, Visual, Select, Operator-pending
nmap nnoremap nunmap Normal
vmap vnoremap vunmap Visual and Select
smap snoremap sunmap Select
xmap xnoremap xunmap Visual
omap onoremap ounmap Operator-pending
map! noremap! unmap! Insert and Command-line
imap inoremap iunmap Insert
lmap lnoremap lunmap Insert, Command-line, Lang-Arg
cmap cnoremap cunmap Command-line
tmap tnoremap tunmap Terminal-Job

推荐配置:

1
2
3
4
5
6
7
8
9
" 保存
nmap <leader>w :update<CR>
" 使用系统剪贴板
vmap <leader>y "+y
vmap <leader>d "+d
nmap <leader>p "+p
nmap <leader>P "+P
vmap <leader>p "+p
vmap <leader>P "+P

其他映射参见各个插件的推荐配置

插件管理

vim有众多的插件,主要发布于github, 为了方便插件的安装、更新、管理,插件管理器应运而生。 vim-plug是出色的vim插件管理器, 安装简单,易于使用,快速,支持指定分支、tag、commit,支持更新后函数钩子等。

安装插件管理器

1
2
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

以下配置在首次打开vim时自动安装插件管理器并安装插件:

1
2
3
4
5
6
7
8
9
"自动安装插件管理器
if empty(glob('~/.vim/autoload/plug.vim'))
    silent !curl -fLo ~/.vim/autoload/plug.vim --create-dirs
                \ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
    augroup vim-plug_
        autocmd!
        autocmd VimEnter * PlugInstall --sync | source $MYVIMRC
    augroup END
endif

配置插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
call plug#begin('~/.vim/bundle')
" 短名称,相当于https://github.com/junegunn/vim-easy-align
Plug 'junegunn/vim-easy-align'
" 完整url的git仓库
Plug 'https://github.com/junegunn/vim-github-dashboard.git'
" 使用非master分支
Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
" 使用有tag标记的release
Plug 'fatih/vim-go', { 'tag': '*' }
" 指定tag
Plug 'nsf/gocode', { 'tag': 'v.20150303'}
" 指定安装目录和更新后动作
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
" 本地插件
Plug '~/my-prototype-plugin'
" 增加其他插件...
" Initialize plugin system
call plug#end()

安装/更新插件

在vim中执行命令:PlugUpdate

界面美化

配色主题

vim有众多的配色主题,推荐以下几个最为流行的主题:

代码高亮

各类型语言文件语法高亮

c/cpp: bfrg/vim-cpp-modern
shell: Shougo/vimshell.vim
protobuf: vim-protobuf
thrift: solarnz/thrift.vim
html: othree/html5.vim
javascript: pangloss/vim-javascript
json: elzr/vim-json
scala: derekwyatt/vim-scala

括号高亮

kien/rainbow_parentheses.vim
rainbow_parentheses

界面增强

状态栏美化

vim-airline/vim-airline
airline
状态栏主题插件 vim-airline/vim-airline-themes 提供了大量可选的状态栏配色主题。
推荐设置:

1
2
3
4
5
let g:airline#extensions#tabline#enabled = 1
let g:airline#extensions#tabline#show_buffers = 1
let g:airline#extensions#tabline#show_tabs = 1
let g:airline#extensions#tabline#buffer_nr_show = 1
let g:airline_theme='solarized'

目录树

scrooloose/nerdtree以目录树的方式显示 目录、文件的层级结构。 Xuyuanp/nerdtree-git-plugin 在目录树中显示文件的git状态。 nerdtree-git-plugin 推荐设置:

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
" 打开/关闭目录树
nmap <F4> :NERDTreeToggle<CR>
" 在目录树中定位当前打开的文件
nmap <F5> :NERDTreeFind<CR>
" 显示隐藏文件
let g:NERDTreeShowHidden = 1
" 目录树宽度
let g:NERDTreeWinSize=35
" 目录树停靠位置
let g:NERDTreeWinPos = "left"
let g:NERDTreeGlyphReadOnly = "RO"
" 高亮
let g:NERDTreeFileExtensionHighlightFullName = 1
let g:NERDTreeExactMatchHighlightFullName = 1
let g:NERDTreePatternMatchHighlightFullName = 1
let g:NERDTreeHighlightFolders = 1 " enables folder icon highlighting using exact match
let g:NERDTreeHighlightFoldersFullName = 1 " highlights the folder name
" 关闭当前行高亮,终端中高亮当前行有严重的性能问题
let g:NERDTreeHighlightCursorline = 0
augroup nerdtree_
    autocmd!
    " autocmd vimenter * NERDTree
    " 设置自动触发命令
    autocmd StdinReadPre * let s:std_in=1
    " 打开vim时没有指定打开的文件,自动打开目录树
    autocmd VimEnter * if argc() == 0 && !exists("s:std_in") && winnr('$') < 2 | NERDTree | endif
    " 打开vim时指定打开的目录,自动打开目录树
    autocmd VimEnter * if argc() == 1 && isdirectory(argv()[0]) && !exists("s:std_in") | exe 'NERDTree' argv()[0] | wincmd p | ene | endif
    " 如果vim中只剩目录树一个面板,关闭vim
    autocmd BufEnter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif
augroup END
" 常用默认快捷键
" R     刷新目录树
" m     弹出菜单,进一步可以选择删除/重命名/创建文件/创建目录等

图标增强

ryanoasis/vim-devicons 提供图标支持
tiagofumo/vim-nerdtree-syntax-highlight 在目录树显示文件图标。 ryanoasis/vim-devicons
图标增强需要额外的字体支持,需要安装相应的字体并设置终端字体
推荐配置:

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
" 安装美化字体
function! InstallAirLineFont()
    let s:usr_font_path = $HOME . '/.local/share/fonts/custom/Droid Sans Mono for Powerline Nerd Font Complete.otf'
    if s:os == "Darwin" "mac
        let s:system_font_path = '/Library/Fonts/Droid Sans Mono for Powerline Nerd Font Complete.otf'
    elseif s:os == "Linux"
        let s:system_font_path = '/usr/share/fonts/custom/Droid Sans Mono for Powerline Nerd Font Complete.otf'
        "elseif s:os == "Windows"
    endif

    if exists("s:builty_vim") && s:builty_vim == 1
                \ && !filereadable(s:usr_font_path)
        execute '!curl -fLo ' . shellescape(s:usr_font_path) . ' --create-dirs ' . 'https://github.com/ryanoasis/nerd-fonts/raw/master/patched-fonts/DroidSansMono/complete/Droid\%20Sans\%20Mono\%20Nerd\%20Font\%20Complete.otf'
        if !filereadable(s:system_font_path) && filereadable(s:usr_font_path)
            execute '!sudo mkdir `dirname ' . shellescape(s:system_font_path) . '` && sudo cp ' . shellescape(s:usr_font_path) . ' ' . shellescape(s:system_font_path)
        endif
    endif
endfunction
if !exists(":InstallAirLineFont")
    command -nargs=0 InstallAirLineFont :call InstallAirLineFont()
endif

" 设置字体
set guifont=DroidSansMono\ Nerd\ Font\ 11
" 状态栏开启图标支持
let g:airline_powerline_fonts = 1

符号栏

符号栏majutsushi/tagbar 能够在侧边栏按照组织层级显示当前编辑文件中的类、函数等符号。
tagbar
推荐配置:

1
2
3
4
5
6
7
8
9
10
11
" 打开/关闭符号栏
nmap <F8> :TagbarToggle<CR>
" 符号栏宽度
let g:tagbar_width=30
" 重映射显示完整符号的快捷键,避免和<leader>键冲突
let g:tagbar_map_showproto = "<leader><leader>"
augroup tagbar_
    autocmd!
    " 打开代码文件自动打开符号栏
    autocmd BufReadPost * if count(['c','cpp','python','java','scala','go'], &ft) | call tagbar#autoopen() | endif
augroup END

minimap

severin-lemaignan/vim-minimap
vim-minimap
推荐配置:

1
2
3
4
5
6
" 打开/关闭minimap
let g:minimap_toggle='<F12>'
" minimap显示字符
let g:minimap_show='<Char-172>'
let g:minimap_update='<Char-172>'
let g:minimap_close='<Char-172>'

剪贴板

junegunn/vim-peekaboo 是剪贴板可视化插件
vim-peekaboo
svermeulen/vim-yoink 可以在剪贴板的多个历史值间快速切换。
yoink
推荐配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
" vim-yoink
" 粘贴之后光标位置保持不变
let g:yoinkMoveCursorToEndOfPaste = 1
" 同步yoink内容到vim数字剪贴板
let g:yoinkSyncNumberedRegisters = 1
" 粘贴剪贴板下一个内容
nmap <leader>j <plug>(YoinkPostPasteSwapBack)
" 粘贴剪贴板上一个内容
nmap <leader>k <plug>(YoinkPostPasteSwapForward)
" 向下滚动所有剪贴板内容
nmap [y <plug>(YoinkRotateBack)
" 向上滚动所有剪贴板内容
nmap ]y <plug>(YoinkRotateForward)
" 复制之后光标位置不变
nmap y <plug>(YoinkYankPreserveCursorPosition)
" 复制之后光标位置不变
xmap y <plug>(YoinkYankPreserveCursorPosition)
" 粘贴,并且启用剪贴板内容切换功能,visual模式下的功能将在后续由vim-subversive插件支持
nmap p <plug>(YoinkPaste_p)
nmap P <plug>(yoinkpaste_p)

函数签名

Shougo/echodoc.vim在命令栏显示函数签名, 当编辑参数时,能够同时提示当前正在编辑的参数的签名
echodoc
推荐配置:

1
2
3
4
5
" 增加命令栏高度,用于显示签名
set cmdheight=2
" 默认打开函数签名显示
let g:echodoc#enable_at_startup = 1
let g:echodoc#enable_force_overwrite = 1

可打印字符显示

bitc/vim-bad-whitespace 高亮显示行尾多余的空格
推荐配置:

1
2
3
4
5
6
" 默认关闭可打印字符的显示
set nolist
" F3 显示可打印字符开关
nnoremap <F3> :set list! list?<CR>
" 可打印字符显示方式
set listchars=eol:$,tab:^I,space:.

markdown预览

plasticboy/vim-markdown 提供markdown文件高亮显示,并支持在浏览器实时预览markdown效果。 推荐配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
" 指定浏览器
let g:mkdp_path_to_chrome = "google-chrome-stable"
" 禁止折叠
let g:vim_markdown_folding_disabled = 1
" 支持数学公式
let g:vim_markdown_math = 1
" 禁止默认快捷键
let g:vim_markdown_no_default_key_mappings = 1
" 离开markdown文件buffer时自动关闭浏览器
let g:mkdp_auto_close = 0
" 额外的语言block名称映射
let g:vim_markdown_fenced_languages = ['c++=cpp', 'viml=vim', 'bash=sh', 'ini=dosini', 'javescript=js']
" 显示所有markdown特殊字符
let g:vim_markdown_conceal = 0
let g:tex_conceal = ""
" 支持json格式化
let g:vim_markdown_frontmatter = 1
" 打开光标下链接
map gx <Plug>(Markdown_OpenUrlUnderCursor)
" 编辑光标下链接
map ge <Plug>Markdown_EditUrlUnderCursor)

缩进线

Yggdroot/indentLine 在tab或空格缩进处显示缩进线
indentLine
推荐配置:

1
2
3
4
5
6
7
8
9
" 开启缩进线
let g:indentLine_enabled = 1
" json和markdown文件禁用缩进线,避免特殊的显示问题
let g:vim_json_syntax_conceal = 0
let g:indentLine_fileTypeExclude = ['json', 'markdown']
augroup indentline_
    autocmd!
    autocmd BufRead,BufNewFile * if count(['markdown'], &ft) | setlocal conceallevel=0 | endif
augroup END

其他界面显示配置

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
" 修改终端标题
set title
" 显示当前vim模式
set showmode
" 在上下移动光标时,光标的上方或下方至少会保留显示的行数
set scrolloff=5
" 自动格式化选项
set formatoptions=tcqr
" 按照缩进折叠
set foldmethod=indent
" 打开文件时默认不折叠
set foldlevelstart=99
" 高亮匹配字符
set incsearch
" 在右下角显示一个完整的命令已经完成的部分
set showcmd
" 总是在窗口的右下角显示行列信息
set ruler
" 不要自动折行
set nowrap
" 显示行号
set number
" 状态栏同时显示了所在的行列,总的行列,所在的百分比等等
set laststatus=2
" 输入括号时显示配对的括号
set showmatch
" 高亮最后一次搜索的文本
set hlsearch
" gvim行高
set linespace=-2
" 隐藏所有的菜单栏/滚动条
set guioptions-=m
set guioptions-=b
set guioptions-=l
set guioptions-=L
set guioptions-=r
set guioptions-=R
set guioptions-=t
set guioptions-=T

编辑

文件编辑

光标移动

光标移动是文本编辑中最高频的动作,vim中提供了大量的快捷键用于光标移动, 包括同方向多次移动、行内字符跳转、搜索跳转等等,下面的插件几乎将光标移动优化到了 极致,通常只需要3-4次击键即可跳转到当前文件可见部分的任意位置。
easymotion/vim-easymotion 是跳转插件,通常只需3-4次击键即可跳转到任意指定字符位置。
easymotion
推荐配置:

1
2
3
4
5
6
7
8
9
10
11
12
" 设置easymotion的触发键
let g:EasyMotion_leader_key = '\'
" 允许设置默认快捷键
let g:EasyMotion_do_mapping = 1
" 智能大小写匹配
let g:EasyMotion_smartcase = 1
" 按回车自动跳到第一个匹配
let g:EasyMotion_enter_jump_first = 1
" s查找字符
nmap s <Plug>(easymotion-overwin-f)
xmap s <Plug>(easymotion-bd-f)
omap s <Plug>(easymotion-bd-f)

justinmk/vim-sneak是短跳转插件, 当跳转的指定字符在可见区域内重复次数较低时,通常只需2-3次击键即可跳转到任意指定 字符位置,比 easymotion/vim-easymotion 少一次击键。
sneak
推荐配置:

1
2
3
4
5
6
7
8
9
10
11
" 开启跳转标签
let g:sneak#label = 1
" 标签字符序列
let g:sneak#target_labels = ";sftunq/SFGHLTUNRMQZ?0123456789"
" f正向查找字符,F反向查找字符
nnoremap <silent> f :<C-U>call sneak#wrap('',           1, 0, 1, 1)<CR>
nnoremap <silent> F :<C-U>call sneak#wrap('',           1, 1, 1, 1)<CR>
xnoremap <silent> f :<C-U>call sneak#wrap(visualmode(), 1, 0, 1, 1)<CR>
xnoremap <silent> F :<C-U>call sneak#wrap(visualmode(), 1, 1, 1, 1)<CR>
onoremap <silent> f :<C-U>call sneak#wrap(v:operator,   1, 0, 1, 1)<CR>
onoremap <silent> F :<C-U>call sneak#wrap(v:operator,   1, 1, 1, 1)<CR>

标记(mark)

标记是vim中用于在文件间或文件内跳转的工具。当需要在多个特定位置之间跳转时, 首先在这些位置设置标记,接着就可以在不同的标记之间快速跳转。 vim使用26个英文字母作为标记的可选名称。 当标记名称为小写时,标记仅在当前文件有效, 仅能在当前文件的不同行之间跳转,不同文件间同名的小写标记不冲突; 当标记名称为大写时,全局有效,能够在不同的文件之间跳转。 当标记较多时,vim内置的标记命令不易使用, kshenoy/vim-signature 简化了设置标记的操作,并且会在对应位置可视化显示标记。
标记间跳转可以使用vim内置的跳转指令', 推荐使用Leaderf插件的标记查找功能。
LeaderF-mark
推荐配置:

1
2
3
4
5
6
7
8
9
10
" 高亮显示标记
let g:SignatureMarkTextHLDynamic=1
let g:SignatureMarkerTextHLDynamic=1
" 删除所有标记时需要确认
let g:SignaturePurgeConfirmation=1
" 常用默认快捷键
" m.        当前行没有标记时,使用下一个不冲突的字母放置标记,当前行有标记时,删除标记
" m<Space>  删除当前文件所有标记
" ]`        跳转到下一个标记位置
" [`        跳转到上一个标记位置

搜索

haya14busa/incsearch.vim 实时高亮正则表达式搜索匹配项。
incsearch
haya14busa/incsearch-fuzzy.vim 实现模糊搜索,并高亮匹配项。
incsearch-fuzzy
推荐配置:

1
2
3
4
5
6
7
8
9
10
" 高亮匹配字符
set incsearch
" 替换默认搜索
map /  <Plug>(incsearch-forward)
map ?  <Plug>(incsearch-backward)
map g/ <Plug>(incsearch-stay)
" 模糊搜索
map z/ <Plug>(incsearch-fuzzy-/)
map z? <Plug>(incsearch-fuzzy-?)
map zg/ <Plug>(incsearch-fuzzy-stay)

rking/ag.vim 在目录中递归搜索字符串,是更加高效的grep替代者。
ag

替换

tpope/vim-abolish 提供增强的字符串替换功能。
abolish
svermeulen/vim-subversive 以可视化的方式提供字符串替换功能, 替换的同时支持vim-yoink插件提供的剪贴板切换功能。
subversive
推荐配置:

1
2
3
4
5
6
7
8
9
10
11
" 在指定范围内替换visual模式选中字符串
" 范围的指定方式与vim光标移动的操作相同,例如:10n表示向下10行
" 指定范围通常使用gg(文件开头)、G(文件末尾),或配合easymotion插件。
xmap <leader>ss <plug>(SubversiveSubstituteRange)
xmap <leader>sS <plug>(SubversiveSubstituteRangeConfirm)
" 在指定范围内替换visual模式选中字符串,匹配时忽略大小写
xmap <leader>si <plug>(SubversiveSubvertRange)
xmap <leader>sI <plug>(SubversiveSubvertRangeConfirm)
" 在visual模式下提供支持剪贴板切换功能的粘贴
xmap p <plug>(SubversiveSubstitute)
xmap P <plug>(SubversiveSubstitute)

多光标

terryma/vim-multiple-cursors 增强了vim的多光标编辑能力。
multiple-cursors
multiple-cursors
multiple-cursors
multiple-cursors
推荐配置:

1
2
3
4
5
6
7
8
9
10
" 性能优化,在multiple-cursor期间禁用一些插件
function! Multiple_cursors_before()
    let s:old_ycm_whitelist = g:ycm_filetype_whitelist
    let g:ycm_filetype_whitelist = {}
    set foldmethod=manual
endfunction
function! Multiple_cursors_after()
    let g:ycm_filetype_whitelist = s:old_ycm_whitelist
    set foldmethod=indent
endfunction

成对符号

tpope/vim-surround 能够方便的编辑成对符号。
surround
jiangmiao/auto-pairs 自动补全成对符号。

智能区域选择

terryma/vim-expand-region 能够智能的扩大/缩小选中区域。
vim-expand-region
推荐配置:

1
2
3
" v选择下个单词/段落 ctrl-v回退选择
vmap v <Plug>(expand_region_expand)
vmap <C-v> <Plug>(expand_region_shrink)

代码开发

代码补全引擎

neoclide/coc.nvim 是基于LSP(language server protocol)的智能引擎,在vim中体验到和IDE完全一致的补全和诊断体验

coc.nvim

推荐配置:

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
" 使用<c-space>结束补全
inoremap <silent><expr> <c-space> coc#refresh()
" 使用`[g` and `]g` 跳转到下一个问题诊断处
nmap <silent> [g <Plug>(coc-diagnostic-prev)
nmap <silent> ]g <Plug>(coc-diagnostic-next)

" 跳转到定义位置
nmap <silent> <C-g> <Plug>(coc-definition)
nmap <silent> <leader>gd <Plug>(coc-definition)
" 跳转到类型定义位置
nmap <silent> <leader>gt <Plug>(coc-type-definition)
" 跳转到实现位置
nmap <silent> <leader>gi <Plug>(coc-implementation)
" 跳转到引用位置
nmap <silent> <leader>gr <Plug>(coc-references)
" 修复当前行可修复的第一个错误修复操作。
nmap <leader>gy <Plug>(coc-fix-current)

" 在预览窗口中展示光标下符号的文档
nnoremap <silent> <leader>gh :call <SID>show_documentation()<CR>
function! s:show_documentation()
    if (index(['vim','help'], &filetype) >= 0)
        execute 'h '.expand('<cword>')
    else
        call CocActionAsync('doHover')
    endif
endfunction
augroup coc_
    autocmd!
    " 自动高亮光标下的符号
    autocmd CursorHold * silent call CocActionAsync('highlight')
augroup END
" 显示所有诊断信息
nnoremap <silent> <leader>ga  :<C-u>CocList diagnostics<cr>
" 重命名光标下的符号
nmap <leader>gn <Plug>(coc-rename)

let g:coc_global_extensions = []
let g:coc_global_extensions += ['coc-ultisnips']
let g:coc_global_extensions += ['coc-yaml']
let g:coc_global_extensions += ['coc-highlight']
let g:coc_global_extensions += ['coc-git']
let g:coc_global_extensions += ['coc-vimlsp']
let g:coc_global_extensions += ['coc-tsserver']
let g:coc_global_extensions += ['coc-json']
let g:coc_global_extensions += ['coc-java']
let g:coc_global_extensions += ['coc-metals']
let g:coc_global_extensions += ['coc-markdownlint']
let g:coc_global_extensions += ['coc-jedi']
let g:coc_global_extensions += ['coc-go']

let g:coc_global_extensions += ['coc-clangd']
call coc#config('clangd.semanticHighlighting', 1)
" 在C++的头文件和实现文件间切换
nmap <silent> <Leader>a :CocCommand clangd.switchSourceHeader<cr>
highlight LspCxxHlGroupMemberVariable ctermfg=LightGray  guifg=LightGray

C++工程中,如果没有配置编译参数、include路径等设置,自动补全效果很差,但是配置这些参数是 比较麻烦的,可以使用rizsotto/Bear自动生成 配置文件compile_commands.json,将配置文件放在工程目录下,智能引擎即可自动加载

Valloric/YouCompleteMe 是上一代vim代码补全引擎,可完全被coc.nvim替换,已不再推荐使用。 支持C、C++、Go、Python、C#、JavaScript、TypeScript、Rust、Java、Ruby、PHP 等多种语言的自动补全、语法检查、代码跳转等。
YouCompleteMe
推荐配置:

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
" 自动检测ycm额外配置文件路径.ycm_extra_conf.py
if !empty(glob("~/.vim/bundle/YouCompleteMe/cpp/ycm/.ycm_extra_conf.py"))
    let g:ycm_global_ycm_extra_conf = "~/.vim/bundle/YouCompleteMe/cpp/ycm/.ycm_extra_conf.py"
endif
if !empty(glob("~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp/ycm/.ycm_extra_conf.py"))
    let g:ycm_global_ycm_extra_conf = "~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp/ycm/.ycm_extra_conf.py"
endif
if !empty(glob("~/.vim/bundle/YouCompleteMe/third_party/ycmd/examples/.ycm_extra_conf.py"))
    let g:ycm_global_ycm_extra_conf = "~/.vim/bundle/YouCompleteMe/third_party/ycmd/examples/.ycm_extra_conf.py"
endif
if !empty(glob(".ycm_extra_conf.py"))
    let g:ycm_global_ycm_extra_conf = ".ycm_extra_conf.py"
endif
" 自动加载.ycm_extra_conf.py,不提示
let g:ycm_confirm_extra_conf=0
" 补全功能在注释中同样有效
let g:ycm_complete_in_comments=1
" 开启基于标签引擎
let g:ycm_collect_identifiers_from_tags_files=1
" 从第一个键入字符就开始罗列匹配项
let g:ycm_min_num_of_chars_for_completion=1
" 禁止缓存匹配项,每次都重新生成匹配项
let g:ycm_cache_omnifunc=0
" 语法关键字补全
let g:ycm_seed_identifiers_with_syntax=1
" 在字符串输入中也能补全
let g:ycm_complete_in_strings = 1
" 在注释中也开启补全
let g:ycm_complete_in_comments = 1
" 禁用eclim诊断,避免冲突
let g:EclimFileTypeValidate = 0
" 总是打开错误提示窗口
let g:ycm_always_populate_location_list = 1
augroup ycm_
    autocmd!
    " ctrl-g跳到定义
    " ]l or [l跳到下/上一个错误
    autocmd BufRead,BufNewFile * if count(['cpp','c','python','java','go'], &ft) | nmap <buffer> <C-g> :YcmCompleter GoToDefinitionElseDeclaration <C-R>=expand("<cword>")<CR><CR> |
                \ nmap <buffer> <leader>gy :YcmCompleter FixIt<CR> |
                \ nnoremap <buffer> <leader>t :YcmCompleter GetType<CR> |
                \ nmap <buffer> [l :lnext<CR> |
                \ nmap <buffer> ]l :lprevious<CR> | endif
augroup END
" 使YCM支持UltiSnips插件,自动补全代码片段
let g:ycm_key_list_select_completion = ['<C-n>', '<Down>']
let g:ycm_key_list_previous_completion = ['<C-p>', '<Up>']
let g:ycm_max_diagnostics_to_display = 0
let g:SuperTabDefaultCompletionType = '<C-n>'

标签系统

标签系统能够根据源码文件抽取类、函数、变量的等符号作为标签,用于代码结构展示、 代码跳转、查找、自动补全等。 最广泛使用的标签系统有ctags和gnu global,其中gnu global速度更快且支持增量更新, 支持25种语言的标签抽取。 ludovicchabant/vim-gutentags 能够自动生成并更新标签文件。 skywind3000/gutentags_plus 提供根据标签文件进行代码跳转、查找的能力, 并设置相应的配置, 使YouCompleteMe、tagbar等依赖标签系统的插件, 能根据标签进行代码补全、标签展示等。
推荐配置:

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
" 禁用默认快捷键
let g:gutentags_plus_nomap = 1
" 自动切换到quickfix窗口
let g:gutentags_plus_switch = 1
" 查找符号
noremap <silent> <leader>gs :GscopeFind s <C-R><C-W><cr>
" 查找定义
noremap <silent> <leader>gg :GscopeFind g <C-R><C-W><cr>
" 查找调用者
noremap <silent> <leader>gc :GscopeFind c <C-R><C-W><cr>
" 查找字符串
noremap <silent> <leader>gt :GscopeFind t <C-R><C-W><cr>
" 按照egrep规则从标签中查找
noremap <silent> <leader>ge :GscopeFind e <C-R><C-W><cr>
" 查找文件
noremap <silent> <leader>gf :GscopeFind f <C-R>=expand("<cfile>")<cr><cr>
" 查看include当前文件的文件
noremap <silent> <leader>gi :GscopeFind i <C-R>=expand("<cfile>")<cr><cr>
" 查找当前函数调用的函数
noremap <silent> <leader>gd :GscopeFind d <C-R><C-W><cr>
" 查找当前符号赋值位置
noremap <silent> <leader>ga :GscopeFind a <C-R><C-W><cr>
" 设定项目根目录额外标志:除了 .git/.svn 外,还有 .root 文件
let g:gutentags_project_root = ['.root']
" 默认生成的数据文件集中到 ~/.cache/tags 避免污染项目目录
let g:gutentags_cache_dir = expand('~/.cache/tags')
" 追踪链接
let g:gutentags_resolve_symlinks = 1
" 默认禁用自动生成
let g:gutentags_modules = []
" 优先使用gnu global(gtags),若没有gtags,使用ctags
if executable('gtags') && executable('gtags-cscope')
    let g:gutentags_modules += ['gtags_cscope']
elseif executable('ctags')
    " 如果有 ctags 可执行就允许动态生成 ctags 文件
    let g:gutentags_modules += ['ctags']
endif
" 设置 ctags 的参数
let g:gutentags_ctags_tagfile = '.tags'
let g:gutentags_ctags_extra_args = []
let g:gutentags_ctags_extra_args = ['--fields=+niaztKS', '--extra=+qf']
let g:gutentags_ctags_extra_args += ['--c++-kinds=+px']
let g:gutentags_ctags_extra_args += ['--c-kinds=+px']
" 禁用 gutentags 自动加载 gtags 数据库的行为
" 避免多个项目数据库相互干扰,由gutentags_plus自动切换
let g:gutentags_auto_add_gtags_cscope = 0
" 手动更新tag快捷键
let g:gutentags_define_advanced_commands = 1
nmap <leader>u :GutentagsUpdate! <CR><CR>

代码片段

SirVer/ultisnips是代码片段框架, honza/vim-snippets 定义了大量各种语言的代码片段,代码片段会由YouCompleteMe等补全插件自动调用, 进行代码片段补全。
ultisnips
推荐配置:

1
2
3
4
5
6
" 执行片段补全快捷键
let g:UltiSnipsExpandTrigger = "<tab>"
" 跳转到下一个待填充片段位置
let g:UltiSnipsJumpForwardTrigger = "<tab>"
" 跳转到前一个待填充片段位置
let g:UltiSnipsJumpBackwardTrigger = "<s-tab>"

静态检查

YouCompleteMe插件对于支持的语言提供了静态检查能力, 但是还有大量格式的文件需要静态检查能力,比如protobuf、thrift、json等, w0rp/ale对几乎所有的代码文本提供静态检查能力, 是YouCompleteMe的良好补充。
ale
推荐配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
" 在airline显示ale的状态
let g:airline#extensions#ale#enabled = 1
" 自动打开错误列表
let g:ale_open_list = 1
" 对YouCompleteMe插件支持较好的语言不使用
let g:ale_linters = {'c': [], 'cpp': [], 'java': []}
augroup ale_0_
    autocmd!
    autocmd FileType c,cpp,java setlocal fdm=syntax | setlocal fen
augroup END
let ale_blacklist = ['c', 'cpp', 'java']
" 跳转到上/下一个错误
augroup ale_1_
    autocmd!
    autocmd BufRead,BufNewFile * if count(s:ale_blacklist, &ft) | nmap <buffer> [l <Plug>(ale_previous_wrap) |
                \ nmap <buffer> ]l <Plug>(ale_next_wrap) | endif
augroup END

格式化

chiel92/vim-autoformat 自动格式化代码。
autoformat
推荐配置:

1
2
3
4
5
6
" Normal模式对整个文件格式化
nmap <leader>i :Autoformat<CR>
" Visual模式对选定的文本格式化
vmap <leader>i :Autoformat<CR>
" 更改python默认的格式化工具
let g:formatters_python = ["yapf","autopep8"]

注释

scrooloose/nerdcommenter 对大量的语言提供注释/取消注释选中文本的能力。
推荐配置:

1
2
3
4
5
" 注释符号后自动添加空格分隔符
let NERDSpaceDelims=1
" 常用默认快捷键,visaul模式使用,对选中的文本生效
" <leader>c<space> |NERDComToggleComment| 使用行注释(如//)符注释/取消注释
" <leader>cm |NERDComMinimalComment| 使用块注释符(如/**/)注释

帮助手册

vim内置命令的帮助直接使用:help命令即可, 如:help autopairs查看auto-pairs插件的帮助说明, :help incsearch查看vim内置配置incsearch的帮助说明。
linux中最广泛使用的帮助手册是man手册, vim-utils/vim-man 能够在vim中查看man手册。
man

文件头

大多数项目会在文件开头部分添加一段格式化的说明性文字,如版权、作者、 授权、创建时间、修改时间等, alpertuna/vim-header 能够添加并自动更新文件头。
文件头示例:

1
2
3
4
5
# File: start.sh
# Author: Your Name <your@mail>
# Date: 13.03.2016
# Last Modified Date: 13.03.2016
# Last Modified By: Your Name <your@mail>

推荐配置:

1
2
3
4
5
6
7
8
9
let g:header_field_author = 'Your Name'
let g:header_field_author_email = 'Your Email'
let g:header_auto_add_header = 0
let g:header_auto_update_header = 1
let g:header_field_filename = 0
let g:header_field_timestamp_format = '%Y-%m-%d %H:%M:%S'
let g:header_field_copyright = 'Copyright (c) %Y Your Company Inc. All rights reserved.'
let g:header_alignment = 1
let g:header_max_size = 20

其他编程语言特定插件

rustushki/JavaImp.vim 能自动添加Java import,能查看JavaDoc,查看JDK源码等。
推荐配置:

1
2
3
4
5
6
7
8
let g:JavaImpPathSep = ';'
let g:JavaImpPaths =
            \ "/usr/lib/jvm/default-java/src/;" .
            \ $HOME . "/project/src/java;"
let g:JavaImpDocPaths = "/usr/lib/jvm/default-java/docs/api;" .
            \ "/project/docs/api"
let g:JavaImpDocViewer = "google-chrome"
let g:JavaImpSortPkgSep = 1

python-mode/python-mode 提供大量python相关的功能,如符号高亮、virtualenv支持、添加/移除断点、 运行python文件、查看pydoc等。
推荐配置:

1
2
3
4
5
6
" 关闭部分YouCompleteMe重复的功能
let g:pymode_folding = 0
let g:pymode_rope_completion = 0
let g:pymode_lint_signs = 0
let g:pymode_lint = 0
let g:pymode_rope = 0

ensime/ensime-vim 提供scala语言的自动补全、错误检查、重构等支持
推荐配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
" 自动设置sbt插件
let s:is_sbt_ensime_exsist=str2nr(system('cat ~/.sbt/1.0/plugins/plugins.sbt | grep "sbt-ensime" | wc -l'))
if s:is_sbt_ensime_exsist == 0
    execute 'silent !mkdir -p ~/.sbt/1.0/plugins'
    execute 'silent !echo "addSbtPlugin(\"org.ensime\" \% \"sbt-ensime\" \% \"2.5.1\")" >> ~/.sbt/1.0/plugins/plugins.sbt'
endif
augroup ensime-vim_
    autocmd!
    autocmd BufWritePost *.scala silent :EnTypeCheck
    " 获取光标下变量的类型
    autocmd BufRead,BufNewFile *.scala nnoremap <buffer> <leader>t :EnType<CR>
    " 跳转到声明
    autocmd BufRead,BufNewFile *.scala nnoremap <buffer> <C-g> :EnDeclaration<CR>
augroup END

derekwyatt/vim-fswitch 在C/cpp的头文件和源文件之间快速切换。
推荐配置:

1
2
3
4
5
6
7
8
9
10
" 切换快捷键
nmap <silent> <Leader>a :FSHere<cr>
augroup fswitch_cpp
    " 支持cc结尾的文件
    autocmd!
    autocmd BufEnter *.cc let b:fswitchdst  = 'h'
    autocmd BufEnter *.cc let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include'
    autocmd BufEnter *.h let b:fswitchdst  = 'cpp,cc,C'
    autocmd BufEnter *.h let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/'
augroup END

GDB调试

vim内置了GDB调试的支持插件,通过命令:packadd termdebug加载插件。:Termdebug 命令启动调试器,例如Termdebug /usr/bin/vim /var/logs/core_dump
termdebug
推荐配置:

1
2
" 默认加载termdebug插件
:packadd termdebug

编辑历史

mbbill/undotree记录并显示文件的编辑历史, 方便撤销/回滚。
undotree
推荐配置:

1
2
3
4
5
6
7
8
9
10
11
if has("persistent_undo")
    " 设置编辑历史存储路径
    set undodir=$HOME/.vim/undo
    " 启用撤销起始功能
    set undofile
    " 最大的可撤销步数
    set undolevels=100
    " 文件小于10000行时记录整个温戛纳内容用于文件恢复
    set undoreload=10000
endif
nnoremap <F6> :UndotreeToggle<cr>

其他

大文本粘贴

在vim中粘贴大量文本时,由于自动缩进机制的存在, 可能导致粘贴文本的格式异常,当粘贴大量文本时,通常需要设置vim为粘贴模式, 即执行命令:set pasteroxma/vim-paste-easy插件能在粘贴大量文本 时自动切换模式。
推荐配置:

1
2
" 不显示paste模式切换信息
let g:paste_easy_message = 0

自动创建目录

vim中执行:w:u等保存文件命令时,若文件父目录不存在 pbrisbin/vim-mkdir 能够自动创建目录。

sudo权限获取

chrisbra/SudoEdit.vim 能够以sudo权限读写文件。

工程管理

文件浏览

目录树是浏览文件结构的高效工具,在编写代码的任务中, 大多数时间我们都对项目文件结构非常了解, 更加需要能够根据路径或文件名快速打开相应文件。 Yggdroot/LeaderF 是强大的模糊/正则搜索工具, 能够模糊/正则搜索文件名、完整路径、函数、文件内容、代码标签(tags)、标记(mark)等。
按照名称模糊查找文件:
LeaderF-nameonly
按照完整路径模糊查找文件:
LeaderF-fullpath
按名称模糊查找已经打开的文件:
LeaderF-buffer
模糊查找当前文件中函数:
LeaderF-func
模糊查找当前文件中标签(tags):
LeaderF-tag
模糊查找当前文件中的标记(mark):
LeaderF-mark
模糊查找当前文件内容:
LeaderF-file
模糊查找命令历史:
LeaderF-cmd
模糊查找搜索历史:
LeaderF-search
推荐配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
" 查找文件
let g:Lf_ShortcutF = '<C-p>'
" 查找打开的文件
let g:Lf_ShortcutB = '<leader>lb'
" 查找隐藏文件
let g:Lf_ShowHidden = 1
" 查找标签
nmap <leader>lt :LeaderfBufTag<CR>
" 查找函数
nmap <leader>lf :LeaderfFunction<CR>
" 查找文件内容
nmap <leader>ll :LeaderfLine<CR>
" 查找标记
nmap <leader>lm :LeaderfMarks<CR>
" 查找命令历史
nmap <leader>lc :LeaderfHistoryCmd<CR>
" 查找搜索历史
nmap <leader>ls :LeaderfHistorySearch<CR>

git

tpope/vim-fugitive 提供了在vim中执行git命令的能力, 如:Git blame %查看当前文件修改者,:Git commit %提交当前文件等。
airblade/vim-gitgutter 能够显示当前文件各行的git状态。
gregsexton/gitv是类似于gitk的工具, 以更加清晰的可视化方式支持几乎所有的git操作,包括分支切换、rebase、 merge、commit等。

尾声

经过以上配置的vim已经是高效的开发环境,但是开发工作不只是代码编辑, 后续的拔掉鼠标系列将陆续介绍一系列提高纯键盘开发调试效率的工具:zsh、 autojump、tmux、chrome cVim plugin…
未完待续…