问题

如何在 Linux 下利用 Vim 搭建 C/C++ 开发环境?

回答
在 Linux 下利用 Vim 搭建 C/C++ 开发环境是一个非常高效且强大的选择。Vim 作为一款高度可定制的文本编辑器,通过一系列插件和配置,可以 превратить его в полноценную интегрированную среду разработки (IDE)。下面我将从基础配置到进阶插件,详细介绍如何搭建 C/C++ 开发环境。

第一步:安装 Vim 和必要的工具

首先,确保你的 Linux 系统已经安装了 Vim 和 C/C++ 编译工具链。

安装 Vim: 大部分 Linux 发行版都预装了 Vim,如果没有,可以通过包管理器安装:
Debian/Ubuntu:
```bash
sudo apt update
sudo apt install vim vimaddonmanager vimaddonmanager 可选,用于管理插件
```
Fedora/CentOS/RHEL:
```bash
sudo yum install vim vimenhanced 或者 sudo dnf install vim vimenhanced
```
Arch Linux:
```bash
sudo pacman S vim
```

安装 C/C++ 编译工具链 (GCC/G++):
Debian/Ubuntu:
```bash
sudo apt install buildessential gdb
```
`buildessential` 包含了 `gcc`, `g++`, `make` 等常用的开发工具。`gdb` 是调试器。
Fedora/CentOS/RHEL:
```bash
sudo yum groupinstall "Development Tools" gdb 或者 sudo dnf groupinstall "Development Tools" gdb
```
Arch Linux:
```bash
sudo pacman S basedevel gdb
```

第二步:配置 Vim 的基本设置

Vim 的配置主要通过 `~/.vimrc` 文件进行。如果该文件不存在,可以手动创建。

创建一个 `~/.vimrc` 文件并添加以下基本配置:

```vim
" 基本设置
set nocompatible " 使用 Vim 的新特性,而不是 Vi 的兼容模式
filetype plugin indent on " 启用文件类型检测、插件和自动缩进
syntax on " 开启语法高亮
set number " 显示行号
set relativenumber " 显示相对行号(配合 normal 模式的 j/k)
set showcmd " 在底部显示命令
set showmode " 在底部显示当前模式
set incsearch " 实时搜索(输入字符时就高亮匹配项)
set hlsearch " 高亮搜索结果
set smartcase " 智能搜索,如果搜索词中有大写字母,则区分大小写,否则不区分
set autochdir " 自动加载修改过的文件
set autoread " 当文件被外部修改时,自动重新加载
set undofile " 开启撤销历史记录,保存到文件
set undodir=~/.vim/undodir " 指定撤销文件的目录
set undolevels=1000 " 设置撤销的层数
set backspace=indent,eol,start " 允许在插入模式下使用退格键删除缩进、行尾符和已输入的字符
set autoindent " 自动缩进
set smartindent " 智能缩进,为 C/C++ 提供更好的缩进支持
set tabstop=4 " 设置 Tab 键的宽度为 4 个空格
set softtabstop=4 " 设置插入模式下按 Tab 键的宽度为 4 个空格
set shiftwidth=4 " 设置自动缩进的宽度为 4 个空格
set expandtab " 将 Tab 键转换为空格
set mouse=a " 开启鼠标支持,可以在 Vim 中使用鼠标进行选择、滚动等操作
set visualbell " 禁用视觉提示音,例如错误提示
set ruler " 在右下角显示当前光标位置
set laststatus=2 " 显示状态栏

" 快捷键配置 (可选,但推荐)
" 映射 Leader Key,通常是
let mapleader = ''
let maplocalleader = ''

" 快速保存和退出
nnoremap :w
nnoremap :q
inoremap :w
inoremap :q

" 快速切换缓冲区
nnoremap a :bnext
nnoremap b :bprevious

" 快速在文件和头文件之间切换 (需要配合 ctags,后面会讲)
" nnoremap f :find

" 创建目录
" 如果 ~/.vim/undodir 不存在,则创建
if !isdirectory("~/.vim/undodir")
call mkdir("~/.vim/undodir", "p")
endif
```

解释一些关键设置:

`set number`, `set relativenumber`: 显示行号,相对行号对于跳转非常有用。
`set syntax on`: 开启语法高亮,让 C/C++ 代码更易读。
`set incsearch`, `set hlsearch`: 增强搜索体验。
`set expandtab`, `set tabstop=4`, `set shiftwidth=4`: 统一代码风格,使用 4 个空格代替 Tab。
`set undofile`, `set undodir`: 强大的撤销功能,即使关闭 Vim,你的编辑历史也不会丢失。
`set mouse=a`: 鼠标支持可以让你更方便地进行选择和导航。
`let mapleader = ''`: 定义 `Leader Key`,很多插件的快捷键都基于它。

第三步:安装和配置插件管理器

为了方便管理 Vim 插件,我们需要一个插件管理器。常用的有:

Vundle: 曾经非常流行,配置相对简单。
Pathogen: 轻量级,易于理解。
vimplug: 目前非常流行,速度快,功能强大。

这里我们以 `vimplug` 为例进行介绍,因为它功能全面且使用方便。

安装 vimplug

打开终端并执行以下命令:

```bash
curl fLo ~/.vim/autoload/plug.vim createdirs
https://raw.githubusercontent.com/junegunn/vimplug/master/plug.vim
```

配置 vimplug

在 `~/.vimrc` 文件顶部添加以下内容来声明 `vimplug` 的使用:

```vim
" 指定你的插件管理器
call plugbegin('~/.vim/plugged')

" 你的插件将在这里列出
" 例如:Plug '插件名称/插件仓库'

call plugend()
```

重要提示: 确保 `call plugbegin()` 和 `call plugend()` 之间没有其他内容。

第四步:安装 C/C++ 开发相关的核心插件

