本文共 8927 字,大约阅读时间需要 29 分钟。
刚接触 Vim 的同学往往因为无法搭建开发环境而“从入门到放弃”,本文旨在帮助这些同学搭建开发环境,聚焦于最核心的开发需求,忽略配色调字体等细节。如果需要开箱即用的 Vim 配置,可以参考相关文档。
在 Vim 中,插件只是一些脚本,存放在特定的目录中,运行时将它们所在的目录加入到 runtimepath 中。Vim 8 内置了插件管理功能,但不支持高级管理功能。Vimmers 实现了多个插件管理器,可以自动下载、更新、安装插件,还可以延迟加载、按需加载,提高启动速度。
上古时期流行手动或使用插件管理器,但这些方式已经落伍。以下是目前比较流行的三个插件管理器:
以上三款插件管理器风格各不相同,都有大量用户,功能相当完善,根据自己的喜好选取即可。推荐新手选择 vim-plug,启动时间特别敏感的同学可以考虑 dein.vim。我的配置在安装 70 余个插件的情况下,启动仅需 60 余秒。
在 .vimrc 中写入以下代码:
call plug#begin('~/.vim/plugged') " 插件目录Plug 'junegunn/vim-easy-align' " 用户名/插件名,默认从 github 下载安装Plug 'https://github.com/junegunn/vim-github-dashboard.git' " 从特定 URL 下载安装call plug#end() 在 .vimrc 中写入以下代码:
set runtimepath+=~/.vim/plugged/repos/github.com/Shougo/dein.vim " 将 dein.vim 添加到 runtimepathif dein#load_state('~/.vim/plugged') " 参数是插件目录 call dein#begin(general#plugin_dir) call dein#add('~/.vim/plugged/repos/github.com/Shougo/dein.vim') " 安装 dein.vim call dein#add('junegunn/vim-easy-align') " 用户名/插件名,默认从 github 下载安装 call dein#end() call dein#save_state()endifif dein#check_install() " 自动安装未安装的插件 call dein#install()endif 在安装插件的代码后,加上这两行设置:
filetype plugin indent onsyntax on
这样可以确保特定于文件类型的插件正常工作。
最简单的代码补全方式是利用生成 tag 文件,补全插件解析 tag 文件进行补全,这种方式有以下两个好处:
基于 tag 的补全不够智能,后来又诞生了 life-changer 级别的补全插件,可以提供 IDE 级别的代码补全。但 YouCompleteMe 不是开箱即用的,需要下载依赖并编译,并且耦合度比较大,只支持特定语言(主要是 C++)。
目前体验补全体验最好的方式是基于 LSP(Language Server protocol)的方案。LSP 是一套通信协议,遵从 LSP 规范的客户端(各种编辑器/IDE)可以通过众多 LSP 服务端按协议标准进行通信,由客户端完成用户界面相关的事情,由服务端提编程语言相关的:补全,定义引用查找,诊断,帮助文档,重构等服务。LSP 的架构图如下:
有了 LSP,不同的 IDE/编辑器只需要实现 LSP 客户端,专心改进用户体验,所有补全的工作都交给 LSP 服务器。使用基于 LSP 的方案,用户可以在多种语言间无缝切换,让 Vim 支持所有语言(只要有 LSP 实现),用户只需要做以下两件事:
目前 LSP 客户端有以下几种选择:
coc.nvim 依赖于 node.js,但 node.js 已经不再支持 32 位机,因此最新的 coc.nvim 很可能无法在 32 位机上运行,请考虑其他几种方案。
目前错误检查有两种方案:
如果使用 coc.nvim,不需要额外配置,开箱即用。coc.nvim 使用 LSP 进行错误检查,不够灵活,无法使用 linter 实时检测代码。
基于外部程序的方案非常灵活,比如,可以在基础的错误检测之外同时使用 clang-tidy 等工具进行检测。目前这种方案最好的插件是 ,并且 ale 可以与 coc.nvim 共存,用 ale 做实时错误检查,coc.nvim 做补全。如果没有特殊需求,直接使用 coc.nvim 即可。
LSP 已经提供了符号索引的功能,可以方便地跳转到定义/引用。通常 LSP 的功能已经够用,但 LSP 存在以下缺点:
比如可能存在汇编和 C 混合的项目,汇编定义了一个变量在 C 中读写,LSP 无法理解汇编,找不到变量定义的地方。
我们可以使用静态代码索引工具,克服 LSP 的以上缺点。目前静态代码索引最好的方案是 ctags 和 混合使用,具体的方法参考韦应笑的深度文章,不再赘述。
在古老的 Vim 工作流中,项目的构建一直是个老大难的问题,要么手动完成,要么自己写简单的脚本完成,VSCode 引入任务系统解决了这个问题,韦易笑大佬的和 又将 VSCode 的任务系统引入到了 Vim 中,彻底改变了 Vim 的工作流。这充分体现了 Vim 的优势,Vim 用户非常乐于吸收别的编辑器的优点,让 Vim 变得更好。
asyncrun.vim 让用户可以异步运行 shell 命令,asynctasks 让用户可以将常用的命令写入到配置文件中(~/.vim/tasks.ini 或项目根目录中的 tasks.ini),一次编写多次使用。详细的使用方法请参考插件的中文文档。基本配置如下:
" 将终端放到 tab 中let g:asynctasks_term_pos = 'tab'" 设置 quickfix 大小let g:asyncrun_open = 10" 设置项目根目录标志" 实际上,许多插件都使用这种方法定位根目录,因此可以定一个变量 g:rootmarks,let g:asyncrun_rootmarks = ['.compile_commands.json', '.ccls', '.git']
以构建 CMake 项目为例,我需要以不同的模式(Debug/Release)执行 CMake,编译项目,可能还会删除二进制目录,利用这两个 life-changer 级别的插件,可以实现一键配置、编译、运行、清理目录。
基于正则表达式的语法高亮在 C++ 这种语法非常复杂的语言上表现的很差,2021 年可以彻底抛弃掉这种老掉牙的高亮方案了。请使用 nvim-treesitter 是 neovim 移植的 treesitter(是的,从别的编辑器超过来的),基于语义高亮代码,性能强,容错好。
配置代码如下:
lua" Tips:您可以在 vimrc 中进行判断,在 Vim 中使用 vim-lsp-cxx-highlight,在 neovim-nightly 中使用 nvim-treesitter,可以参考我配置中的 init.vim 和 autoload/tools.vim。
许多 Vim 外的编辑器用户喜欢使用文件树定位项目文件,但 Vimmer 更喜欢使用模糊查找插件定位文件。尽管如此,文件树也并非一无用处,在浏览自己不熟悉的项目时,文件树插件可以帮助我们了解项目结构。
NERDtree 虽然是最经典的文件树插件,但在许多介绍 Vim 的文章中被骂的狗血临头。许多人批评 NERDtree 性能差,在 Linux 这种规模的项目中会直接卡死,但应付中小型项目绰绰有余。
LeaderF 是国人开发的一款模糊查找插件,性能最强,并且支持许多插件。配置如下:
let g:Lf_PreviewResult = { \ 'File': 0, \ 'Buffer': 0, \ 'Mru': 0, \ 'Tag': 1, \ 'BufTag': 1, \ 'Function': 1, \ 'Line': 0, \ 'Colorscheme': 0, \ 'Rg': 1, \ 'Gtags': 1}let g:Lf_PreviewInPopup = 1 " 在 popup 窗口中预览结果let g:Lf_PreviewCode = 1 " 预览代码let g:Lf_RootMarkers = ['.root', 'compile_command.json', '.git'] " 你的根目录标志let g:Lf_WorkingDirectoryMode = 'A' " 设置 LeaderF 工作目录为项目根目录,如果不在项目中,则为当前目录let g:Lf_ShortcutF = " f"let g:Lf_ShortcutB = " bl"nnoremap p :LeaderfFunctionAll " 搜索函数nnoremap l :LeaderfBufTagAll " 搜索缓冲区中的 tagnnnoremap d :LeaderfTag " 搜索项目中的 tagnnnoremap h :LeaderfHelp " 搜索 vim helpnnoremap rg :Leaderf rg " 调用 ripgrep 查找字符串 现在,只要按下 <div></div> <leader><div></div> f,即使是 Linux 这种级别的项目,也能在一瞬间切换到目标文件。
既然 LeaderF 的模糊搜索功能如此强大,能不能让 LeaderF 搜索我们定义的 asynctask.vim 任务?答案当然是可以的!以下是如何在 LeaderF 中搜索任务的配置:
function! s:lf_task_source(...) let rows = asynctasks#source(&columns * 48 / 100) let source = [] for row in rows let name = row[0] let source += [name . ' ' . row[1] . ' : ' . row[2]] endfor return sourceendfunctionfunction! s:lf_task_accept(line, arg) let pos = stridx(a:line, '<') if pos < 0 return endif let name = strpart(a:line, 0, pos) let name = substitute(name, '^\s*\(.\{-}\)\s*$', '\1', '') if name != '' exec "AsyncTask " . name endifendfunctionfunction! s:lf_task_digest(line, mode) let pos = stridx(a:line, '<') if pos < 0 return [a:line, 0] endif let name = strpart(a:line, 0, pos) return [name, 0]endfunctionlet g:Lf_Extensions = get(g:, 'Lf_Extensions', {})let g:Lf_Extensions.task = { \ 'source': string(function('s:lf_task_source'))[10:-3], \ 'accept': string(function('s:lf_task_accept'))[10:-3], \ 'get_digest': string(function('s:lf_task_digest'))[10:-3], \ 'highlights_def': { \ 'Lf_hl_funcScope': '^\S\+', \ 'Lf_hl_funcDirname': '^\S\+\s*\zs<.*>\ze\s*:', \ }, \ }nnoremap T :Leaderf task " T 模糊搜索任务 调试一直是 Vim 的弱点,最近 Debug Adapter Protocol(DAP)的提出带来了一些改变。YouCompleteMe 的主要开发者 puremourning 创建了 dap.vim,这是目前最强的 Vim 调试插件,仍处于开发阶段,您如果有兴趣的话可以参考我的博客。
Tpope 的 timetravel.vim 让 Git 工作流在 Vim 中顺畅无比,使用 gitgutter 让 Git 状态在侧边栏展示。
command! -bang -nargs=* -complete=file Make AsyncRun -program=make @" 异步 git push git-gutterlet g:gitgutter_map_keys = 0nmap ghp (GitGutterPreviewHunk) " 预览修改(diff)nmap ghs (GitGutterStageHunk) " 暂存修改nmap ghu (GitGutterUndoHunk) " 丢弃修改nmap [c (GitGutterPrevHunk) " 上一处修改nmap ]c (GitGutterNextHunk) " 下一处修改
注释请使用 ally/vim-format,它易于拓展,可以支持所有文件类型。vim-format 会根据文件类型执行对应的格式化命令,C/C++ 默认使用 clang-format,所以您只需要将 .clang-format 放到项目根目录即可。
定义一个快捷键快速格式化代码:
nnoremapbf :Autoformat
目前最流行的注释/反注释是 nerdcommenter,相比于 vim-commentary 功能更强,拓展性更好,因此推荐使用 nerdcommenter。
" Add spaces after comment delimiters by defaultlet g:NERDSpaceDelims = 1" Align line-wise comment delimiters both sideslet g:NERDDefaultAlign = 'both'" Allow commenting and inverting empty lines (useful when commenting a region)let g:NERDCommentEmptyLines = 1" Enable trimming of trailing whitespace when uncommentinglet g:NERDTrimTrailingWhitespace = 1" Enable NERDCommenterToggle to check all selected lines is commented or notlet g:NERDToggleCheckAllLines = 1" Useful for comment argumentlet g:NERDAllowAnyVisualDelims = 0" Usefull when comment argumentlet g:NERDAltDelims_asm = 1
nerdcommenter 默认的快捷键请参考文档。请不要再蜗牛一样地用 :help 命令查看文档,用 <div></div> <leader><div></div> h 模糊搜索!
转载地址:http://imdyz.baihongyu.com/