10年Vimer准备转战Visual Studio Code了,这些年的Vim使用来看,和VS Code这种IDE对比下来,稳定性和高效都相对差一点,特别是近两年,频繁的在C++,Go,Lua,Python之间来回切换,能明显感觉Vim的稳定性和效率都会明显下降。不过还是总结一下Vim的使用,和一些插件,以及NeoVim,Visual Studio Code的相关配置。
Vim
为什么会走上使用vim之路,可能刚接触Linux那会(实验室的机房),扑面而来黑洞洞的bash shell,只知道能用vim来进行文件的操作,后来接触到vim的指令,以及vim的丰富的插件生态,不禁感叹vim那么强大,想着随便一个终端,连上开发服务器都可以快速的进行Coding,这可能也是让我坚持到现在的原因吧。
随着Iaas基础设施和Cloud Native的的发展,稳定,高性能的远端开发容器( or VM)+轻终端的开发模式成为了越来越多人的首选,Visual Studio Code等功能强大的IDE也能够支持连接远端服务器进行远程开发。基于docker image的不可变性,我们可以将我们的配置和插件全部镜像化,这样就可以在任何地方使用了。
Vim基础
Vim环境的配置里有两个很重要的文件~/.viminfo
和~/.vimrc
。
.viminfo
是记录vim动作的文档,也就是通过vim所进行的编辑操作,都会在该文档有所记录,这就是为什么重复编辑同一个文档时,第二次进入该文档,光标会停留在上次离开的位置。当然该文档还会标记对操作的每个文件的编辑历史及其动作。
.vimrc
是当前用户vim环境的配置文件。系统的vim环境配置文件为/etc/vim/vimrc
,一般不建议修改。.vimrc
文件默认情况下不会存在,需要用户自己创建。Vim的环境设定参数有很多,下面是一些基本常用的参数:
VIM命令 | 备注 |
---|---|
:set nu :set nonu |
显示/隐藏行号 |
:set hlsearch :set nohlsearch |
high light search针对搜索结果进行高亮 |
:set autoindent :set noautoindent |
自动缩排 |
:set backup | 自动备份文档,当更新一个档案时, 源文件会被另存成一个名为 filename~的文件 |
:set ruler | 右下角显示状态栏 |
:set backspace=(0|1|2) | 在INSERT模式下,默认可以删除任意字符,但此功能可以修改 backspace=2:可以删除任意字符 backspace=0|1:仅仅可以删除在此次进入INSERT模式下输入的字符 |
:syntax on :syntax off |
根据程序类型是否显示语法相关不同颜色 |
:set bg=dark :set bg=light |
设置背景色 |
这里需要注意的是,Windows下vim的个人配置文件在C:\用户\username\
下对应的用户名下面。需要注意的是Linux用户目录下的vim配置文件与Windows下的差异如下。
系统 | 用户vim环境配置 | 用户vim配置目录 |
---|---|---|
Windows | .vimrc | .vim |
Linux | _vimrc | vimfiles |
这两个文件默认也同样是没有的,需要自己创建。
Vim基本配置
在每次第一次使用vim时候,都需要对vim的基本环境进行自己习惯性的设置,特别是在通过vim进行代码编写的时候,还需要进行插件的安装。后面在介绍bash-support的时候也会提及,bash-support的插件中提供了一个标准化的.vimrc
,可以直接作为我们的基础.vimrc
来使用,我一直就是基于这个.vimrc
来进行定制化配置的。
下面两个基本配置是以前记录的,后面详细完善一下吧。可以先不用看
Tab
在代码编写过程中,众所周知,各个软件对tab的宽度设置不一,然后有时候又会结合空格来写代码,导致代码夹杂中tab和空格,惨不忍睹,所以很多时候我们都建议在使用任何编辑器的时候,都要将tab的宽度设置为4个字符,且转换为空白符插入:
TAB替换为空格:
1 | :set ts=4 |
空格替换为TAB:
1 | :set ts=4 |
加!
是用于处理非空白字符之后的TAB,即所有的TAB,若不加!,则只处理行首的TAB。我常用的配置是
1 | set ts=4 |
自动缩排
filetype indent on
这个命令,首先,它会检查文件的类型。这与设置语法高亮时所做的类型检查是一样的。一旦Vim确定了文件类型,它就会为此类型的文件搜索一个对应的定义其缩进风格的文件。 Vim的发布版中包含了很多为每种语言设置不同缩进风格的脚本文件(/usr/share/vim/vimfiles/indent
)。这些脚本文件正是为你在编辑中使用自动缩进功能进行准备工作。如果你不想用自动缩进的话,还可以再把它关闭掉:filetype indent off
使用自动缩进最简单的形式是打开autoindent
选项。它使用当前行前面一行的缩进。
1 | filetype indent on |
Vim插件管理器
Vim的生态相对比较丰富,有着各式各样的插件可供使用。虽然Vim从6.0开始内置支持了Vimball:一种标准化的插件打包和发布各式,让插件的安装和管理更加简单方便。但是Vim本身并不提供自动化的插件的安装,管理,分发,也就是Vim并没有直接提供Vim生态的registry软件源的服务。所以Vimball逐渐被流行的插件管理器所取代,例如:Vundle.vim和vim-plug。
现在介绍一下和对比一下我接触过比较流行的两个Vim插件管理器,Vundle.vim和vim-plug,下面简单介绍一下:
Vundle.vim
Vundle.vim本身作为Vim的一款插件,用来管理Vim的其他插件。Vim本身有一款自己的插件管理器vimball,该管理器安装插件还是要针对每一个插件,都需要进行:下载,手动安装。而vundle安装插件只需要在vimrc中添加一行就搞定了,Vundle支持的功能有以下几点:
- 通过.vimrc来跟踪和配置管理插件;
- 安装配置的插件;
- 更新配置的插件;
- 搜索指定的插件;
- 删除不使用的插件
Vundle的安装过程,以及插件的安装过程其实很简单,在github的README上都有,简述如下:
1 | git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim |
然后就可以在.vimrc
进行配置需要依赖的插件了,这里只罗列一下Vundle支持安装的插件类型,如下:
1 |
|
在.vimrc
中配置好自己需要的插件后,进入vim,在命令模式下输入PluginInstall就可以安装未安装的插件,够方便吧。Vundle支持一下命令:
1 | :PluginList //list已安装的插件 |
vim-plug
vim-plug是一款极简主义的Vim插件管理器,它的特点是:
- 安装简单:只需要一个文件,不需要引用任何代码;
- 使用简单:简明直观的语法;
- 超级快的插件安装和更新:依赖于并行操作;
- 浅克隆,减少磁盘空间和下载时间;
- 启动时按需加载,以加速vim启动;
- 可以检查和回滚更新;
- 可以支持指定插件的:Branch/tag/commit;
- 可以支持插件下载后执行:Post-update hooks
- 支持外部工具管理插件;
安装vim-plug插件管理器很方便,如下:
1 | curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ |
和Vundle一样,然后就可以在.vimrc
进行配置需要依赖的插件了,这里罗列一下vim-plug支持安装的插件类型,如下:
1 | call plug#begin() |
vim-plug支持的更详细的命令和参数选项可以参考:README
Vundle.vim和vim-plug对比
上面简单介绍了一下Vundle.vim和vim-plug,可以看到vim-plug支持的功能相对比较丰富:
- vim-plug可以指定插件的
branch/tag/commit
,而Vundle.vim不支持; - vim-plug可以支持插件安装后,设置
Post-update hooks
,而Vundle.vim不支持; - vim-plug可以支持插件按需加载,而Vundle.vim不支持;
- vim-plug可以支持插件安装时进行浅克隆,以加速安装和节省磁盘空间,而Vundle.vim不支持;
而除了Vundle.vim支持非git repo插件的安装外,其他所有功能vim-plug都支持,对比vim-plug的stars:33K,而Vundle.vim的stars:23.8k,且感觉Vundle.vim已经很不活跃了,且GitHub官方社区有个关于[VundleVim Org 被封,导致VundleVim仓库消失](The VundleVim organization has been flagged.)的讨论,综合考虑,我还是建议使用vim-plug插件来管理vim的插件;
Vim常用插件
不过官网的Scripts列表可以进行按照评分排序,排名靠前的插件还是很有用的。
bash-support
bash-support是一款为Vim 提供 Bash 脚本语言支持的插件。它为 Bash 脚本文件带来了代码片段插入,语法高亮、代码折叠、关键字补全,脚本运行,调试等基本功能,旨在提升 Vim 中编写 Bash 脚本的体验。
我觉得主要功能有(参考bash-support帮助文档 2. USAGE without GUI (Vim)
):
- 创建shell文件的时候,自动添加文件注释头;
- 快速的注释管理:行尾注释,端注释,函数注释等;
- 快速的插入声明语句:case,if,function等语句;
还有一个很赞的就是,bash-support的插件中提供了一个标准化的.vimrc
,可以直接作为我们的基础.vimrc
来使用,我一直就是基于这个.vimrc
来进行定制化配置的。
安装要求:Vim version 7.4+ or Neovim 0.2.1+;
基于vim-plug安装如下:
1 | Plug 'WolfgangMehner/bash-support', { 'commit': '99c746c' } |
安装后,简单的演示图如下:
常用的命令如下:
1 | \sc case in ... esac (n, i) |
NERDtree
NERDTree是一款功能强大的Vim文件系统浏览插件,主要的功能就是以树状结构浏览整个文件系统,并可以快速的打开文件和目录。直接通过vim-plug插件安装:
1 | Plug 'preservim/nerdtree', { 'tag': '7.1.2' } |
NERDTree的全局指令主要有以下几个,不过我一般只用快捷指令映射的:NERDTree
,:
:NERDTree [<start-directory> | <bookmark>]
:打开指定的目录树,如果不传参数,默认打开当前工作目录,否则打开对应的目录,或者书签指定的目录(后面不再提书签,因为感觉很少用到):NERDTreeVCS [<start-directory> | <bookmark>]
:和:NERDTree
类似,但是会从当前工作目录向上搜索repo版本控制所在的目录,并将其设置为root。支持的VCS包含:Git,Subversion,Mercurial,Bazaar,Darcs;:NERDTreeToggle [<start-directory> | <bookmark>]
:如果 NERDTree 窗口已打开,则关闭它,否则打开它;:NERDTreeRefreshRoot
:刷新当前NERDTree 根节点的目录树;:NERDTreeCWD
:切换NERDTree 根节点到当前的工作目录;
NERDTree还有针对目录树的Bookmark
命令,主要是用来针对目录创建书签,方便快速跳转,但是实际很少使用,有兴趣的可以查看NERDTree的帮助文档。
除了上面提供的全局命令,在NERDTree的窗口中,还提供了很多快捷映射键,下面只列出我比较常用的,具体可查看NERDTree的帮助文档(:h NERDTree
):
1 | o: 展开选择的目录,打开选择的文件 |
下面是我的.vimrc
关于NERDTree的快捷键映射:
1 | " 将:NERDTree命令映射到快捷键:\NE |
NERDTree的README中有更多实用的配置,这里不做介绍,其中有一个vim打开文件自动启动NERDTree的功能,但是我测试发现,会导致内容串行,所以就没有启用。还有一个不好的就是NERDTree没有文件递归查找的功能。
下面是NERDTree安装后的使用示意:
bufexplorer
bufexplorer插件,主要用来查看和打开已编辑过的历史文件列表,以达到快速切换文件的目的。
基于vim-plug进行安装如下:
1 | Plug 'jlanzarotta/bufexplorer', { 'commit': '20f0440' } |
安装完成后,bufexplorer的使用很方便,bufexplorer提供了内置了的快捷键映射,包括:
1 | \be: 正常打开bufexplorer |
当然可以通过:BufExplorer
命令来进行操作,如下是使用界面:
LeaderF
LeaderF是一款很强大的模糊查找工具,能够快速的定位文件,缓冲区,最近使用文件,标签等
LeaderF的安装要求:
- vim 7.4.1126+;
- Python2.7+ or Python3.1+;
- popup mode, neovim 0.5.0+ or vim 8.1.1615+ are required.
基于vim-plug进行安装如下:
1 | Plug 'Yggdroot/LeaderF', { 'tag': 'v1.25', 'do': ':LeaderfInstallCExtension' } |
为了更好的搜索性能,LeaderF需要安装C扩展(见上面插件安装过程),基于这个扩展,模糊搜索的速度可以快10倍以上。安装成功后,可以在vim中通过echo g:Lf_fuzzyEngine_C
来查看,如果输出1表示安装成功。
LeaderF支持子命令主要包括:
1 | file 在工作空间中搜索文件,可以定位和预览匹配的文件 |
Leaderf的README给出了建议的配置,我在其上面,稍作修改,如下:
1 | let g:Lf_CacheDirectory = expand('~/.vim/cache') |
针对Leaderf我觉得比较常用的是:file
,tag
,line
,mru
,下面是相关演示:
Gutentags
GutentagsVim插件用于管理Vim中非常需要的tags标签文件。它会在你工作时自动(重新)生成标签文件,而且完全不会影响到你。它甚至会尽最大努力将这些标签文件也不会妨碍到你。它没有任何依赖,只需安装就可以正常工作。
和其他大部分Vim插件一样,为了生成tags文件,Gutentags需要先弄清楚你的项目空间,为此需要它需要知道项目的根目录标记,例如版本控制的文件夹.git
,.svn
,.hg
等,这个是由环境变量let g:gutentags_project_root
来控制的,不过默认该值为空(这个设计不好,按道理应该配置上常规的root marker),所以需要我们自己安装后进行配置。
当你编辑的文件被发现位于上面标记的项目中,Gutentags将确保该项目的标签文件是最新的。之后,当你在该项目的文件中工作时,它会部分重新生成标签文件。每次保存时,它会在后台悄悄更新该文件的标签。
Gutentags很大的特性就是tags文件增量修改。Gutentags是基于ctags来生成和追加新的tags,由于ctags不支持删除,所以在文件发送变化时,Gutentags会从tags文件中删除不存在的tags,然后再通过ctags进行tags的添加,这样就可以保持tags文件和源码文件的一致性。
Gutentags足够智能,不会在你频繁保存文件或项目非常庞大时触发多个ctags进程,从而互相干扰。
Gutentags不会触动手动设置的tags
变量,它只会将自动生成的tags文件追加到tags
列表中。这样可以避免与用户手动配置的tags文件冲突。且这样其他基于tags文件的插件,也能自动使用到最新和源码一致的tags,例如LeaderF的模糊搜索也是基于tags文件。
通过vim-plug进行安装如下:
1 | Plug 'ludovicchabant/vim-gutentags', { 'commit': 'aa47c5e' } |
安装完成后,由于Gutentags的默认配置很多为空,所以需要简单配置如下:
1 | " gutentags搜索工程目录的标志,碰到这些文件/目录名就停止向上一级目录递归,默认为空" |
安装完成后,打开项目工程文件,Gutentags就会自定生成项目的tags文件,并将其添加到tags变量中,以供基于tags文件的其他Vim插件使用,如下:
1 | tags=~/.cache/tags/data-vgserver_proj-server-.tags,./tags,./TAGS,tags,TAGS |
Tagbar
Tagbar插件提供了一种简单的方式来浏览当前文件的标签,并获取其结构的概览。它通过创建一个侧边栏来实现这一点,该侧边栏按作用域显示当前文件的由ctags生成的标签。这意味着例如在C++中,方法会显示在它们所定义的类下面。
Tagbar不是一个管理tags文件的工具,它只是根据已有的tags文件,在内存中创建tags结构,以供结构的概览。
Tagbar安装依赖:
- Vim >= 7.3.1058 or 任意版本的 NeoVim.
- ctags:Tarbar强烈建议使用Universal Ctags,它是从Exuberant Ctags fork出来的项目,E-Ctags目前已经不维护了,U-Ctags更新了其很多bug和支持了更多的格式,以及unicode支持。
通过vim-plug插件安装如下:
1 | Plug 'preservim/tagbar', { 'commit': '12edcb5' } |
安装完成后,执行:Tagbar
就可以打开当前文件的结构概览了,由于比较简单就没必要配置什么快捷键了,下面我单独配置了Vim启动,自动开启Tagbar的配置:
1 | " 打开vim自动开启 |
如下是Tagbar的安装后使用效果:
其实在使用Tagbar之前还有一个历史比较悠久的taglist,在vim-scripts官网上下载量排第一,但是taglist已经基本不维护了,而且Tagbar相对taglist结构展示更加结构化,美观,且可折叠展开,如下:
Fugitive
vim-fugitive一款优秀的Git的Vim插件,它可以通过调用:Git
指令加上任意git的命令,就可以完成git一样的操作,和使用git 命令行的效果一样,不过做了一些改进,对于我来说fugitive最常用的就是可以查看任何打开文件的每行的体积历史。
通过vim-plug安装如下:
1 | Plug 'tpope/vim-fugitive', { 'commit': '2377e16' } |
安装完成后,我配置了两个快捷键:
1 | nnoremap <script> <silent> <unique> <Leader>gitb :Git blame<CR> nnoremap <script> <silent> <unique> <Leader>gitd :Git diff<CR> |
如下是通过:Git blame
命令查看文件blame的提交信息,这个是目前用的最多的,其他的git操作,其实都是在shell下操作了。
a.vim
a.vim是一个单纯用来快速切换C/C++的头文件和源文件的插件,还是比较好用的,不过已经不在维护了,最后一个版本还是停留在2007年,不过够用就行。
通过vim-plug安装如下:
1 | Plug 'vim-scripts/a.vim', { 'tag': '2.18' } |
a.vim
使用方式也很简单,支持命令:
1 | :A, 最常用的头文件和源文件切换 |
安装后使用效果如下:
vim-lua-format
vim-lua-format是一款基于LuaFormatter的Lua代码Format的Vim插件,使用起来比较简单。
基于vim-plug安装:
1 | Plug 'andrejlevkovitch/vim-lua-format', { 'commit': '9996af0' } |
安装后配置相关快捷键和命令:
1 | "快捷键方式<ctrl+c ctrl+k> |
vim-go
vim-go是一款提供Vim Go语言开发的插件,其实后面解释的YCM也支持Go语言的Semantic级别的补全,但是其他方面例如导航,错误检查,格式化等支持性和便捷性相对有一些差异,所以针对Go语言开发,vim-go插件还是很有用的。
vim-go主要支持的功能包括:
- 支持Go的:构建、运行、测试,
:GoBuild
编译包,:GoInstall
安装包,:GoTest
运行测试,:GoTestFunc
运行单个测试函数,:GoRun
快速执行当前文件 - 语法高亮、折叠与格式化;
- 调试支持:通过
:GoDebugStart
与delve
集成调试; - 代码补全与语言服务:通过
gopls
提供补全和其他语言服务支持,和YCM比很差; - 代码导航:
:GoDef
跳转到符号/声明的位置,:GoDoc
/:GoDocBrowser
查看文档; - 包管理:
:GoImport
导入包,:GoDrop
删除导入的包; - 静态分析:
:GoLint
/:GoMetaLinter
lint代码,:GoVet
捕获静态错误,:GoErrCheck
检查错误处理; - 高级源代码分析:
:GoImplements
/:GoCallees/:GoReferrers
等通过gopls
实现;
基于vim-plug安装:
1 | Plug 'fatih/vim-go', { 'commit': '14eedf6', 'do': ':GoUpdateBinaries' } |
安装完成后,基本可以做到开箱即用,无需什么配置。
YouCompelteMe
YouCompleteMe,一款Vim的代码补全引擎,它支持:
- 快速的,即时输入的代码补全功能。
- 支持模糊搜索代码补全,即所谓的identifier completer,基于标识符的代码补全。
- 支持代码理解,即理解代码上下文语义,提供更准确的建议,即所谓的 semantic completer,基于语义的代码补全。
- 支持代码重构,即根据上下文语义对代码进行修改、优化等操作。
其实简单来说也YCM的补全引擎包含两类:
- identifier completer标识符补全器,即输入任意字符而弹出的浮动窗口,基于标识符补全出现的匹配字符串后面有一个[ID];
- semantic completer语义补全器,即在 C++ 中输入 . 或 -> 后弹出的浮动窗口。
由于公司开发环境限制了Linux的版本,导致直到最近我才基于Docker Image真正配置上YouCompleteMe插件进行开发体验,果然是真香,感觉有了媲美IDE的Semantic Completion的功能。
YCM拥有一些内置的补全引擎,且支持任何符合LSP协议规范的Language Server(目前主流语言都有相对成熟的Language Server),因此可以为任何语言提供Semantic级别的代码补全。下面是一些主流语言支持LSP的Language Server列表:
1 | 1. C/C++/Objective-C (clangd) |
YCM内置包含的:
- 一个基于字符匹配的引擎,identifier completer,支持任何语言使用,就是所谓的模式搜索代码补全;
- 一个基于
clangd
的强大引擎,可以为C/C++/Objective-C/Objective-C++/CUDA 提供原生的 semantic completion。但是这里 需要注意:YCM版本内置的clangd版本编译依赖的是特定的Linux发行版(2024-03目前依赖的是Ubuntu 22.04),所以在其他发行版本不一定可以使用,因为编译依赖的glibc版本不一样。所以如果遇到这个问题,编译和配置的时候使用自定义的clangd引擎就好。 - 一个基于Jedi的自动补全引擎,用来支持Python2/Python3的 semantic completion。
- 一个基于OmniSharp-Roslyn的自动补全引擎,用于支持C#的 semantic completion。
- 一个基于Gopls自动补全引擎,用于支持Go的 semantic completion。
- 一个基于TSServer自动补全引擎,用于支持JavaScript和TypeScript的 semantic completion。
- 一个基于rust-analyzer自动补全引擎,用于支持rust的 semantic completion。
- 一个基于jdt.ls自动补全引擎,用于支持rust的 semantic completion。
- 一个为了任何语言所设计的通用的LSP实现。
- 一个基于omnifuc的自动补全引擎,它利用Vim的omnicomplete系统提供的数据,为许多其他语言(如Ruby、PHP等)提供语义补全功能。
如下是我的配置正常的YCM,在vim编辑代码时的自动补全界面:
在上面演示中,我们无需按任何快捷键就能够获取代码补全的候选列表,我们只需要正常输入代码,建议就会自动弹出,至于是否使用补全,取决于我们的选择。如果自动提示补全的代码是我们要的,按TAB键就可以将其输入,否则我们继续输入,忽略补全提示就好。
这里需要注意:自动补全的提示并不是基于输入串的前缀匹配,而是基于输入串的子序列匹配,即任何输入的字符都需要是按顺序的存在于目标字符串中,例如abc
是 xaybgc
的子序列,但不是 xbyxaxxc
的子序列。过滤之后,一个复杂的排序系统会将最相关的补全字符串排在菜单的顶部,因此一般只需要按TAB键一次。
上面演示的是基于标识符的代码补全引擎identifier completer,我们看到自动补全的字符串后面有一个[ID],即表示是由identifier completer完成的。该功能适用于任何编程语言。标识符的匹配是来源你打开的当前的文件,你访问过的其他文件,以及你的tags文件中的所有标识符。
下面的演示动画中,是YCM的基于 semantic completer语义引擎的自动补全。在输入 .
、->
或 ::
(对于 C++,其他语言使用不同的触发器),语义引擎就会被触发。
上面这个演示中也可以看到YCM一个很重要的特性:语法诊断显示功能(左侧有>>
高亮,可配置外显),该功能受 Syntastic启发(README已经停止维护,建议使用ALE,但是我觉得YCM更强大)。语法诊断显示也是自动完成的,不需要额外的快捷键或者保存文件来触发。
YCM还号称可能是唯一一个Vim自动补全引擎支持Unicode补全的,但是话说我们也没机会用到吧,谁没事输入unicode字符呀,这一般需要虚拟键盘才可以输入。
YCM针对一些编程语言提供了类似IDE的语义特性:
- 在输入函数调用的参数时显示签名帮助(参数提示)(仅Vim),例如上面的演示中就有参数签名相关的提示;
- 查找标识符的声明、定义、用法等(
CTRL-I
跳转到定义,CTRL-O跳回,配合:GoToDeclaratio
,GoToDefinition
,GoToReferences
使用),以及交互式符号查找器(通过GoToSymbol
在当前workspace进行identify搜索); - 显示类、变量、函数等的类型信息(停留在对应identify上就可以显示);
- 在 preview window,或者popup next to the cursor中显示方法、成员等的文档信息(仅限于 Vim)(停留在对应identify上就可以显示)。
- 修复常见的编码错误,例如缺少分号、错别字等(
FixIt
子命令)。 - 跨文件语义重命名变量。
- formatting code(
Format
子命令); - removing unused imports, sorting imports
如下展示了查找标识符声明,引用,显示函数说明的简单功能:
安装说明
由于YCM一直以来的策略是支持最新的Ubuntu LTS的最新vim版本,且依赖的GLIBC的都是该Ubuntu LTS的,目前支持的是Ubuntu 22.04,所以,这也是我一直以来没有正常使用YCM的原因,因为我们的开发环境的VM都是相对比较低的CentOS发行版,最近才开始尝试基于Docker Images来构建稳定的开发环境。
Runtime | Min Version | Recommended Version (full support) | Python |
---|---|---|---|
Vim | 8.2.3995 | 9.0.214 | 3.8 |
Neovim | 0.5 | Vim 9.0.214 | 3.8 |
编译ycmd过程中需要支持C++17,所以关于编译版本要求:
Compiler | Current Min |
---|---|
GCC | 8 |
Clang | 7 |
MSVC | 15.7 (VS 2017) |
除了以上的相关依赖的版本要求,还需要注意相关事项:
- 通过源码编译Python版本的时候,需要开启
--enable-shared
; - YCM需要CMake版本 >= 3.13;
- 针对C/C++的语义补全,需要clang/clangd,Clang如果使用自定义的系统版本,需要Clang 17.0.1+的版本;
安装问题记录
首先声明我使用的Image是CentOS 8.4.2105的镜像。
我的YCM安装完成后,发现只有基于identifier completer标识符补全的功能,根本没有semantic completer基于语义的提示,例如C++中输入./->都没有相关的提示,按照README说明,安装后,应该自动会有semantic completion的功能。Sad。
通过:YcmDiags
并没有任何错误,通过:YcmDebugInfo
可以看到如下:
1 | Printing YouCompleteMe debug information... |
通过:YcmDebugInfo
可以看到相关启动服务的日志文件,看一下Clangd的错误日志,/tmp/clangd_stderrrw71uww1.log
,打开果然有错误,如下,还是YCM默认继承的clangd引擎依赖的glibc版本相对较高,尝试用系统发行版支持的clang试试
1 | /root/.vim/plugged/YouCompleteMe/third_party/ycmd/third_party/clangd/output/bin/clangd: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /root/.vim/plugged/YouCompleteMe/third_party/ycmd/third_party/clangd/output/bin/clangd) |
如下,在.vimrc
中配置使用系统自带的clangd,
1 | let g:ycm_clangd_binary_path = '/bin/clangd' |
但是打开cpp文件时,又报了新的错误,如下:
1 | NoExtraConfDetected: No .ycm_extra_conf.py file detected, so no compile flags are available. Thus no semantic support for C/C++/ObjC/ObjC++. Go READ THE DOCS *NOW*, DON'T file a bug report. |
且通过:YcmDebugInfo
发现,并没有使用clangd,只是使用libclang进行semantic completion,且对应的日志中提示: Clangd at /bin/clangd is out-of-date
,
1 | 6 2024-03-21 15:34:54,499 - INFO - Adding buffer identifiers for file: /data/linux_namespace.cpp |
最终在YCM的README中,发现:如果使用自定义的clangd或者libclang,当前YCM需要clang 17.0.1的版本。真是无语…
接下来就开始了clang的编译之旅:
1 | $ git clone --depth=1 --branch=llvmorg-17.0.1 https://github.com/llvm/llvm-project.git |
构建完成后,在.vimrc
中设置使用自定义的clangd版本配置:
1 | let g:ycm_clangd_binary_path = '/usr/local/bin/clangd' |
升级clang到17.0.1后,YCM终于可以支持semantic completion了,在vim打开cpp文件后,可以看到clangd正常启动,如下:
1 | /usr/local/bin/clangd -header-insertion-decorators=0 -limit-results=500 |
但是打开clangd的错误日志发现有报错,如下:
1 | E[20:19:08.190] VFS: failed to set CWD to /data/home/walkerdu/vgserver_proj/server/build/framework/base: No such file or directory |
这里比较奇怪的是,我是在Container中安装的环境,并没有这个目录,为什么clangd会去尝试切换这个目录呢?仔细观察发现,这是项目的build路径,我挂载到Container的volume中的目录在VM中有构建过整个项目,所以clangd尝试解析build中的构建过程来进行semantic completion,所以发现了问题,这里删除后就不会有这个报错了。
配置说明
YouCompleteMe插件安装完成后,默认的配置选项都会被设置为一个合理的值,一般不需要额外设置,详细可见:~/.vim/plugged/YouCompleteMe/plugin/youcompleteme.vim
,比较重要的一些选项如下:
g:ycm_min_num_of_chars_for_completion
输入几个字符后,触发自动补全提示,默认是2,我改为了1
1 | let g:ycm_min_num_of_chars_for_completion = 1 |
g:ycm_min_num_identifier_candidate_chars
自动补全提示中最少的字符个数,默认为0,表示不限制
1 | let g:ycm_min_num_identifier_candidate_chars = 0 |
g:ycm_max_num_candidates
基于语义自动补全的最大候选个数,默认为50,官方建议不要设置为0或者超过100,因为这样会影响性能
1 | let g:ycm_max_num_candidates = 50 |
g:ycm_max_num_candidates_to_detail
1 | let g:ycm_max_num_candidates_to_detail = 0 |
g:ycm_max_num_identifier_candidates
基于标识符引擎自动补全的最大提示数量,默认10,官方建议不要设置为0或者超过100,因为这样会影响性能
1 | let g:ycm_max_num_identifier_candidates = 10 |
g:ycm_auto_trigger
是否启用自动补全,默认开启,如果关闭,会关闭YCM的identifier completer标识符补全器和semantic completer 语义补全器。
1 | let g:ycm_auto_trigger = 1 |
如果你只想关闭YCM的identifier completer,但是保留器semantic completer,那么可以通过设置g:ycm_min_num_identifier_candidate_chars
为一个很大的值来实现,例如:
1 | g:ycm_min_num_identifier_candidate_chars = 999 |
g:ycm_filetype_whitelist & g:ycm_filetype_blacklist
用来控制YCM生效的文件类型列表,配置是一个vim的字典,key是各类文件类型,例如python
,cpp
,默认g:ycm_filetype_whitelist
包含所有的文件,g:ycm_filetype_blacklist
配置一些不生效的文件,具体可以在vim中通过:h filetype
查看
1 | let g:ycm_filetype_whitelist = {'*': 1} |
BTW,vim通过set ft?
来查看一个文件的类型名字。
g:ycm_filetype_specific_completion_to_disable
此选项控制 YCM semantic completer语义补全引擎应该为哪些 Vim 文件类型关闭。默认配置:
1 | let g:ycm_filetype_specific_completion_to_disable = |
开箱即用的Vim镜像
之前为什么一直没有折腾Vim的插件就是因为,插件各种依赖,开发环境的经常变更,导致维护比较困难,后来接触到Docker后,这个问题很大程度上可以被解决,所以构建了一个基于CentOS的Vim镜像(Ubuntu的还没来得及),集成了基本的插件,大家有兴趣可以下下来试试。
基于CentOS的Vim镜像的构建文件和上下文都在这个Repo:develop-vim-centos;
develop-vim-centos的仓库里面包含基础的.vimrc
配置,包含了上面介绍所有插件和配置。
然后为了方便开箱即用,vim的所有插件已经全部下载和初始化了,可以直接在Docker Hub中下载,地址如下:
1 | # 环境初始化ok的vim镜像,运行后需要执行:PlusInstall 来安装所有vim插件 |
@Update :2024-04-07
抽空解决了Ubuntu 22.04在CentOS VM上运行目录权限的问题(看文章后面),构建了基于基于Ubuntu 22.04 LTS的Vim镜像。构建文件和上下文都在这个Repo:develop-vim-ubuntu;
可以通过如下地址直接开箱即用,vim的所有插件已经全部下载和初始化了:
1 | # 环境初始化ok的vim镜像,运行后需要执行:PlusInstall 来安装所有vim插件 |
镜像构建中的问题
基于ubuntu22.04
构建的Container中(VM是CentOS 7.2)居然连根目录的读权限都没有,奔溃了,但是可以查看默认的当前目录,且写文件也没有问题,这就很奇怪了
1 | root@ac941745381f:/# ls / |
搜索了一下,和这个Issue:https://github.com/docker-library/openjdk/issues/465比较像,我的base的OS也是CentOS Linux release 7.2 (Final)的,然后使用的Ubuntu:22.04的基础镜像,按照Issue的说法,我要升级 Docker, runc, and likely libseccomp
,这就蛋疼了。
根据下面这两个帖子,应该是Docker Image采用的Linux 发行版本的内核比较新,使用了比较新的内核特性,但是旧版本的Docker,runc没有能够做到兼容,这里主要还是Linux较新版本的内核采用了libseccomp新版本支持的特性,但是旧版本的Docker,runc不支持的原因。
https://stackoverflow.com/questions/70714357
https://stackoverflow.com/questions/69786640
那其实是找到问题所在了,使用--privileged
选项可以解决这个权限的问题,对于开发容器来说,算是先解决了,升级runc的成本有点高,先这样吧。
上面的问题解决后,下面的问题也不再发生了:
官方ubuntu镜像在apt-get update
的时候提示
1 | Err:1 http://security.ubuntu.com/ubuntu jammy-security InRelease |
全部换成阿里的软件源,有同样的问题
apt-get update --allow-insecure-repositories
然后安装的时候通过apt-get install --no-install-recommends
来绕过公钥验证完成安装。