接下来,我们将安装一些对 C/C++ 开发至关重要的插件。在 `~/.vimrc` 文件中 `call plugbegin()` 和 `call plugend()` 之间添加插件的声明。

```vim
" 插件管理
call plugbegin('~/.vim/plugged')

" 核心插件
Plug 'preservim/nerdtree' " 文件浏览器
Plug 'scrooloose/nerdcommenter' " 代码注释管理
Plug 'davidhalley/tagbar' " 代码结构导航 (需要 ctags)
Plug 'junegunn/vimgitgutter' " Git 状态标记 (文件修改提示)
Plug 'sheerun/vimpolyglot' " 语言支持和语法高亮 (包含 C/C++)
" Plug 'tpope/vimfugitive' " Git 增强插件 (可选,但很好用)

" 代码补全 (可选,但强烈推荐)
" Plug 'prabirshrestha/vimlsp' " Language Server Protocol 客户端
" Plug 'prabirshrestha/asyncrun.vim' " 异步运行插件
" Plug 'prabirshrestha/vimcppcompletion' " C/C++ 代码补全 (配合 clangd)

" 更好的缩进和代码格式化 (可选)
" Plug 'Chouff/vimautoformat' " 自动格式化代码 (支持 clangformat, astyle 等)

" 主题 (可选)
" Plug 'morhetz/gruvbox' " 一款流行的 Vim 主题

call plugend()
```

安装插件

保存 `~/.vimrc` 文件后,进入 Vim,然后输入以下命令来安装所有声明的插件:

```vim
:PlugInstall
```

Vim 会自动下载并安装插件。

配置 NERDTree (文件浏览器)

NERDTree 是一个非常实用的文件浏览器,可以让你在 Vim 中方便地浏览项目文件。

在 `~/.vimrc` 中添加:

```vim
" NERDTree 配置
nnoremap :NERDTreeToggle " 使用 Ctrl+n 切换 NERDTree 的显示/隐藏
```

现在,你可以通过 `Ctrl+n` 来打开或关闭文件浏览器。

配置 NerdCommenter (代码注释)

NerdCommenter 让你能够轻松地注释和取消注释代码行。

```vim
" NerdCommenter 配置
" 设置默认的注释方式为 C/C++ 的行注释 //
let g:NERDCommenter_config = {
'comment_style': ' '//',
'left_margin_chars': '',
'right_margin_chars': '',
'trim_leading_whitespace': 0,
'follow_whitespace': 1,
'indent_style': 'auto',
'indent_width': 4,
'space_before_comment': 1,
'space_after_comment': 1,
'comment_line_ending': '',
'line_ending_replacement': ''
}

" 常用快捷键
" cc : 注释/取消注释当前行或选中文本
" c$ : 注释当前行到行尾
" ci : 块注释
" cA : 在行尾添加注释
" cm : 块注释
" cs : 替代注释方式 (例如切换到 / /)
```

你可以使用 `cc` (例如 `cc`) 来注释或取消注释代码行。

配置 Tagbar (代码结构导航)

Tagbar 可以让你快速查看当前文件的函数、变量、类等结构。

安装 `ctags`: Tagbar 需要 `ctags` 工具来生成标签。
Debian/Ubuntu: `sudo apt install exuberantctags`
Fedora/CentOS/RHEL: `sudo yum install ctags` (或 `dnf`)
Arch Linux: `sudo pacman S ctags`

配置 Tagbar:
```vim
" Tagbar 配置
" 使用 F4 键打开/关闭 Tagbar
nnoremap :TagbarToggle
```

在打开的 C/C++ 文件中,按下 `F4` 即可看到 Tagbar 窗口,显示代码结构。

配置 vimgitgutter (Git 状态标记)

vimgitgutter 会在行号旁边显示文件相对于 Git 版本库的修改状态(新增、修改、删除)。

这个插件通常无需太多额外配置,安装即可生效。

配置 vimpolyglot (语言支持和语法高亮)

`vimpolyglot` 集成了大量的语言支持和语法高亮规则,包括 C 和 C++。安装此插件后,C/C++ 的语法高亮会更加完善。

配置代码补全 (LSP 方案 推荐)

为了获得类似 IDE 的代码补全、跳转定义、错误检查等功能,我们需要集成 Language Server Protocol (LSP)。

1. 安装 LSP 服务器: 对于 C/C++,最常用的 LSP 服务器是 `clangd`。
Debian/Ubuntu: `sudo apt install clangd`
Fedora/CentOS/RHEL: `sudo yum install clangd` (或 `dnf`)
Arch Linux: `sudo pacman S clangd`

2. 配置 Vim 的 LSP 插件:
在 `~/.vimrc` 中添加 `vimlsp` 相关的插件(如果你之前没有添加的话):
```vim
" 确保这些插件已在 plugbegin() 和 plugend() 之间声明
" Plug 'prabirshrestha/vimlsp'
" Plug 'prabirshrestha/asyncrun.vim' " 依赖项
" Plug 'prabirshrestha/vimcppcompletion' " C/C++ 补全插件
```

然后进行配置:
```vim
" LSP 配置
if executable('clangd')
augroup lsp
autocmd!
" 仅在 C/C++ 文件中自动启动
autocmd BufRead,BufNewFile .c,.h,.cpp,.hpp,.cxx,.hxx,.cc,.hh,.c++ :LspInstallServer clangd
autocmd BufRead,BufNewFile .c,.h,.cpp,.hpp,.cxx,.hxx,.cc,.hh,.c++ :LspSetup
augroup END
endif

" 映射 LSP 的常用功能
" 自动补全列表
" inoremap pumvisible() ? "" : "" " Enter 键选择补全项
" inoremap pumvisible() ? "" : "" " Tab 键向下选择
" inoremap pumvisible() ? "" : "" " Shift+Tab 键向上选择

" 跳转定义 (Ctrl+])
nnoremap :LspDefinition
" 跳转到声明 (Ctrl+t)
nnoremap :LspDeclaration
" 查找引用
nnoremap r :LspReferences
" 重命名
nnoremap n :LspRename
" 显示诊断信息 (错误/警告)
nnoremap d :LspHover " 显示当前位置的悬停信息
nnoremap D :LspDocumentHighlight " 显示符号的使用情况
nnoremap e :LspDiagnostics " 显示所有诊断信息

" 格式化代码 (需要 clangformat 工具)
nnoremap f :LspDocumentFormat

" 回退到上一个跳转位置
nnoremap :LspOutline
```

重要提示:
你需要确保你的项目有 `compile_commands.json` 文件,`clangd` 会读取这个文件来理解你的项目结构和编译选项。通常情况下,使用 `CMake` 构建的项目会自动生成 `compile_commands.json`。如果你没有 CMake,可以考虑使用 `bear` 工具来生成。
如果你使用 `vimcppcompletion`,它提供了 C/C++ 相关的补全。

第五步:构建和调试 C/C++ 项目

Vim 可以与 `make` 和 `gdb` 等工具集成,方便进行项目的构建和调试。

使用 Make 构建项目

假设你有一个 `Makefile`:

```makefile
CC = gcc
CXX = g++
CFLAGS = Wall Wextra g
CXXFLAGS = Wall Wextra g

TARGET = myprogram
SRCS = main.c utils.c
OBJS = $(SRCS:.c=.o)

all: $(TARGET)

$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $^ o $@

%.o: %.c
$(CC) $(CFLAGS) c $< o $@

clean:
rm f $(OBJS) $(TARGET)

.PHONY: all clean
```

你可以在 Vim 中执行 Make 命令:

```vim
" 在 Vim 的命令行模式下
:!make " 构建项目
:!make clean " 清理项目
```

更方便的方式是使用 `:make` 命令(需要 `makeprg` 配置):

```vim
" 配置 make 程序的路径
set makeprg=make

" 使用 :make 来编译
nnoremap b :make
```

执行 `:make` 后,如果编译出错,Vim 会将错误信息显示在底部,你可以通过 `:copen` 打开快速修复列表,方便跳转到错误行。

集成 GDB 调试

Vim 可以通过 `vimgdb` 或 `gdb` 插件集成 `gdb`。这里我们使用 `vimgdb` 的内置功能。

1. 编译时开启调试信息: 确保你的编译命令带有 `g` 选项,这会生成调试信息。

2. 启动 GDB:
在 Vim 中,输入:
```vim
:debug main
```
或者,如果你已经编译了可执行文件 `myprogram`:
```vim
:!gdb myprogram
```
然后,在 GDB 提示符下输入 `run` 或 `r` 来运行程序。

3. 使用 GDB 插件(更推荐):
安装 `vimdebugger` 或 `vimgdbdebugger` 等插件。这里我们以 `vimdebugger` 为例。
```vim
" Plug 'vit964/vimdebugger'
```
`vimdebugger` 支持多种调试器,包括 GDB。
配置快捷键来启动和控制调试器:
```vim
" GDB 调试配置 (使用 vimdebugger)
" 启用调试器
let g:debugger_client = 'gdb'

" 启动调试器并加载可执行文件
nnoremap dp :DebuggerStart " 例如::DebuggerStart ./myprogram

" 设置断点
nnoremap bp :DebuggerBreakpoint

" 运行/继续
nnoremap c :DebuggerContinue

" 单步步过
nnoremap n :DebuggerNext

" 单步步入
nnoremap i :DebuggerStep

" 单步步出
nnoremap o :DebuggerFinish

" 查看变量值
nnoremap p :DebuggerPrint

" 跳转到当前执行位置
nnoremap l :DebuggerCurrentLine

" 退出调试器
nnoremap q :DebuggerQuit
```

使用这些快捷键,你就可以在 Vim 内部完成大部分调试操作。

第六步:优化和个性化配置

选择一个好的颜色主题: 一个舒适的颜色主题可以显著提高编码体验。可以尝试 `gruvbox`, `solarized`, `dracula` 等。
```vim
" 例如:gruvbox
" Plug 'morhetz/gruvbox'
colorscheme gruvbox
```

配置状态栏: 可以使用 `vimairline` 或 `lightline.vim` 来增强 Vim 的状态栏,显示更多信息。
```vim
" Plug 'vimairline/vimairline'
" Plug 'itchyny/lightline.vim'
```
安装后,通常无需太多配置即可工作。

快捷键定制: 根据自己的习惯调整快捷键。

文件模板: 创建代码模板,例如 C++ 源文件和头文件的基本结构。

总结和工作流程

一个典型的 C/C++ 开发流程可能如下:

1. 打开项目: 使用 `Ctrl+n` 打开 NERDTree,浏览项目文件。
2. 编写代码: 在 Vim 中打开源文件,利用语法高亮、代码补全 (LSP)、自动缩进等功能进行编写。
3. 查看代码结构: 使用 `F4` (Tagbar) 查看函数、变量等。
4. 注释代码: 使用 `cc` (NerdCommenter) 快速注释。
5. 检查 Git 状态: `vimgitgutter` 会显示修改状态。
6. 构建项目: 使用 `:make` 或 `:!make` 来编译。
7. 调试: 使用 `:DebuggerStart ./your_program` 启动 GDB,然后利用快捷键进行单步调试、设置断点、查看变量等。
8. 格式化代码: 使用 `f` (LSP 的 `DocumentFormat`) 来统一代码风格。

重要提示:

学习 Vim 的基础操作: 在安装插件之前,务必熟悉 Vim 的基本操作,如模式切换、移动、编辑、保存、退出等。
阅读插件文档: 每个插件都有其详细的配置选项和使用方法,遇到问题时查阅官方文档是最好的方式。
迭代配置: 搭建开发环境是一个持续优化的过程。根据你的实际需求,不断调整和添加插件。

通过以上步骤,你可以在 Linux 下利用 Vim 搭建一个功能强大、高效且高度个性化的 C/C++ 开发环境。祝你编码愉快!

网友意见

user avatar

2018年了,不要再看网上那些老旧的文章还在教你使用手工生成 tags 的,请使用自动代码索引生成工具,比如 vim-gutentags,现在网上好像就没有一篇能正确讨论 Vim C/C++ 环境搭建的,都在谈些十年前的东西,所以我写了篇关于 Vim 8 和 C/C++ 相关插件的介绍:

假设你已经有一定 Vim 使用经验,并且折腾过 Vim 配置,能够相对舒适的在 Vim 中编写其他代码的时候,准备在 Vim 开始 C/C++ 项目开发,或者你已经用 Vim 编写了几年 C/C++ 代码,想要更进一步,让自己的工作更加顺畅的话,本文就是为你准备的:


插件管理

为什么把插件管理放在第一个来讲呢?这是比较基本的一个东西,如今 Vim 下熟练开发的人,基本上手都有 20-50 个插件,遥想十年前,Vim里常用的插件一只手都数得过来。过去我一直使用老牌的 Vundle 来管理插件,但是随着插件越来越多,更新越来越频繁,Vundle 这种每次更新就要好几分钟的东西实在是不堪重负了。

在我逐步对 Vundle 失去耐心之后,我试用了 vim-plug ,用了两天以后就再也回不去 Vundle了,它支持全异步的插件安装,安装50个插件只需要一分钟不到的时间,这在 Vundle 下面根本不可想像的事情,插件更新也很快,不像原来每次更新都可以去喝杯茶去,最重要的是它支持插件延迟加载:

       " 定义插件,默认用法,和 Vundle 的语法差不多 Plug 'junegunn/vim-easy-align' Plug 'skywind3000/quickmenu.vim'  " 延迟按需加载,使用到命令的时候再加载或者打开对应文件类型才加载 Plug 'scrooloose/nerdtree', { 'on':  'NERDTreeToggle' } Plug 'tpope/vim-fireplace', { 'for': 'clojure' }  " 确定插件仓库中的分支或者 tag Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }     

定义好插件以后一个 :PlugInstall 命令就并行安装所有插件了,比 Vundle 快捷不少,关键是 vim-plug 只有单个文件,正好可以放在我 github 上的 vim 配置仓库中,每次需要更新 vim-plug 时只需要 :PlugUpgrade,即可自我更新。使用时建议给插件分组,同类别插件放一个组里,vimrc 里面只需要确定下启用哪些组就行了。

抛弃 Vundle 切换到 vim-plug 以后,不仅插件安装和更新快了一个数量级,大量的插件我都配置成了延迟加载,Vim 启动速度比 Vundle 时候提高了不少。使用 Vundle 的时候一旦插件数量超过30个,管理是一件很痛苦的事情,而用了 vim-plug 以后,50-60个插件都轻轻松松。


符号索引

现在有好多 ctags 的代替品,比如 gtags, etags 和 cquery。然而我并不排斥 ctags,因为他支持 50+ 种语言,没有任何一个符号索引工具有它支持的语言多。同时 Vim 和 ctags 集成的相当好,用它依赖最少,大量基础工作可以直接通过 ctags 进行,然而到现在为止,我就没见过几个人把 ctags 用对了的。

就连配置文件他们都没写对,正确的 ctags 配置应该是:

       set tags=./.tags;,.tags     

这里解释一下,首先我把 tag 文件的名字从“tags” 换成了 “.tags”,前面多加了一个点,这样即便放到项目中也不容易污染当前项目的文件,删除时也好删除,gitignore 也好写,默认忽略点开头的文件名即可。

前半部分 “./.tags; ”代表在文件的所在目录下(不是 “:pwd”返回的 Vim 当前目录)查找名字为 “.tags”的符号文件,后面一个分号代表查找不到的话向上递归到父目录,直到找到 .tags 文件或者递归到了根目录还没找到,这样对于复杂工程很友好,源代码都是分布在不同子目录中,而只需要在项目顶层目录放一个 .tags文件即可;逗号分隔的后半部分 .tags 是指同时在 Vim 的当前目录(“:pwd”命令返回的目录,可以用 :cd ..命令改变)下面查找 .tags 文件。

最后请更新你的 ctags,不要再使用老旧的 Exuberant Ctags,这货停止更新快十年了,请使用最新的 Universal CTags 代替之,它在 Exuberant Ctags 的基础上继续更新迭代了近十年,如今任然活跃的维护着,功能更强大,语言支持更多。

(注意最新版 universal ctags 调用时需要加一个 --output-format=e-ctags,输出格式才和老的 exuberant ctags 兼容否则会有 windows 下路径名等小问题)。


自动索引

过去写几行代码又需要运行一下 ctags 来生成索引,每次生成耗费不少时间。如今 Vim 8 下面自动异步生成 tags 的工具有很多,这里推荐最好的一个:vim-gutentags,这个插件主要做两件事情:

- 确定文件所属的工程目录,即文件当前路径向上递归查找是否有 `.git`, `.svn`, `.project` 等标志性文件(可以自定义)来确定当前文档所属的工程目录。

- 检测同一个工程下面的文件改动,能会自动增量更新对应工程的 `.tags` 文件。每次改了几行不用全部重新生成,并且这个增量更新能够保证 `.tags` 文件的符号排序,方便 Vim 中用二分查找快速搜索符号。

vim-gutentags 需要简单配置一下:

       " gutentags 搜索工程目录的标志,碰到这些文件/目录名就停止向上一级目录递归 let g:gutentags_project_root = ['.root', '.svn', '.git', '.hg', '.project']  " 所生成的数据文件的名称 let g:gutentags_ctags_tagfile = '.tags'  " 将自动生成的 tags 文件全部放入 ~/.cache/tags 目录中,避免污染工程目录 let s:vim_tags = expand('~/.cache/tags') let g:gutentags_cache_dir = s:vim_tags  " 配置 ctags 的参数 let g:gutentags_ctags_extra_args = ['--fields=+niazS', '--extra=+q'] let g:gutentags_ctags_extra_args += ['--c++-kinds=+px'] let g:gutentags_ctags_extra_args += ['--c-kinds=+px']  " 检测 ~/.cache/tags 不存在就新建 if !isdirectory(s:vim_tags)    silent! call mkdir(s:vim_tags, 'p') endif     

有了上面的设置,你平时基本感觉不到 tags 文件的生成过程了,只要文件修改过,gutentags 都在后台为你默默打点是否需要更新数据文件,你根本不用管,还会帮你:setlocal tags+=... 添加到局部 tags 搜索列表中。

为当前文件添加上对应的 tags 文件的路劲而不影响其他文件。得益于 Vim 8 的异步机制,你可以任意随时使用 ctags 相关功能,并且数据库都是最新的。需要注意的是,gutentags 需要靠上面定义的 project_root 里的标志,判断文件所在的工程,如果一个文件没有托管在 .git/.svn 中,gutentags 找不到工程目录的话,就不会为该野文件生成 tags,这也很合理。想要避免的话,你可以在你的野文件目录中放一个名字为 .root 的空白文件,主动告诉 gutentags 这里就是工程目录。

最后啰嗦两句,少用 CTRL-] 直接在当前窗口里跳转到定义,多使用 CTRL-W ] 用新窗口打开并查看光标下符号的定义,或者 CTRL-W } 使用 preview 窗口预览光标下符号的定义。

我自己还写过不少关于 ctags 的 vimscript,例如在最下面命令行显示函数的原型而不用急着跳转,或者重复按 `ALT+;` 在 preview 窗口中轮流查看多个定义,不切走当前窗口,不会出一个很长的列表让你选择,有兴趣可以刨我的 vim dotfiles


编译运行

再 Vim 8 以前,编译和运行程序要么就让 vim 傻等着结束,不能做其他事情,要么切到一个新的终端下面去单独运行编译命令和执行命令,要么开个 tmux 左右切换。如今新版本的异步模式可以让这个流程更加简化,这里我们使用 AsyncRun 插件,简单设置下:

       Plug 'skywind3000/asyncrun.vim'  " 自动打开 quickfix window ,高度为 6 let g:asyncrun_open = 6  " 任务结束时候响铃提醒 let g:asyncrun_bell = 1  " 设置 F10 打开/关闭 Quickfix 窗口 nnoremap <F10> :call asyncrun#quickfix_toggle(6)<cr>     

该插件可以在后台运行 shell 命令,并且把结果输出到 quickfix 窗口:

最简单的编译单个文件,和 sublime 的默认 build system 差不多,我们定义 F9 为编译单文件:

       nnoremap <silent> <F9> :AsyncRun gcc -Wall -O2 "$(VIM_FILEPATH)" -o "$(VIM_FILEDIR)/$(VIM_FILENOEXT)" <cr>     

其中 $(...) 形式的宏在执行时会被替换成实际的文件名或者文件目录,这样按 F9 就可以编译当前文件,同时按 F5 运行:

       nnoremap <silent> <F5> :AsyncRun -raw -cwd=$(VIM_FILEDIR) "$(VIM_FILEDIR)/$(VIM_FILENOEXT)" <cr>     

用双引号引起来避免文件名包含空格,“-cwd=$(VIM_FILEDIR)” 的意思时在文件文件的所在目录运行可执行,后面可执行使用了全路径,避免 linux 下面当前路径加 “./” 而 windows 不需要的跨平台问题。

参数 `-raw` 表示输出不用匹配错误检测模板 (errorformat) ,直接原始内容输出到 quickfix 窗口。这样你可以一边编辑一边 F9 编译,出错了可以在 quickfix 窗口中按回车直接跳转到错误的位置,编译正确就接着执行。

接下来是项目的编译,不管你直接使用 make 还是 cmake,都是对一群文件做点什么,都需要定位到文件所属项目的目录,AsyncRun 识别当前文件的项目目录方式和 gutentags相同,从文件所在目录向上递归,直到找到名为 “.git”, “.svn”, “.hg”或者 “.root”文件或者目录,如果递归到根目录还没找到,那么文件所在目录就被当作项目目录,你重新定义项目标志:

       let g:asyncrun_rootmarks = ['.svn', '.git', '.root', '_darcs', 'build.xml']      

然后在 AsyncRun 命令行中,用 “<root>” 或者 “$(VIM_ROOT)”来表示项目所在路径,于是我们可以定义按 F7 编译整个项目:

       nnoremap <silent> <F7> :AsyncRun -cwd=<root> make <cr>     

那么如果你有一个项目不在 svn 也不在 git 中怎么查找 <root> 呢?很简单,放一个空的 .root 文件到你的项目目录下就行了,前面配置过,识别名为 .root 的文件。

继续配置用 F8 运行当前项目:

       nnoremap <silent> <F8> :AsyncRun -cwd=<root> -raw make run <cr>     

当然,你的 makefile 中需要定义怎么 run ,接着按 F6 执行测试:

       nnoremap <silent> <F6> :AsyncRun -cwd=<root> -raw make test <cr>     

如果你使用了 cmake 的话,还可以照葫芦画瓢,定义 F4 为更新 Makefile 文件,如果不用 cmake 可以忽略:

       nnoremap <silent> <F4> :AsyncRun -cwd=<root> cmake . <cr>     

由于 C/C++ 标准库的实现方式是发现在后台运行时会缓存标准输出直到程序退出,你想实时看到 printf 输出的话需要 fflush(stdout) 一下,或者程序开头关闭缓存:“setbuf(stdout, NULL);” 即可。

同时,如果你开发 C++ 程序使用 std::cout 的话,后面直接加一个 std::endl 就强制刷新缓存了,不需要弄其他。而如果你在 Windows 下使用 GVim 的话,可以弹出新的 cmd.exe 窗口来运行刚才的程序:

       nnoremap <silent> <F5> :AsyncRun -cwd=$(VIM_FILEDIR) -mode=4 "$(VIM_FILEDIR)/$(VIM_FILENOEXT)" <cr> nnoremap <silent> <F8> :AsyncRun -cwd=<root> -mode=4 make run <cr>     

在 Windows 下使用 -mode=4 选项可以跟 Visual Studio 执行命令行工具一样,弹出一个新的 cmd.exe窗口来运行程序或者项目,于是我们有了下面的快捷键:

  • F4:使用 cmake 生成 Makefile
  • F5:单文件:运行
  • F6:项目:测试
  • F7:项目:编译
  • F8:项目:运行
  • F9:单文件:编译
  • F10:打开/关闭底部的 quickfix 窗口

恩,编译和运行基本和 NotePad++ / GEdit 的体验差不多了。如果你重度使用 cmake 的话,你还可以写点小脚本,将 F4 和 F7 的功能合并,检测 CMakeLists.txt 文件改变的话先执行 cmake 更新一下 Makefile,然后再执行 make,否则直接执行 make,这样更自动化些。


动态检查

代码检查是个好东西,让你在编辑文字的同时就帮你把潜在错误标注出来,不用等到编译或者运行了才发现。我很奇怪 2018 年了,为啥网上还在到处介绍老旧的 syntastic,但凡见到介绍这个插件的文章基本都可以不看了。老的 syntastic 基本没法用,不能实时检查,一保存文件就运行检查器并且等待半天,所以请用实时 linting 工具 ALE

大概长这个样子,随着你不断的编辑新代码,有语法错误的地方会实时帮你标注出来,侧边会标注本行有错,光标移动过去的时候下面会显示错误原因,而具体错误的符号下面会有红色波浪线提醒。Ale 支持多种语言的各种代码分析器,就 C/C++ 而言,就支持:gcc, clang, cppcheck 以及 clang-format 等,需要另行安装并放入 PATH下面,ALE能在你修改了文本后自动调用这些 linter 来分析最新代码,然后将各种 linter 的结果进行汇总并显示再界面上。

同样,我们也需要简单配置一下:

       let g:ale_linters_explicit = 1 let g:ale_completion_delay = 500 let g:ale_echo_delay = 20 let g:ale_lint_delay = 500 let g:ale_echo_msg_format = '[%linter%] %code: %%s' let g:ale_lint_on_text_changed = 'normal' let g:ale_lint_on_insert_leave = 1 let g:airline#extensions#ale#enabled = 1  let g:ale_c_gcc_options = '-Wall -O2 -std=c99' let g:ale_cpp_gcc_options = '-Wall -O2 -std=c++14' let g:ale_c_cppcheck_options = '' let g:ale_cpp_cppcheck_options = ''     

基本上就是定义了一下运行规则,信息显示格式以及几个 linter 的运行参数,其中 6,7 两行比较重要,它规定了如果 normal 模式下文字改变以及离开 insert 模式的时候运行 linter,这是相对保守的做法,如果没有的话,会导致 YouCompleteMe 的补全对话框频繁刷新。

记得设置一下各个 linter 的参数,忽略一些你觉得没问题的规则,不然错误太多没法看。默认错误和警告的风格都太难看了,你需要修改一下,比如我使用 GVim,就重新定义了警告和错误的样式,去除默认难看的红色背景,代码正文使用干净的波浪下划线表示:

       let g:ale_sign_error = "ue009ue009" hi! clear SpellBad hi! clear SpellCap hi! clear SpellRare hi! SpellBad gui=undercurl guisp=red hi! SpellCap gui=undercurl guisp=blue hi! SpellRare gui=undercurl guisp=magenta     

不同项目之间如果评测标准不一样还可以具体单独制定 linter 的参数,具体见 ALE 帮助文档了。我基本使用两个检查器:gcc 和 cppcheck,都可以在 ALE 中进行详细配置,前者主要检查有无语法错误,后者主要会给出一些编码建议,和对危险写法的警告。

我之前用 syntastic 时就用了两天就彻底删除了,而开始用 ALE 后,一用上就停不下来,头两天我还一度觉得它就是个可有可无的点缀,但是第三天它帮我找出两个潜在的 bug 的时候,我开始觉得没白安装,比如:

即便你使用各类 C/C++ IDE,也只能给实时你标注一些编译错误或者警告,而使用 ALE + cppcheck/gcc,连上面类似的潜在错误都能帮你自动找出来,并且当你光标移动过去时在最下面命令行提示你错误原因。

用上一段时间以后,让你写 C/C++ 有一种安心和舒适的感觉。


修改比较

这是个小功能,在侧边栏显示一个修改状态,对比当前文本和 git/svn 仓库里的版本,在侧边栏显示修改情况,以前 Vim 做不到实时显示修改状态,如今推荐使用 vim-signify 来实时显示修改状态,它比 gitgutter 强,除了 git 外还支持 svn/mercurial/cvs 等十多种主流版本管理系统。

没注意到它时,你可能觉得它不存在,当你有时真的看上两眼时,你会发现这个功能很贴心。最新版 signify 还有一个命令`:SignifyDiff`,可以左右分屏对比提交前后记录,比你命令行 svn/git diff 半天直观多了。并且对我这种同时工作在 subversion 和 git 环境下的情况契合的比较好。

Signify 和前面的 ALE 都会在侧边栏显示一些标记,默认侧边栏会自动隐藏,有内容才会显示,不喜欢侧边栏时有时无的行为可设置强制显示侧边栏:“set signcolumn=yes” 。


文本对象

相信大家用 Vim 进行编辑时都很喜欢文本对象这个概念,diw 删除光标所在单词,ciw 改写单词,vip 选中段落等,ci"/ci( 改写引号/括号中的内容。而编写 C/C++ 代码时我推荐大家补充几个十分有用的文本对象,可以使用 textobj-user 全家桶:

       Plug 'kana/vim-textobj-user' Plug 'kana/vim-textobj-indent' Plug 'kana/vim-textobj-syntax' Plug 'kana/vim-textobj-function', { 'for':['c', 'cpp', 'vim', 'java'] } Plug 'sgur/vim-textobj-parameter'     

它新定义的文本对象主要有:

  • i, 和 a, :参数对象,写代码一半在修改,现在可以用 di, 或 ci, 一次性删除/改写当前参数
  • ii 和 ai :缩进对象,同一个缩进层次的代码,可以用 vii 选中,dii / cii 删除或改写
  • if 和 af :函数对象,可以用 vif / dif / cif 来选中/删除/改写函数的内容

最开始我不太想用额外的文本对象,一直在坚持 Vim 固有的几个默认对象,生怕手练习惯了肌肉形成记忆到远端没有环境的 vim 下形成依赖改不过来,后来我慢慢发现挺有用的,比如改写参数,以前是比较麻烦的事情,这下流畅了很多,当我发现自己编码效率得到比较大的提升时,才发现习惯依赖不重要,行云流水才是真重要。以前看到过无数次都选择性忽略的东西,有时候试试可能会有新的发现。


编辑辅助

大家都知道 color 文件定义了众多不同语法元素的色彩,还有一个关键因素就是语法文件本身能否识别并标记得出众多不同的内容来?语法文件对某些东西没标注,你 color 文件确定了颜色也没用。因此 Vim 下面写 C/C++ 代码,语法高亮准确丰富的话能让你编码的心情好很多,这里推荐 vim-cpp-enhanced-highlight 插件,提供比 Vim 自带语法文件更好的 C/C++ 语法标注,支持 标准 11/14/17。

前面编译运行时需要频繁的操作 quickfix 窗口,ale查错时也需要快速再错误间跳转(location list),就连文件比较也会用到快速跳转到上/下一个差异处,unimpaired 插件帮你定义了一系列方括号开头的快捷键,被称为官方 Vim 中丢失的快捷键。

我们好些地方用到了 quickfix / location 窗口,你在 quickfix 中回车选中一条错误的话,默认会把你当前窗口给切走,变成新文件,虽然按 CTRL+O 可以返回,但是如果不太喜欢这样切走当前文件的做法,可以设置 switchbuf,发现文件已在 Vim 中打开就跳过去,没打开过就新建窗口/标签打开,具体见帮助。

Vim最爽的地方是把所有 ALT 键映射全部留给用户了,尽量使用 Vim 的 ALT键映射,可以让冗长的快捷键缩短很多,请参考:《Vim和终端软件中支持ALT映射》。


代码补全

传统的 Vim 代码补全基本以 omni 系列补全和符号补全为主,omni 补全系统是 Vim 自带的针对不同文件类型编写不同的补全函数的基础语义补全系统,搭配 neocomplete 可以很方便的对所有补全结果(omni补全/符号补全/字典补全)进行一个合成并且自动弹出补全框,虽然赶不上 IDE 的补全,但是已经比大部分编辑器补全好用很多了。然而传统 Vim 补全还是有两个迈不过去的坎:语义补全太弱,其次是补全分析无法再后台运行,对大项目而言,某些复杂符号的补全会拖慢你的打字速度。

新一代的 Vim 补全系统,YouCompleteMeDeoplete,都支持异步补全和基于 clang 的语义补全,前者集成度高,后者扩展方便。对于 C/C++ 的话,我推荐 YCM,因为 deoplete 的 clang 补全插件不够稳定,太吃内存,并且反应比较慢,它的代码量和代码质量和 YCM完全不是一个量级的。所以 C/C++ 的补全的话,请直接使用 YCM,没有之一,而使用 YCM的话,需要进行一些简单的调教:

       let g:ycm_add_preview_to_completeopt = 0 let g:ycm_show_diagnostics_ui = 0 let g:ycm_server_log_level = 'info' let g:ycm_min_num_identifier_candidate_chars = 2 let g:ycm_collect_identifiers_from_comments_and_strings = 1 let g:ycm_complete_in_strings=1 let g:ycm_key_invoke_completion = '<c-z>' set completeopt=menu,menuone  noremap <c-z> <NOP>  let g:ycm_semantic_triggers =  {             'c,cpp,python,java,go,erlang,perl': ['re!w{2}'],             'cs,lua,javascript': ['re!w{2}'],             }     

这样可以输入两个字符就自动弹出语义补全,不用等到输入句号 . 或者 -> 才触发,同时关闭了预览窗口和代码诊断这些 YCM 花边功能,保持清静,对于原型预览和诊断我们后面有更好的解决方法,YCM这两项功能干扰太大。

上面这几行配置具体每行的含义,可以见:《YouCompleteMe 中容易忽略的配置》。另外我在 Windows 下编译了一个版本,你用 Windows 的话无需下载VS编译,点击 [这里]。我日常开发使用 YCM 辅助编写 C/C++, Python 和 Go 代码,基本能提供 IDE 级别的补全。


函数列表

不再建议使用 tagbar, 它会在你保存文件的时候以同步等待的方式运行 ctags (即便你没有打开 tagbar),导致vim操作变卡,特别是 windows下开了反病毒软件扫描的话,有时候保存文件卡5-6秒。2018年了,我们有更好的选择,比如使用 @Yggdroot 开发的 LeaderF 来显示函数列表:

全异步显示文件函数列表,不用的时候不会占用你任何屏幕空间,将 ALT+P 绑定到 `:LeaderfFunction!` 这个命令上,按 ALT+P 就弹出当前文件的函数列表,然后可以进行模糊匹配搜索,除了上下键移动选择外,各种vim的跳转和搜索命令都可以始用,回车跳转然后关闭函数列表,除此之外按 i 进入模糊匹配,按TAB切换回列表选择。

Leaderf 的函数功能属于你想要它的时候它才会出来,不想要它的时候不会给你捣乱。


文件切换

文件/buffer模糊匹配快速切换的方式,比你打开一个对话框选择文件便捷不少,过去我们常用的 CtrlP 可以光荣下岗了,如今有更多速度更快,匹配更精准以及完美支持后台运行方式的文件模糊匹配工具。我自己用的是上面提到的 LeaderF,除了提供函数列表外,还支持文件,MRU,Buffer名称搜索,完美代替 CtrlP,使用时需要简单调教下:

       let g:Lf_ShortcutF = '<c-p>' let g:Lf_ShortcutB = '<m-n>' noremap <c-n> :LeaderfMru<cr> noremap <m-p> :LeaderfFunction!<cr> noremap <m-n> :LeaderfBuffer<cr> noremap <m-m> :LeaderfTag<cr> let g:Lf_StlSeparator = { 'left': '', 'right': '', 'font': '' }  let g:Lf_RootMarkers = ['.project', '.root', '.svn', '.git'] let g:Lf_WorkingDirectoryMode = 'Ac' let g:Lf_WindowHeight = 0.30 let g:Lf_CacheDirectory = expand('~/.vim/cache') let g:Lf_ShowRelativePath = 0 let g:Lf_HideHelp = 1 let g:Lf_StlColorscheme = 'powerline' let g:Lf_PreviewResult = {'Function':0, 'BufTag':0}     

这里定义了 CTRL+P 在当前项目目录打开文件搜索,CTRL+N 打开 MRU搜索,搜索你最近打开的文件,这两项是我用的最频繁的功能。接着 ALT+P 打开函数搜索,ALT+N 打开 Buffer 搜索:

LeaderF 是目前匹配效率最高的,高过 CtrlP/Fzf 不少,敲更少的字母就能把文件找出来,同时搜索很迅速,使用 Python 后台线程进行搜索匹配,还有一个 C模块可以加速匹配性能,需要手工编译下。LeaderF在模糊匹配模式下按 TAB 可以切换到匹配结果窗口用光标或者 Vim 搜索命令进一步筛选,这是 CtrlP/Fzf 不具备的,更多方便的功能见它的官方文档。

文件/MRU 模糊匹配对于熟悉的项目效率是最高的,但对于一个新的项目,通常我们都不知道它有些什么文件,那就谈不上根据文件名匹配什么了,我们需要文件浏览功能。如果你喜欢把 Vim 伪装成 NotePad++ 之类的,那你该继续使用 NERDTree 进行文件浏览,但你想按照 Vim 的方式来,推荐阅读这篇文章:

Oil and vinegar - split windows and project drawer

然后像我一样开始使用 vim-dirvish,进行一些配置,比如当前文档按“-”号就能不切窗口的情况下在当前窗口直接返回当前文档所在的目录,再按一次减号就返回上一级目录,按回车进入下一级目录或者再当前窗口打开光标下的文件。进一步映射 “<tab>7” , “<tab>8” 和 “<tab>9” 分别用于在新的 split, vsplit 和新标签打开当前文件所在目录,这样从一个文件如手,很容易找到和该文件相关的其他项目文件。

最后一个是 C/C++ 的头文件/源文件快速切换功能,有现成的插件做这事情,比如 a.vim,我自己没用,因为这事情太简单,再我发现 a.vim 前我就觉得需要这个功能,然后自己两行 vim 脚本就搞定了。


参数提示

这个功能应人而异,有人觉得不需要,有人觉得管用。写 C/C++ 时函数忘了可以用上面的 YCM 补全,但很多时候是参数忘记了怎么办?YCM的参数提示很蛋疼,要打开个 Preview 窗口,实在是太影响我的视线了,我自己写过一些参数提醒功能,可以在最下面的命令行显示当前函数的参数,不过这是基于 tags 的,搭配前面的 gutentags,对其他语言很管用,但对 C/C++ 我们还可以使用更好的 echodoc 插件:

它可以无缝的和前面的 YCM 搭配,用 libclang 给你生成参数提示,当你用 YCM 的 tab 补全了一个函数名后,只要输入左括号,下面命令行就会里面显示出该函数的参数信息,随着光标移动,下面还会高亮出来你正在处于哪个参数位置。

唯一需要设置的是使用 “set noshowmode”关闭模式提示,就是底部 ---INSERT--- 那个,我们一般都用 airline / lightline 之类的显示当前模式了,所以默认模式提示可以关闭,INSERT 模式下的命令行,完全留给 echodoc 显示参数使用。


-------------

2018年了,用点新方法,网上那些 Vim 开发 C/C++ 的文章真的都可以淘汰了。

更多参考:

本文主要是针对 C/C++ 环境搭建的插件介绍,关于基本使用,欢迎参考上面这些链接。篇幅有限,这里只能谈一部分内容,对大多数人已经够了,如果你想继续深入的话,同国内 Vim 大咖们交流的话,

欢迎进入 tg 群:t.me/vimzh

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有