所需即所获:IDE = _plugins_ + vim

从零搭建专属Vim IDE:高效开发环境定制指南 集成开发环境(IDE)是现代软件开发的效率核心,它通过整合代码编辑、调试、版本控制等工具,为开发者提供一站式解决方案。其底层原理在于通过插件化架构和语言服务器协议(LSP),实现对不同编程语言的智能支持。这种定制化环境的价值在于能精准匹配个人工作流,显著提升编码效率与专注度,尤其适用于追求极致控制力的开发者。在众多定制方案中,基于Vim构建IDE因其强大的键盘驱动模式和高度可扩展性而备受青睐。通过配置插件管理器和模块化功能,如使用vim-plug管理插件、coc.nvim实现智能补全、ALE进行语法检查,开 阅读详情
新版迁移:https://github.com/yangyangwithgnu/use_vim_as_ide

http://yangyangwithgnu.github.io/







0 vim 必知会
    0.1 .vimrc 文件
    0.2 .vim/ 目录
1 源码安装编辑器 vim
2 插件管理
3 界面美化
    3.1 主题风格
    3.2 营造专注氛围
    3.3 添加辅助信息
    3.4 其他
4 代码分析
    4.1 语法高亮
    4.2 代码缩进
    4.3 代码折叠
    4.4 接口与实现快速切换
    4.5 代码收藏
    4.6 代码导航
    4.7 标签列表
5 代码开发
    5.1 快速开关注释
    5.2 模板补全
    5.3 快速输入结对符
    5.4 智能补全
        基于标签的智能补全
        基于语义的智能补全
    5.5 由接口快速生成实现框架
    5.6 库信息参考
6 工程管理
    6.1 工程文件浏览
    6.2 多文档编辑
    6.3 环境恢复
7 工具链集成
    7.1 编译器/构建工具集成
        代码编译
        系统构建
        一键编译
    7.2 静态分析器集成
8 其他辅助
    8.1 内容查找
    8.2 内容替换
    8.3 快速移动
9 尾声


开始前,我假设你:0)具备基本的 vim 操作能力,清楚如何打开/编辑/保存文档、命令与插入模式间切换;1)希望将 vim 打造成 C/C++ 语言的 IDE,而非其他语言。
关于 vim 的优点,你在网上能查到 128+ 项,对我而言,只有两项:0)所想即所得,让手输入的速度跟上大脑思考的速度,1)所需即所获,只有你想不到的功能、没有实现不了的插件。希望获得前者的能力,你需要两本教程深入学习,《Practical Vim: Edit Text at the Speed of Thought》和《vim user manual》;要想拥有后者的能力,通读本文 -。-#。对于 vim 的喜爱,献上湿哥哥以表景仰之情:
vi 之大道如我心之禅,
  vi 之漫路即为禅修,  
  vi 之命令禅印于心,  
 未得此道者视之怪诞,
 与之为伴者洞其真谛,
 长修此道者巨变人生。
作: reddy@lion.austin.com
译:yangyang.gnu@gmail.com
言归正传,说说 vim 用于代码编写提供了哪些直接和间接功能支撑。vim 用户手册中,50% 的例子都是在讲 vim 如何高效编写代码,由此可见,vim 是一款面向于程序员的编辑器,即使某些功能 vim 无法直接完成,借助其丰富的插件资源,必定可以达成目标,这就是所需即所获。
我是个目标驱动的信奉者,本文内容,我会先给出优秀 C/C++ IDE 应具备哪些功能,再去探索如何通过 vim 的操作或插件来达到目标。最终至少要像这个样子:
(图形环境下IDE总揽)
(纯字符模式下IDE总揽)

0 vim 必知会
在正式开始前先介绍几个 vim 的必知会,这不是关于如何使用而是如何配置 vim 的要点,这对理解后续相关配置非常有帮助。

0.1 .vimrc 文件
.vimrc 是控制 vim 行为的配置文件,位于 ~/.vimrc,不论 vim 窗口外观、显示字体,还是操作方式、快捷键、插件属性均可通过编辑该配置文件将 vim 调教成最适合你的编辑器。
很多人之所以觉得 vim 难用,是因为 vim 缺少默认设置,甚至安装完后你连配置文件自身都找不到,不进行任何配置的 vim 的确难看、难用。不论用于代码还是普通文本编辑,有必要将如下基本配置加入 .vimrc 中:
    前缀键。vim 插件帮助文档中经常出现 <leader>,即,前缀键。vim 中很多快捷键,再加上各类插件的快捷键,在单层空间中难免引起冲突,为缓解该问题,引入了前缀键 <leader>,这样,键 r 可以配置成r、<leader>r、<leader><leader>r 等等多个快捷键。前缀键是 vim 使用率较高的一个键(最高的当属 Esc),选一个最方便输入的键作为前缀键,将有助于提高编辑效率。找个无须眼睛查找、无须移动手指的键 —— 分号键,挺方便的,就在你右手小指处:
" 定义快捷键的前缀,即<Leader>
let mapleader=";"
既然前缀键是为快捷键服务的,那随便说下快捷键设定原则:不同快捷键尽量不要有同序的相同字符。比如,<leader>e 执行操作 0 和 <leader>eb 执行操作 1,在你键入 <leader>e 后,vim 不会立即执行操作 0,而是继续等待用户键入 b,即便你只想键入 <leader>e,vim 也不得不花时间等待输入以确认是哪个快捷键,显然,这让 <leader>e 响应速度变慢。<leader>ea 和 <leader>eb 就没问题。
    文件类型侦测。允许基于不同语言加载不同插件(如,C++ 的语法高亮插件与 python 的不同):
" 开启文件类型侦测
filetype on
" 根据侦测到的不同类型加载对应的插件
filetype plugin on
    快捷键。把 vim(非插件)常用操作设定成快捷键,提升效率:
" 定义快捷键到行首和行尾
nmap lb 0
nmap le $
" 设置快捷键将选中文本块复制至系统剪贴板
vnoremap <Leader>y "+y
" 设置快捷键将系统剪贴板内容粘贴至 vim
nmap <Leader>p "+p
" 定义快捷键关闭当前分割窗口
nmap <Leader>q :q<CR>
" 定义快捷键保存当前窗口内容
nmap <Leader>w :w<CR>
" 定义快捷键保存所有窗口内容并退出 vim
nmap <Leader>WQ :wa<CR>:q<CR>
" 不做任何保存,直接退出 vim
nmap <Leader>Q :qa!<CR>
" 依次遍历子窗口
nnoremap nw <C-W><C-W>
" 跳转至右方的窗口
nnoremap <Leader>lw <C-W>l
" 跳转至方的窗口
nnoremap <Leader>hw <C-W>h
" 跳转至上方的子窗口
nnoremap <Leader>kw <C-W>k
" 跳转至下方的子窗口
nnoremap <Leader>jw <C-W>j
" 定义快捷键在结对符之间跳转,助记pair
nmap <Leader>pa %
    其他。搜索、vim 命令补全等设置:
" 开启实时搜索功能
set incsearch
" 搜索时大小写不敏感
set ignorecase
" 关闭兼容模式
set nocompatible
" vim 自身命令行模式智能补全
set wildmenu
以上的四类配置不仅影响 vim,而且影响插件是否能正常运行。很多插件不仅要在 .vimrc 中添加各自特有的配置信息,还要增加 vim 自身的配置信息,在后文的各类插件介绍中,我只介绍对应插件特有配置信息,当你发现按文中介绍操作后插件未生效,很可能是 vim 自身配置信息未添加,所以一定要把上述配置拷贝至到你的 .vimrc 中,再对照本文介绍一步步操作。.vimrc 完整配置信息参见附录,每个配置项都有对应注释。另外,由于有些插件还未来得及安装,在你实验前面的插件是否生效时,vim 可能有报错信息提示,先别理会,安装完所有插件后自然对了。

0.2 .vim/ 目录
.vim/ 目录是存在所有插件的地方。vim 有一套自己的脚本语言 vimscript,通过这种脚本语言可以实现与vim交互,达到功能扩展的目的。一组 vimscript 就是一个vim插件,vim的很多功能都由各式插件实现。此外,vim 还支持 perl、python、lua、ruby 等主流脚本语言编写的插件,前提是 vim 源码编译时增加 ---enable-perlinterp、--enable-pythoninterp、--enable-luainterp、--enable-rubyinterp 等选项。vim.org 和 github.com 有丰富的插件资源,任何你想得到的功能,如果vim无法直接支持,那一般都有对应的插件为你服务,有需求时可以去逛逛。
vim 插件目前分为 *.vim 和 *.vba 两类,前者是传统格式的插件,实际上就是一个文本文件,通常 someplugin.vim(插件脚本)与 someplugin.txt(插件帮助文件)并存在一个打包文件中,解包后将 someplugin.vim 拷贝到 ~/.vim/plugin/ 目录,someplugin.txt 拷贝到 ~/.vim/doc/ 目录即可完成安装,重启 vim 后刚安装的插件就已经生效,但帮助文件需执行 :helptags ~/.vim/doc/ 才能生效,可通过 :h someplugin 查看插件帮助信息。传统格式插件需要解包和两次拷贝才能完成安装,相对较繁琐,所以后来又出现了 *.vba 格式插件,安装便捷,只需在 shell 中依次执行如下命令即可:
vim someplugin.vba
:so %
:q
不论是直接拷贝插件文件到目录,还是通过 *.vba 安装,都不便于插件卸载、升级,后来又出现了管理插件的插件 pathogen,后文介绍。
后面就正式开始了喽,文中前后内容顺序敏感,请依次查阅。

1 源码安装编辑器 vim
发行套件的软件源中预编译的 vim 要么不是最新版本,要么功能有阉割,有必要升级成全功能的最新版,当然,源码安装必须滴。
卸载老版、下载新版(ftp://ftp.vim.org/pub/vim/unix/vim-7.4.tar.bz2),解压至 ~/downloads/vim74/:
cd ~/downloads/vim74/
./configure --with-features=huge --enable-rubyinterp --enable-pythoninterp --with-python-config-dir=/usr/lib/python2.7/config/ --enable-perlinterp --enable-gui=gtk2 --enable-cscope --prefix=/usr --enable-luainterp
make VIMRUNTIMEDIR=/usr/share/vim/vim74 && make install
其中,--enable-rubyinterp、--enable-pythoninterp、--enable-perlinterp、--enable-luainterp 等分别表示支持 ruby、pthon、perl、lua 编写的插件,--enable-gui=gtk2 表示生成 gvim,--enable-cscope 支持 cscope,--with-python-config-dir=/usr/lib/python2.7/config/ 指定 python 路径,这几个特性非常重要,影响后面各类插件的使用。如果安装过程中出现缺失依赖库及其头文件,按提示自行安装后重新编译。

2 插件管理
既然本文主旨在于讲解如何通过插件将 vim 打造成中意的 C/C++ IDE,那么高效管理插件是首要解决的问题。
vim 自身希望通过在 .vim/ 目录中预定义子目录管理所有插件(比如,子目录 doc/ 存放插件帮助文档、plugin/ 存放通用插件脚本),vim 的各插件打包文档中通常也包含上述两个(甚至更多)子目录,用户将插件打包文档中的对应子目录拷贝至 .vim/ 目录即可完成插件的安装。一般情况下这种方式没问题,但我等重口味插件用户,.vim/ 将变得混乱不堪,至少存在如下几个问题:
    0)插件名字冲突。所有插件的帮助文档都在 doc/ 子目录、插件脚本都在 plugin/ 子目录,同个名字空间下必然引发名字冲突;
    1)插件卸载麻烦。你需要先知道 doc/ 和 plugin/ 子目录下哪些文件是属于该插件的,再逐一删除,容易多删/漏删。
我希望 .vim/ 下的每个子目录是对应一个插件,这样需要升级、卸载插件时,直接找到对应插件目录变更即可。pathogen 为此而生,它突破了 vim 只能识别 .vim/doc/、.vim/plugin/ 等等路径的限制,你可以在按插件名创建独立目录,然后将插件打包档提取至各自插件目录中。通常来说,你需要先创建 ~/.vim/bundle/ 目录,bundle/ 就是以后存放各插件目录的父目录。
安装:先清空 .vim/ 下的所有文件(备份?);创建目录 ~/.vim/bundle/pathogen/autoload/;下载 pathogen.vim(http://www.vim.org/scripts/script.php?script_id=2332)至 ~/.vim/bundle/pathogen/autoload/。
设置:接下来在 .vimrc 增加相关配置信息:
# 将 pathogen 自身也置于独立目录中,需指定其路径
runtime bundle/pathogen/autoload/pathogen.vim
# 运行 pathogen
execute pathogen#infect()
    使用:比如要安装新插件 plugin_name,先在 ~/.vim/bundle/ 下创建目录  plugin_name/,然后到 vim 官网下载 plugin_name 压缩包并解压至 ~/.vim/bundle/plugin_name/ 即可,注意不要重复包含多次 plugin_name/ 目录,如,~/.vim/bundle/plugin_name/plugin_name/。要卸载插件,直接删除 plugin_name/ 插件目录即可。另外,通过 pathogen 管理插件后,相较以前有几点变化:
    0)切勿通过发行套件自带的软件管理工具安装任何插件,不然 .vim/ 又要混乱了;    1)pathogen 无法安装配色主题风格,只能将主题插件手工放置于 ~/.vim/colors/;
    2)安装 *.vba 类型插件:
:e plugin_name.vba
:!mkdir -p ~/.vim/bundle/plugin_name
:UseVimball ~/.vim/bundle/plugin_name
    3)生成帮助文档:
:Helptags
非特殊情况,后文介绍到的插件不再累述如何安装。
另外,说说插件的下载源。相同插件在 vim.org 和 github.com 上都能找到,有些插件在 vim.org 上是最新版,有些又在 github.com 上更新,比如,indexer 插件,在 vim.org(http://www.vim.org/scripts/script.php?script_id=3221)上的版本是 4.15,而在 github.com 上的却是 1.2(https://github.com/shemerey/vim-indexer),所以我建议先去作者个人网站上找,没有再在 vim.org 和 github.com 上比较哪个的最新。甚至,同在 github.com 上都有很多重名插件,自己得稍微花时间确认下,本文中出现的插件,我都会附上最新版下载地址。

3 界面美化
玉不琢不成器,vim 不配不算美。刚安装好的 vim 朴素得吓人,这是与我同时代的软件么?
(默认 vim 界面)
就我的审美观而言,至少有几个问题:语法高亮太单薄、主题风格太简陋、窗口元素太冗余、辅助信息太欠缺。

3.1 主题风格
    一套好的配色方案绝对会影响你的编码效率,vim 内置了 10 多种配色方案供你选择,GUI 下,可以通过菜单(Edit -> Color Scheme)试用不同方案,字符模式下,需要你手工调整配置信息,再重启 vim 查看效果(csExplorer 插件,可在字符模式下不用重启即可查看效果)。不满意,可以去http://vimcolorschemetest.googlecode.com/svn/html/index-c.html慢慢选。我自认为“阅美无数”,目前最夯三甲得算素雅 solarized(https://github.com/altercation/vim-colors-solarized)、多彩 molokai(https://github.com/tomasr/molokai)、复古 phd(http://www.vim.org/scripts/script.php?script_id=3139)。前面说过,pathogen 无法安装主题插件,请将主题插件(仅 *.vim 文件而非插件目录,即,solarized.vim、molokai.vim、phd.vim)拷贝至 ~/.vim/colors/,然后在 .vimrc 中设定选用其作为主题:
" 配色方案
set background=dark
colorscheme solarized
"colorscheme molokai
"colorscheme phd
其中,不同主题都有暗/亮色系之分,这样三种主题六种风格,久不久换一换,给你不一样的心情:
(solarized 主题风格)

3.2 营造专注氛围
    如今的 UX 设计讲究的是内容至上,从 GNOME3 的变化就能看出。编辑器界面展示的应全是代码,不应该有工具条、菜单、滚动条浪费空间的元素,另外,编程是种精神高度集中的脑力劳动,不应出现闪烁光标、花哨鼠标这些分散注意力的东东。配置如下:
" 禁止光标闪烁
set gcr=a:block-blinkon0
" 禁止显示滚动条
set guioptions-=l
set guioptions-=L
set guioptions-=r
set guioptions-=R
" 禁止显示菜单和工具条
set guioptions-=m
set guioptions-=T
    重启 vim 后效果如下:
(去除冗余窗口元素)
    还容易分神?好吧,我们把 vim 弄成全屏模式。vim 自身无法实现全屏,必须借助第三方工具 wmctrl,一个控制窗口 XYZ 坐标、窗口尺寸的命令行工具。先自行安装 wmctrl,再在 .vimrc 中增加如下信息:
" 将外部命令 wmctrl 控制窗口最大化的命令行参数封装成一个 vim 的函数
fun! ToggleFullscreen()
    call system("wmctrl -ir " . v:windowid . " -b toggle,fullscreen")
endf
" 全屏开/关快捷键
map <silent> <F11> :call ToggleFullscreen()<CR>
" 启动 vim 时自动全屏
autocmd VimEnter * call ToggleFullscreen()

上面是一段简单的 vimscript 脚本,外部命令 wmctrl 及其命令行参数控制将指定窗口 windowid(即,vim)全屏,绑定快捷键 F11实现全屏/窗口模式切换(LINUX 下各 GUI 软件约定使用 F11 全屏,最好遵守约定),最后配置启动时自动全屏。



3.3添加辅助信息

去除了冗余元素让vim界面清爽多了,为那些实用辅助信息腾出了空间。光标当前位置、显示行号、高亮当前行/列等等都很有用:

"总是显示状态栏
setlaststatus=2

"显示光标当前位置
setruler

"开启行号显示
setnumber

"高亮显示当前行/列
setcursorline
set cursorcolumn

"高亮显示搜索结果
sethlsearch

效果如下:

(添加辅助信息)



3.4其他美化

默认字体不好看,挑个自己喜欢的,前提是你得先安装好该字体。中文字体,我喜欢饱满方正的(微软雅黑),英文字体喜欢圆润的(Consolas),vim无法同时使用两种字体,怎么办?有人制作发布了一款中文字体用微软雅黑、英文字体用Consolas的混合字体——yaheiconsolas hybrid字体,号称最适合中国程序员使用的字体,效果非常不错(本文全文采用该字体)。在.vimrc中设置下:

"设置gvim显示字体
setguifont=YaHei\ Consolas\ Hybrid\ 11.5

其中,由于字体名存在空格,需要用转义符“\”进行转义;最后的11.5用于指定字体大小。

代码折行也不太美观,禁止掉:

"禁止折行
setnowrap

前面介绍的主题风格对状态栏不起作用,需要借助插件Powerline(https://github.com/Lokaltog/vim-powerline)美化状态栏,在.vimrc中设定状态栏主题风格:

"设置状态栏主题风格
letg:Powerline_colorscheme='solarized256'

效果如下:

(界面美化最终效果)

图中,中英文混合字体看着是不是很舒服哈;增强后的状态栏,不仅界面漂亮多了,而且多了好些辅助信息(所在函数名、文件编码格式、文件类型)。



4代码分析

阅读优秀开源项目源码是提高能力的重要手段,营造舒适、便利的阅读环境至关重要。



4.1语法高亮

代码只有一种颜色的编辑器,就好像红绿灯只有一种颜色的路口,全然无指引。现在已是千禧年后的十年了,早已告别上世纪六、七十年代黑底白字的时代,即使在字符模式下编程(感谢伟大的fbterm),我也需要语法高亮。所幸vim自身支持语法高亮,只需显式打开即可:

"开启语法高亮功能
syntaxenable

"允许用指定语法高亮配色方案替换默认方案
syntaxon

效果如下:

(语法高亮)

上图中STL容器模板类unordered_multimap并未高亮,对滴,vim对C++语法高亮支持不够好(特别是STL、C++11新增元素),必须借由插件stl.vim进行增强,下载(http://www.vim.org/scripts/script.php?script_id=4293)后拷贝至~/.vim/bundle/STL-Syntax/after/syntax/cpp/,重启即可。效果如下:

(增强C++11及STL的语法高亮)



4.2代码缩进

C/C++中的代码执行流由复合语句控制,如if(){}判断复合语句、for(){}循环符号语句等等,这势必出现大量缩进。缩进虽然不影响语法正确性,但对提升代码清晰度有不可替代的功效。

在vim中有两类缩进表示法,一类是用1个制表符('\t'),一类是用多个空格('')。两者并无本质区别,只是源码文件存储的字符不同而已,但,缩进可视化插件对两类缩进显示方式不同,前者只能显示为粗块,后者可显示为细条,就我的审美观而言,选后者。增加如下配置信息:

"自适应不同语言的智能缩进
filetypeindent on

"将制表符扩展为空格
setexpandtab

"设置编辑时制表符占用空格数
settabstop=4

"设置格式化时制表符占用空格数
setshiftwidth=4

"让vim把连续数量的空格视为一个制表符
setsofttabstop=4

其中,注意下expandtab、tabstop与shiftwidth、softtabstop、retab:

  • expandtab,把制表符转换为多个空格,具体空格数量参考tabstop和shiftwidth变量;

  • tabstop与shiftwidth是有区别的。tabstop指定我们在插入模式下输入一个制表符占据的空格数量,LINUX内核编码规范建议是8,看个人需要;shiftwidth指定在进行缩进格式化源码时制表符占据的空格数。所谓缩进格式化,指的是通过vim命令由vim自动对源码进行缩进处理,比如其他人的代码不满足你的缩进要求,你就可以对其进行缩进格式化。缩进格式化,需要先选中指定行,要么键入=让vim对该行进行智能缩进格式化,要么按需键入多次<或>手工缩进格式化;

  • softtabstop,如何处理连续多个空格。因为expandtab已经把制表符转换为空格,当你要删除制表符时你得连续删除多个空格,该就是告诉vim把连续数量的空格视为一个制表符,即,只删一个字符即可。通常应将这tabstop、shiftwidth、softtabstop三个变量设置为相同值;

另外,你总会阅读其他人的代码吧,他们对制表符定义规则与你不同,这时你可以手工执行vim的retab命令,让vim按上述规则重新处理制表符与空格关系。

很多编码规范建议缩进(代码嵌套类似)最多不能超过4层,但难免有更多层的情况,缩进一多,我那个晕啊:

(多层缩进)

我希望有种可视化的方式能将相同缩进的代码关联起来,IndentGuides(https://github.com/nathanaelkane/vim-indent-guides)来了。安装好该插件后,增加如下配置信息:

"随vim自启动
letg:indent_guides_enable_on_vim_startup=1

"从第二层开始可视化显示缩进
letg:indent_guides_start_level=2

"色块宽度
letg:indent_guides_guide_size=1

"快捷键i开/关缩进可视化
:nmap<silent> <Leader>i <Plug>IndentGuidesToggle

重启vim效果如下:

(不连续的缩进可视化)

(*__*)?断节?IndentGuides通过识别制表符来绘制缩进连接线,断节处是空行,没有制表符,自然绘制不出来,算是个小bug,但瑕不掩瑜,有个小技巧可以解决:换行-空格-退格:

(完美可视化缩进)



4.3代码折叠

有时为了去除干扰,集中精力在某部分代码片段上,我会把不关注部分代码折叠起来。vim自身支持多种折叠:手动建立折叠(manual)、基于缩进进行折叠(indent)、基于语法进行折叠(syntax)、未更改文本构成折叠(diff)等等,其中,indent、syntax比较适合编程,按需选用。增加如下配置信息:
" 基于缩进或语法进行代码折叠
"set foldmethod=indent
set foldmethod=syntax
" 启动 vim 时关闭折叠代码
set nofoldenable
    操作:za,打开或关闭当前折叠;zM,关闭所有折叠;zR,打开所有折叠。效果如下:
(代码折叠)

4.4 接口与实现快速切换
    我习惯把类的接口和实现分在不同文件中,肯定会出现编辑接口-编辑实现的循环,分别先打开接口文件(*.h)和实现文件(*.cpp),再手动切换是一种方式,但效率不高。我希望,假如我在接口文件中,vim 自动帮我找到对应的实现文件,当前键入快捷键,要么把当前窗口切换为对应实现文件,要么增加子窗口显示对应实现文件
。

a.vim(https://github.com/vim-scripts/a.vim)来了。安装后增加配置信息:

"*.cpp和*.h间切换
nmap<Leader>ch :A<CR>
"
子窗口中显示*.cpp或*.h
nmap<Leader>sch :AS<CR>

这样,兼容;ch就能在实现文件和接口文件间切换,兼容;sch子窗口中将显示实现文件/接口文件。

a.vim实现原理很简单,基于文件名进行关联,比如,a.vim能识别my_class.h与my_class.cpp,而无法识别my_class.h与your_class.cpp。所以,你在命名文件时得注意下。



4.5代码收藏

源码分析过程中,常常需要在不同代码间来回跳转,我需要“收藏”分散在不同处的代码行,以便需要查看时能快速跳转过去,这时,vim的书签(mark)功能派上大用途了。

vim书签的使用很简单,在你需要收藏的代码行键入mm,这样就收藏好了,你试试,没反应?不会吧,难道你LINUX内核编译参数有问题,或者,vim的编译参数没给全,让我想想,别急,喔,对了,你是指看不到书签?对对对,书签本来就看不到吖。这可不行,小二,来个让书签可视化的插件,亲,来了,visualmark(https://github.com/vim-scripts/Visual-Mark),记得好评。

visual mark使用快捷键mm创建/删除书签,F2正向遍历书签,Shift+ F2 逆向遍历,不太方便,得改;另外,书签颜色不好看,得调。看

看帮助如何配置,昏,没帮助,得,直接改它的源码吧。找到~/.vim/bundle/Visual-Mark/plugin/visualmark.vim,将

map<unique> <F2> <Plug>Vm_goto_next_sign
map<unique> <s-F2> <Plug>Vm_goto_prev_sign

替换成

map<unique>mn <Plug>Vm_goto_next_sign
map<unique> mp <Plug>Vm_goto_prev_sign

这样,mn正向遍历书签、mp逆向遍历;再将

if&bg == "dark"
highlight SignColor ctermfg=whitectermbg=blue guifg=white guibg=RoyalBlue3
else
highlightSignColor ctermbg=white ctermfg=blue guibg=grey guifg=RoyalBlue3
endif

替换成

if&bg == "dark"
highlight SignColor ctermfg=whitectermbg=blue guifg=#FD971F guibg=#1D1D1D
else
highlightSignColor ctermbg=white ctermfg=blue guifg=LightGreenguibg=DarkRed
endif

你可以根据自己喜好提取喜欢颜色的RGB(推荐,提色工具gpick,色卡http://www.colorschemer.com/schemes/),按上例设置即可。提醒下,RGB的前缀是#而非0X,别惯性思维.*_*.

效果如下:
(可视化书签)
    另外,我虽然选用了 visual mark,但不代表它完美了,对我而言,存在两个硬伤:一是,创建的书签无法保存,下次打开该文件后又得重新窗口;一是,无法在不同文件的书签间跳转。前者可借由 vim 的 session 和 viminfo 特性解决,详见后文“环境恢复”节,后者暂无解决思路,只能先切换文件再跳转书签。

4.6 代码导航
    假设你正在分析某个开源项目源码,在 main.cpp 中遇到调用函数 func(),想要查看它如何实现,一种方式:在 main.cpp 中查找 -> 若没有在工程内查找 -> 找到后打开对应文件 -> 文件内查找其所在行 -> 移动光标到该行 -> 分析完后切换会先前文件,不仅效率太低更要命的是影响我的思维连续性。我需要另外高效的方式,就像真正函数调用一样:光标选中调用处的 func() -> 键入某个快捷键自动转换到 func() 实现处 -> 键入某个键又回到 func() 调用处,这就是所谓的代码导航。
    先了解下什么是标签(tag)。这可厉害了,标签可谓是现代 IDE 的基石之一,没有它,类/函数/对象列表、代码补全、代码导航、函数原型提示等等功能是不可能实现的。代码中的类、结构、类成员、函数、对象、宏这些元素就是标签,每个标签有它自己的名字、定义、类型、所在文件中的行位置、所在文件的路径等等属性。
    编译环节之一就是提取标签,但由于编译器并未把生成的标签输出至文本,后来出现了专门用于生成标签的工具 Exuberant Ctags (http://ctags.sourceforge.net/,有墙,后简称 ctags)。ctags,最初只支持生成 C/C++ 语言的标签,目前已支持 41 种语言,具体列表运行如下命令获取:
ctags --list-languages
    学习知识最好方式就是动手实践。我们以 main.cpp、my_class.h、my_class.cpp 三个文件为例:
    第一步,准备代码文件。创建演示目录
/data/workplace/example/
、库子目录/data/workplace/example/lib/,创建如下内容的main.cpp:

#include<iostring>
#include <string>
#include"lib/my_class.h"

usingnamespace std;

intg_num = 128;

//重载函数
staticvoid
printMsg (char ch)
{
std::cout << ch <<std::endl;
}

int
main (void)
{
//
局部对象
conststring name = "yangyang.gnu";

//类
MyClass one;

//成员函数
one.printMsg();

//使用局部对象
cout<< g_num << name << endl;

return (EXIT_SUCCESS);
}

创建如下内容的my_class.h:

#pragmaonce

classMyClass
{
public:
void printMsg(void);

private:
;
};

创建如下内容的my_class.cpp:

#include"my_class.h"

//重载函数
staticvoid
printMsg (int i)
{
std::cout << i <<std::endl;
}

void
MyClass::printMsg (void)
{
std::cout << "I'MMyClass!" << std::endl;
}

第二步,生成标签文件。现在运行ctags生成标签文件:

cd/data/workplace/example/
ctags -R--c++-kinds=+p+l+x+c+d+e+f+g+m+n+s+t+u+v --fields=+liaS --extra=+q--language-force=c++

命令行参数较

多,主要关注--c++-kinds,ctags默认并不会提取所有标签,运行

ctags--list-kinds=c++

可看到ctags支持生成标签类型的全量列表:

c classes
d macro definitions
e enumerators (values insidean enumeration)
f function definitions
g enumeration names
l local variables [off]
m class, struct, and unionmembers
n namespaces
p function prototypes [off]
s structure names
t typedefs
u union names
v variabledefinitions
x external and forward variable declarations[off]

其中,标为off的局部对象、函数声明、外部对象等类型默认不会生成标签,所以我显式加上所有类型。运行完后,example/下多了个文件tags,内容大致如下:

!_TAG_FILE_FORMAT 2 /extendedformat; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
!_TAG_PROGRAM_AUTHOR DarrenHiebert /dhiebert@users.sourceforge.net/
!_TAG_PROGRAM_NAME Exuberant Ctags //
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
!_TAG_PROGRAM_VERSION 5.8 //
MyClass lib/my_class.h /^classMyClass $/;" c
MyClass::printMsg lib/my_class.cpp /^MyClass::printMsg (void)$/;" f class:MyClass signature:(void)
MyClass::printMsg lib/my_class.h /^ voidprintMsg(void);$/;" p class:MyClass access:public signature:(void)
endl lib/my_class.cpp /^ std::cout << "I'M MyClass!"<< std::endl;$/;" m class:std file:
endl lib/my_class.cpp /^ std::cout << i <<std::endl;$/;" m class:std file:
endl main.cpp /^ cout <<g_num << name << endl;$/;" l
endl main.cpp /^ std::cout << ch <<std::endl;$/;" m class:std file:
g_num main.cpp /^int g_num= 128;$/;" v
main main.cpp /^main (void)$/;" f signature:(void)
name main.cpp /^ const string name ="yangyang.gnu";$/;" l
one main.cpp /^ MyClass one;$/;" l
printMsg lib/my_class.cpp /^MyClass::printMsg (void)$/;" f class:MyClass signature:(void)
printMsg lib/my_class.cpp /^printMsg (int i)$/;" f file: signature:(int i)
printMsg lib/my_class.h /^ voidprintMsg(void);$/;" p class:MyClass access:public signature:(void)
printMsg main.cpp /^ one.printMsg();$/;" p file: signature:()
printMsg main.cpp /^printMsg (char ch)$/;" f file: signature:(char ch)
std::endl lib/my_class.cpp /^ std::cout << "I'MMyClass!" << std::endl;$/;" m class:std file:
std::endl lib/my_class.cpp /^ std::cout << i <<std::endl;$/;" m class:std file:
std::endl main.cpp /^ std::cout << ch <<std::endl;$/;" m class:std file:

其中,!开头的几行是ctags生成的软件信息忽略之,下面的就是我们需要的标签,每个标签项至少有如下字段(命令行参数不同标签项的字段数不同):标签名、标签所在的文件名(也是文件路径)、标签项所在行的内容、标签类型(如,l表示局部对象),另外,如果是函数,则有函数签名字段,如果是成员函数,则有访问性字段等等。

第三步,引入标签文件。就是让vim知晓标签文件的路径。在/data/workp

lace/example/目录下用vim打开main.cpp,在vim中执行如下目录引入标签文件tags:

:settags+=/data/workplace/example/tags

既然vim有个专门的命令来引入标签,说明vim能识别标签。虽然标签文件中并无行号,但已经有标签所在文件,以及标签所在行的完整内容,vim只需切换至对应文件,再在文件内作内容查找即可找到对应行。换言之,只要有对应的标签文件,vim就能根据标签跳转至标签定义处。

这时,你可以体验下初级的代码导航功能。把光标移到main.cpp的one.printMsg()那行的printMsg上,键入快捷键g],vim将罗列出名为printMsg的所有标签候选列表,按需选择键入编号即可导航进入。如下图:

(待选标签)

目前为止,离我预期还有差距。

第一,选择候选列表影响思维连续性。首先得明白为何会出现待选列表。前面说过,vim做的事情很简单,就是把光标所在单词放到标签文件中查找,如果只有一个,当前你可以直接导航过去,大部分时候会找到多项匹配标签,比如,函数声明、函数定义、函数调用、函数重载等等都会导致同个函数名出现在多个标签中,vim无法知道你要查看哪项,只能让你自己选择。其实,因为标签文件中已经包含了函数签名属性,vim的查找机制如果不是基于关键字,而是基于语义的话,那也可以直接命中,期待后续vim有此功能吧。既然无法直接解决,换个思路,我不想选择列表,但可以遍历匹配标签。就是说,我不想输入数字选择第几项,但可以接受键入正向快捷键后遍历第一个匹配标签,再次键入快捷键遍历第二个,直到最后一个,键入反向快捷键逆序遍历。这下事情简单了,命令:tnext和:tprevious分

别先后和向前遍历匹配标签,定义两个快捷键搞定:

"正向遍历同名标签
nmap<Leader>tn :tnext<CR>

"反向遍历同名标签
nmap<Leader>tp :tprevious<CR>

等等,这还不行,vim中有个叫标签栈(tagsstack)的机制,:tnext、:tprevious只能遍历已经压入标签栈内的标签,所以,你在遍历前需要通过快捷键ctrl-]将光标所在单词匹配的所有标签压入标签栈中,然后才能遍历。不说复杂了,以后你只需先键入ctrl-],若没导航至需要的标签,再键入<leader>tn往后或者<leader>tp往前遍历即可。如下图所示:

(代码导航)
    第二,如何返回先前位置。当分析完函数实现后,我需要返回先前调用处,可以键入 vim 快捷键 ctrl-t 返回,如果想再次进入,可以用前面介绍的方式,或者键入 ctrl-i。另外,注意,ctrl-o 以是一种返回快捷键,但与 ctrl-t 的返回不同,前者是返回上次光标停留行、后者返回上个标签。
    第三,如何自动生成标签并引入。开发时代码不停在变更,每次还要手动执行 ctags 命令生成新的标签文件,太麻烦了,得想个法周期性针对这个工程自动生成标签文件,并通知 vim 引人该标签文件,嘿,还真有这样的插件 —— indexer(http://www.vim.org/scripts/script.php?script_id=3221)。indexer 依赖 DfrankUtil(http://www.vim.org/scripts/script.php?script_id=3884)、vimprj(http://www.vim.org/scripts/script.php?script_id=3872)两个

插件,请一并安装。请在.vimrc中增加:

"设置插件indexer调用ctags的参数

"默认--c++-kinds=+p+l,重新设置为--c++-kinds=+p+l+x+c+d+e+f+g+m+n+s+t+u+v
"
默认--fields=+iaS不满足YCM要求,需改为--fields=+iaSl
letg:indexer_ctagsCommandLineOptions="--c++-kinds=+p+l
+x+c+d+e+f+g+m+n+s+t+u+v--fields=+iaSl--extra=+q"

另外,indexer还有个自己的配置文件,用于设定各个工程的根目录路径,配置文件位于~/.indexer_files,内容可以设定为:

---------------~/.indexer_files ---------------

[multiple_source_proj]
/data/workplace/multiple_source_proj/

[single_source_proj]
/data/workplace/single_source_proj/

[example]
/data/workplace/example/

[biabiamiamia]
/data/computer/
实践/biabiamiamia/

上例设定了四个工程的根目录,方括号内是对应工程名,后续有新工程,直接添至该文件中即可。这样,从以上目录打开任何代码文件时,indexer便对整个目录创建标签文件,若代码文件有更新,那么在文件保存时,indexer将自动调用ctags更新标签文件,并自动引入进vim中。(indexer生成的标签文件以工程名命名,位于~/.indexer_files_tags/)

好了,解决了这三个问题后,vim的代码导航已经达到我的预期。



4.7标签列表

借助代码导航我能跟着执行流分析代码,但我要分析指定函数实现细节怎么办?先找到该函数,再导航过去?

我希望有个插件能把从当前代码文件中提取出的所有标签单独放在一个子窗口中,最好还能按标签类型给我分门别类,唔...唔,只有tagbar(https://github.com/majutsushi/tagbar)能满足,它自动周期性调用ctags获取结果。先自行安装tagbar,然后在.vimrc中增加如下信息:

"设置tagbar子窗口的位置出现在主编辑区的左边
lettagbar_left=1

"设置显示/隐藏标签列表子窗口的快捷键。速记:taglist
nnoremap <Leader>tl :TagbarToggle<CR>

"设置标签子窗口的宽度
lettagbar_width=32

"tagbar子窗口中不显示冗余帮助信息
letg:tagbar_compact=1

"设置ctags对哪些代码元素生成标签
letg:tagbar_type_cpp = {
\ 'kinds' : [
\'d:macros:1',
\ 'g:enums',
\ 't:typedefs:0:0',
\ 'e:enumerators:0:0',
\ 'n:namespaces',
\ 'c:classes',
\ 's:structs',
\ 'u:unions',
\ 'f:functions',
\ 'm:members:0:0',
\'v:global:0:0',
\ 'x:external:0:0',
\'l:local:0:0'

\ ]
\ 'sro' : '::',
\ 'kind2scope' : {
\ 'g' : 'enum',
\ 'n' :'namespace',
\ 'c' : 'class',
\ 's' :'struct',
\ 'u' : 'union'
\ },
\'scope2kind' : {
\ 'enum' : 'g',
\'namespace' : 'n',
\ 'class' : 'c',
\'struct' : 's',
\ 'union' : 'u'
\ }
\ }

说下粗体行,前面提过,ctags默认并不会提取局部对象、函数声明、外部对象等类型的标签,我必须让tagbar告诉ctags改变默认参数—— 这就是 tagbar_type_cpp变量存在的意义,所以才在前面的配置信息中将外部对象和局部对象显式将其加进tagbar_type_cpp中。

重启vim后,打开一个C/C++源码文件,键入<leader>tl,将在左侧的tagbar窗口中将可看到标签列表:

(标签列表)

其中,注意几个特点:
0
)按作用域归类不同标签。按名字空间n_foo、类Foo进行归类,在内部有声明、有定义;
1
)显示标签类型。名字空间、类、函数等等;
2
)显示完整函数原型;
3
)图形化显示共有成员(+)、私有成员(-)、保护成员(#);
操作:(从标签找源码)选择对应标签后回车即可跳至源码中对应标签位置;(从源码找标签)在源码中暂停几秒鼠标和键盘操作,tagbar子窗口中对应标签将高亮;每次保存文件时或者切换到不同代码文件时tagbar自动调用ctags更是标签数据库;tagbar有两种排序方式,一是按标签名字母先后顺序、一是按标签在源码中出现的先后顺序,在.vimrc中我配置选用后者,键入s切换不同不同排序方式。

另外,我在想个问题:indexer调用ctags生成用标签,tagbar也要调用ctags生成用标签,为何不能由其中之一生成标签,另外一个复用呢?我到没细看两个插件的实现代码,估计是前者与ctags间是文件接口模式,后者与ctags是管道接口模式。插件多了还是麻烦-。-

5 代码开发
在具体编码过程中,我需要一系列提高生产力的功能:批量开/关注释、快速输入结对符、快速输入代码模板、代码智能补全、路径智能补全、从接口生成实现、查看参考库信息等等,我们逐一来实现。

5.1 快速开关注释
    需要注释时,到每行代码前输入 //,取消注释时再删除 //,这种方式不是现代人的行为。IDE 应该支持对选中文本块批量(每行)添加注释符号,反之,可批量取消。本来 vim 通过宏方式可以支持该功能,但每次注释时要自己录制宏,关闭 vim 后宏无法保存,所以有人专门编写了一款插件 NERD Commenter(https://github.com/scrooloose/nerdcommenter),NERD Commenter 根据编辑文档的扩展名自适应采用何种注释风格,如,文档名 x.cpp 则采用 // 注释风格,而 x.c 则是 /**/ 注释风格;另外,如果选中的代码并非整行,那么该插件将用 /**/ 只注释选中部分。
    常用操作:
<leader>cc,注释当前选中文本,如果选中的是整行则在每行首添加 //,如果选中一行的部分内容则在选中部分前后添加分别 /*、*/;
<leader>cu,取消选中文本块的注释。
如下图所示:
(快速开/关注释)
    另外,有时需要 ASCII art 风格的注释,可用 DrawIt!(http://www.vim.org/scripts/script.php?script_id=40)。
    常用操作:
:Distart,开始绘制结构化字符图形,这时可用方向键绘制线条,空格键绘制或擦除字符;
:Distop,停止绘制结构化字符图形。
如下图所示:
(ASCII art风格注释)

5.2 模板补全
    开发时,我经常要输入相同的代码片断,比如 if-else、switch 语句,如果每个字符全由手工键入,我可吃不了这个苦,我想要简单的键入就能自动帮我完成代码模板的输入,并且光标停留在

需要我编辑的位置,比如键入do,vim自动完成

if(/* condition */) {
TODO
}

而且帮我选中/*condition */ 部分,不会影响编码连续性——UltiSnips(https://github.com/SirVer/ultisnips),我的选择。

UltiSnips预定义了几十种语言常用的代码模板,位于~/.vim/bundle/UltiSnips/UltiSnips/,UltiSnips有一套自己的代码模板语法规则,比如:

snippetif "if statement" i
if (${1:/* condition */}) {
${2:TODO}
}
endsnippet

其中,snippet和endsnippet用于表示模板的开始和结束;if是模板名;"ifstatement"是模板描述,你可以把多个模板的模板名定义成一样(如,if() {}和if() {} else {}两模板都定义成相同模板名if),在模板描述中加以区分(如,分别对应"ifstatement"和"if-elsestatement"),这样,在YCM(重量级智能补全插件)的补全列表中可以根据模板描述区分选项不同模板;i是模板控制参数,用于控制模板补全行为,具体参见“快速输入结对符”一节;${1}、${2}是<tab>跳转的先后顺序。

在进行模板补全时,你是先键入模板名(如,if),接着键入补全快捷键(默认<tab>),然后UltiSnips根据你键入的模板名在代码模板文件中搜索匹配的“模板名-模板”,找到对应模板后,将模板在光标当前位置展开。

默认情况下,UltiSnips模板补全快捷键是<tab>,与后面介绍的YCM快捷键有冲突,所有须在.vimrc中重新设定:

"UltiSnips的tab键与YCM冲突,重新设定
letg:UltiSnipsExpandTrigger="<leader><tab>"
letg:UltiSnipsJumpForwardTrigger="<leader><tab>"
letg:UltiSnipsJumpBackwardTrigger="<leader><s-tab>"

UltiSnips预定义了几十种语言常用的代码模板,我关注C/C++的两个文件:~/.vim/bundle/UltiSnips/UltiSnips/c.snippets和~/.vim/bundle/UltiSnips/UltiSnips/cpp.snippets,显然,前者是C程序的代码模板,后者是C++程序的代码模板。我现在几乎不写纯C代码了,所以为了以后代码模板维护方便,我把c.snippets清空了,按自己习惯重写了cpp.snippets,完整cpp.snippets内容如下:

#=================================
#
预处理
#=================================
# #include "..."
snippet INC
#include"${1:TODO}"${2}
endsnippet

##include <...>
snippet inc
#include <${1:TODO}>${2}
endsnippet

#=================================
#
结构语句
#=================================

#if
snippet if
if (${1:/* condition */}) {
${2:TODO}
}
endsnippet

#else if
snippet ei
else if (${1:/* condition */}) {
${2:TODO}
}
endsnippet

#else
snippet el
else {
${1:TODO}
}
endsnippet

#return
snippet re
return(${1:/* condition */});
endsnippet

#Do While Loop
snippet do
do {
${2:TODO}
} while(${1:/* condition */});
endsnippet

#While Loop
snippet wh
while (${1:/* condition */}) {
${2:TODO}
}
endsnippet

#switch
snippet sw
switch (${1:/* condition */}) {
case${2:c}: {
}
break;

default: {
}
break;
}
endsnippet

#通过迭代器遍历容器(可读写)
snippetfor
for (auto ${2:iter} = ${1:c}.begin(); ${3:$2} != $1.end();${4:++iter}) {
${5:TODO}
}
endsnippet

#通过迭代器遍历容器(只读)
snippetcfor
for (auto ${2:citer} = ${1:c}.cbegin(); ${3:$2} !=$1.cend(); ${4:++citer}) {
${5:TODO}
}
endsnippet

#通过下标遍历容器
snippetFor
for (auto ${2:i} = 0; $2 != ${1}.size(); ${3:++}$2) {
${4:TODO}
}
endsnippet

#C++11风格for循环遍历(可读写)
snippetF
for (auto& e : ${1:c}) {
}
endsnippet

#C++11风格for循环遍历(只读)
snippetCF
for (const auto& e : ${1:c}) {
}
endsnippet

#For Loop
snippet FOR
for (unsigned ${2:i} = 0; $2 <${1:count}; ${3:++}$2) {
${4:TODO}
}
endsnippet

#try-catch
snippet try
try {
} catch (${1:/* condition*/}) {
}
endsnippet

snippetca
catch (${1:/* condition */}) {
}
endsnippet

snippetthrow
th (${1:/* condition */});
endsnippet

#=================================
#
容器
#=================================
# std::vector
snippet vec
vector<${1:char}> v${2};
endsnippet

#std::list
snippet lst
list<${1:char}> l${2};
endsnippet

#std::set
snippet set
set<${1:key}> s${2};
endsnippet

#std::map
snippet map
map<${1:key}, ${2:value}> m${3};
endsnippet

#=================================
#
语言扩展
#=================================
# Class
snippet cl
class ${1:`Filename('$1_t', 'name')`}
{
public:
$1 ();
virtual ~$1 ();

private:
};
endsnippet

#=================================
#
结对符
#=================================
#
括号bracket
snippet b "bracket" i
(${1})${2}
endsnippet

#方括号squarebracket,设定为st而非sb,避免与b冲突
snippetst "square bracket" i
[${1}]${2}
endsnippet

#大括号brace
snippet br "brace" i
{
${1}
}${2}
endsnippet

#单引号singlequotes
snippet sq "single quotes" i
'${1}'${2}
endsnippet

#双引号quotes
snippet q "quotes" i
"${1}"${2}
endsnippet

#指针符号arrow
snippet ar "arrow" i
->${1}
endsnippet

#dot
snippet d "dot" i
.${1}
endsnippet

#作用域scope
snippet s "scope" i
::${1}
endsnippet

很简单,根据个人偏好按需调整。效果如下:
(模板补全)

5.3 快速输入结对符
    平时,最让我头痛的字符莫过于 {}、""、[] 等这类结对符,输入它们之所以麻烦,主要因为A)盲打很难找准它们位置,B)还得同时按住shift键。两者再一叠加,非常影响我的思维。要高效输入结对符,应该是输入少量几个字母(对,字母,不是字符)后 vim 自动为你输入完整结对符,而非是我输入一半 vim 输入另一半(不用 delimitMate 的原因)。刚好,这在 UltiSnips 能力范围内,只要定义好模板,可完美地解决这类问题,具体模板见上例中最后的结对符部分。
    在定义结对符模板时,你应该考虑加上模板控制参数 i。默认情况下,UltiSnips 只会当模板名前是空白字符或行首时才进行模板补全,比如,定义 () 的模板如下:
snippet b "bracket"
(${1})${2}
endsnippet
我要调用函数 printf(),在输入完 printf 后应该接着输入括号模板名 b,然后输入模板展开快捷键 <leader><tab>,你会发现 UltiSnips 无法帮你补全模板,因为它看到的不是 b 而是 printfb,这在模板文件中根本未定义。有一种间接解决方式是在 printf 后加个空格,再输入 b<leader><tab> 进行补全,这就成了 printf (),不喜欢这种编码风格。其实,UltiSnips 的作者也注意到这个问题了,他让你可以通过前面提过的模板控制参数 i 进行解决。重新定义 () 的模板如下:
snippet b "bracket" i
(${1})${2}
endsnippet
这样,UltiSnips 只管光标前 1 个字符是否是 b,若是则补全 (),不论 b 前是否有其他字符。类似,其他结对符模板都按此加上 i 控制参数。结对符模板完整定义参见上一节 cpp.snippets 示例。如下是几个快速输入结对符的演示:
(快速输入结对符)
    另外,要想高效编辑结对符,你得了解 vim 自身的某些快捷键。比如,有如下字符串且光标在该字符串的任意字符上,这时在命令模式下键入 va) 后将选中包括括号在内的整个字符串:
(快速选中结对符)
其中,v 是动作、a 是范围、) 是结对符。结对符命令的动作包括:选中 v、复制 y、删除 d、删除后插入 c;结对符命令的范围包括:含结对符 a、不含结对符 i。针对不同结对符,组合不同动作和范围就有 4*2 种方式。比如,va{ 将选中含结对符 {} 的所有字符,di[ 删除不含结对符 [] 的字符串。

5.4 智能补全
    真的,我高潮叻。智能补全是提升编码效率的杀手锏。试想下,有个函数叫getCountAndSizeFromRemotefile(),当你输入 get 后 IDE 自动帮你输入完整的函数名,又如,有个文件 ~/this/is/a/deep/dir/file.txt,就像在 shell 中一样,键入 tab 键自动补全文件路径那是何等惬意!
    智能补全有两类实现方式:基于标签的、基于语义的。

基于标签的智能补全
    前面代码导航时介绍过标签,每个标签项含有标签名、作用域等等信息,当键入某几个字符时,基于标签的补全插件就在标签文件中搜索匹配的标签项,并罗列出来,你选择中意的,这与前面代码导航类似,一个是用于跳转、一个用于输入。基于标签的补全,后端 ctags 先生成标签文件,前端采用插件 new-omni-completion(内置)进行识别。这种方式操作简单、效果不错,一般来说两步搞定。
第一步,生成标签文件。在工程目录的根目录执行 ctags,该目录下会多出个 tags 文件 ;

第二步,引入标签文件。在vim中引入标签文件,在vim中执行命令

:settags+=/home/your_proj/tags

后续,在编码时,键入标签的前几个字符后依次键入ctrl-xctrl-o将罗列匹配标签列表、若依次键入ctrl-xctrl-i则文件名补全、ctrl-xctrl-f则路径补全。

举个例子,演示如何智能补全C++标准库。与前面介绍的一般步骤一样,先调用ctags生成标准库的标签文件,再在vim中引入即可,最后编码时由相应插件实时搜索标签文件中的类或模板,显示匹配项:

首先,获取C++标准库源码文件。安装的GNUC++标准库源码文件,openSUSE可用如下命令:

zypperinstall libstdc++46-devel

安装成功后,在/usr/include/c++/4.6/可见到所有源码文件;

接着,执行ctags生成标准库的标签文件:

cd/usr/include/c++/4.6
ctags -R --c++-kinds=+l+x+p --fields=+iaSl--extra=+q --language-force=c++ -f stdcpp.tags

然后,让OmniCppComplete成功识别标签文件中的标准库接口。C++标准库源码文件中使用了_GLIBCXX_STD名字空间(GNUC++标准库的实现是这样,如果你使用其他版本的标准库,需要自行查找对应的名字空间名称),标签文件里面的各个标签都嵌套在该名字空间下,所以,要让OmniCppComplete正确识别这些标签,必须显式告知OmniCppComplete相应的名字空间名称。在.vimrc中增加如下内容:

letOmniCpp_DefaultNamespaces = ["_GLIBCXX_STD"]

最后,在vim中引入该标签文件。在.vimrc中增加如下内容:

settags+=/usr/include/c++/4.6/stdcpp.tags

后续你就可以进行C++标准库的代码补全,比如,在某个string对象名输入.时,vim自动显示成员列表。如下图所示:

(基于标签的C++标准库补全)

没明白?-。-#咱再来个例子,看看如何补全LINUX系统API。与前面的标准库补全类似,唯一需要注意,LINUX系统API头文件中使用了GCC编译器扩展语法,必须告诉ctags在生成标签时忽略之,否则将生产错误的标签索引。

首先,获取LINUX系统API头文件。openSUSE可用如下命令:

zypperinstall linux-glibc-devel

安装成功后,在/usr/include/中可见相关头文件;

接着,执行ctags生成系统API的标签文件。LINUX内核采用GCC编译,为提高内核运行效率,LINUX源码文件中大量采用GCC扩展语法,这影响ctags生成正确的标签,必须借由ctags的-I命令参数告之忽略某些标签,若有多个忽略字符串之间用逗号分割。比如,在文件unistd.h中几乎每个API声明中都会出现__THROW、__nonnull关键字,前者目的是告诉GCC这些函数不会抛异常,尽量多、尽量深地优化这些函数,后者目的告诉GCC凡是发现调用这些函数时第一个实参为nullptr指针则将其视为语法错误,的确,使用这些扩展语法方便了我们编码,但却影响了ctags正常解析,这时可用-I__THROW,__nonnull命令行参数让ctags忽略这些语法扩展关键字:

cd/usr/include/
ctags -R --c-kinds=+l+x+p --fields=+
lS-I __THROW,__nonnull-f sys.tags

最后,在vim中引入该标签文件。在.vimrc中增加如下内容:

settags+=/usr/include/sys.tags

从以上两个例子来看,不论是C++标准库、boost、ACE这些重量级开发库,还是LINUX系统API均可遵循“下载源码(至少包括头文件)-执行ctags生产标签文件-引入标签文件”的流程实现基于标签的智能补全,若有异常,唯有如下两种可能:一是源码中使用了名字空间,借助OmniCppComplete插件的OmniCpp_DefaultNamespaces配置项解决;一是源码中使用了编译器扩展语法,借助ctags的-I参数解决(上例仅列举了少量GCC扩展语法,此外还有__attribute_malloc__、__wur等等大量扩展语法,具体请参见GCC手册。以后,如果发现某个系统函数无法自动补全,十有八九是头文件中使用使用了扩展语法,先找到该函数完整声明,再将其使用的扩展语法加入-I列表中,最后运行ctags重新生产新标签文件即可)。



基于语义的智能补全

对于智能补全只有轻度需求的用户来说,基于标签的补全能够很好地满足需求,但对于我这类重度需求用户来说,但凡涉及标签,就存在以下几个问题:
问题一,必须定期调用ctags生成标签文件。代码在不同更新,每次智能补全前你得先手动生成标签文件,这还好,你可以借助前面的indexer在保存文件时更新标签文件;麻烦的是,你代码中要用到C++标准库中的接口吧,那么事前你先得对整个标准库生成标签文件,这也还好,无非多个步骤;更昏人的是,你得使用了编译器语法扩展的库,你还得一条条找出具体使用了哪些扩展语言,再让ctags忽略执行语法关键字,真有够麻烦的;
问题二,ctags本身对C++支持有限。面对函数形参、重载操作符、括号初始化的对象,ctags有心无力;对于C++11新增lambda表达式、auto类型推导更是不认识。

我需要更优的补全机制——基于语义的智能补全。语义补全,实时探测你是否有补全需求,无须你定期生成标签,可解决问题一;语义补全,是借助编译器进行代码分析,只要编译器对C++规范支持度高,不论标准库、类型推导,还是boost库中的智能指针都能补全。什么是语义分析补全?看下图:

(语义分析补全)

代码中定义的TCandyBar类型只包括3个成员,但clang_complete能补全编译器根据C++规范自动添加的两个重载操作符、一个默认构造函数、一个析构函数,这就是基于语义分析的智能补全。

要进行语义分析,编译器必不可少。LINUX上有两大主流C++编译器GCC和clang,基于不同编译器,开源社区分别创造出GCCSense和clang_complete两个语义补全插件,又得纠结选哪个-。-... <穿越>请跳转至“源码安装编译器clang”部分做两件事,一是按介绍安装好最新版clang及其标准库,二是看明白clang相较GCC的优势</穿越>...

我选clang_complete,原因如下:
0
)使用难度低。clang采用低耦合设计,语义分析结果(也就是AST)能以接口形式供外围程序使用,无须任何调整,clang_complete便能能轻松拿到clang输出的语义分析结果;而GCC采用高耦合设计,你必须结合补丁重新源码编译GCC,才能让GCCSense接收到它的语义分析结果;
1
)维护时间长。clang_complete最新更新为13年上,而GCCSense则是09年下;
2
)支持跨平台。clang_complete支持所有平台,而GCCSense支持UNIX-like,不支持windows。(好啦,这点是我凑数的,我又不用windows<_<)

clang_complete使用简单,在vim输入模式下,依次键入要补全的字符,需要弹出补全列表时,手工输入<leader>tab。比如有如下代码片断:

stringname_yang = "yangyang.gnu";
string name_wang ="wangwang";

我要补全这两个对象,可以先键入n<leader>tab,这时clang_complete将罗列出所有以n开头的待选项,接着输入ame_后剩下name_yang和name_wang两项,按需选择即可。

到这个节目眼上,我应该先给出 clang_complete 的下载地址,再告诉你如何配置 .vimrc,然后给个截图收工,但是、但是,你知道我是个纠结症+完美症重度患者,虽然 clang_complete 相较 ctags+new-omni-completion 的模式有了质的飞跃,仍有雕琢余地:    
    0)无法随键而全。补全动作有感知,要弹出候选列表菜单还得键入 <leader>tab,我希望插件能自动感知我的补全需求;
    1)无法模糊搜索。上例中,键入的字符要是少了那要出来一堆待选项,选起来眼睛累,多键入几个字符倒是可以让选项少些,但输入多了手又累。这是由于 clang_complete 采用的顺序匹配算法,只要改用子序列匹配算法(模糊搜索算法的一种)即可搞定,这样,我键入 ny 就只出现 name_yang,键入 nw 出现 name_wang;
    2)无法高速补全。补全列表速度不够快,clang_complete 由 python 编写,生成补全列表的速度有一定影响,再加上整个补全动作是在 vim GUI 主线程中执行,所以有时会导致 GUI 假死,我需要由静态语言编写插件内核、动态语言作为粘合剂的补全插件,提升效率;
    什么叫所需即所获?就是当你需要什么功能,它就能给你什么功能。YouCompleteMe(后简称 YCM,https://github.com/Valloric/YouCompleteMe),一个随键而全的、支持模糊搜索的、高速补全的插件,太棒了!YCM 由 google 公司搜索项目组的软件工程师 Strahinja Val Markovic 所开发,YCM 后端调用 libclang(以获取 AST,当然还有其他语言的语义分析库,我不关注)、前端由 C++ 开发(以提升补全效率)、外层由 python 封装(以成为 vim 插件),它可能是我见过安装最复杂的 vim 插件了。有了YCM,基本上 clang_complete、AutoComplPop、Supertab、neocomplcache 可以再见了。
    要运行 YCM 需要几个预备条件:
    0)vim 版本至少达到 7.3.584,且支持 python2,参照“源码安装编辑器 vim”部分可满足;
    1)需要 clang 支持,且版本至少达到 3.3,参照“代码编译”部分可满足;
    接着开始安装 YCM:
    第一步,下载 YCM 源码包及相关依赖:
cd ~/.vim/bundle/
git clone https://github.com/Valloric/YouCompleteMe.git
cd YouCompleteMe/
# 获取 YCM 的依赖包
git submodule update --init --recursive
    第二步,编译 YCM 共享库:
cd ~/downloads/
mkdir ycm_build
cd ycm_build
cmake -G "Unix Makefiles" -DUSE_SYSTEM_LIBclang=ON . ~/.vim/bundle/YouCompleteMe/cpp/
make ycm_support_libs
在 ~/.vim/bundle/YouCompleteMe/python/ 将生成 ycm_client_support.so 、libclang.so、ycm_core.so 三个共享库文件;
    按照惯例,我该介绍 YCM 的设置。
    设置一,YCM 后端调用 clang 进行语义分析,而 clang 有很多参数选项(如,是否支持 C++11 的 -std=c++11、是否把警告视为错误的 -Werror),必须有个渠道让 YCM 能告知 clang,这可以在 .vimrc 中增加一个全局配置,但我有多个工程,每个工程使用的 clang 参数选项不同岂不是每次都要调整 .vimrc?!YCM 采用更具弹性的方式,每个工程有一个名为 .ycm_extra_conf.py 的私有配置文件,在此文件中写入工程的编译参数选项。下面是个完整的例子:
import os
import ycm_core
flags = [
    '-std=c++11',
    '-stdlib=libc++',
    '-Werror',
    '-Weverything',
    '-Wno-deprecated-declarations',
    '-Wno-disabled-macro-expansion',
    '-Wno-float-equal',
    '-Wno-c++98-compat',
    '-Wno-c++98-compat-pedantic',
    '-Wno-global-constructors',
    '-Wno-exit-time-destructors',
    '-Wno-missing-prototypes',
    '-Wno-padded',
    '-x',
    'c++',
    '-I',
    '.',
    '-I',
    '/usr/include/',
    '-I',
    '/usr/include/c++/v1/'
]
compilation_database_folder = ''
if compilation_database_folder:
  database = ycm_core.CompilationDatabase( compilation_database_folder )
else:
  database = None
SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ]
def DirectoryOfThisScript():
  return os.path.dirname( os.path.abspath( __file__ ) )
def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
  if not working_directory:
    return list( flags )
  new_flags = []
  make_next_absolute = False
  path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
  for flag in flags:
    new_flag = flag
    if make_next_absolute:
      make_next_absolute = False
      if not flag.startswith( '/' ):
        new_flag = os.path.join( working_directory, flag )
    for path_flag in path_flags:
      if flag == path_flag:
        make_next_absolute = True
        break
     if flag.startswith( path_flag ):
        path = flag[ len( path_flag ): ]
        new_flag = path_flag + os.path.join( working_directory, path )
        break
   if new_flag:
      new_flags.append( new_flag )
  return new_flags
def IsHeaderFile( filename ):
  extension = os.path.splitext( filename )[ 1 ]
  return extension in [ '.h', '.hxx', '.hpp', '.hh' ]
def GetCompilationInfoForFile( filename ):
  if IsHeaderFile( filename ):
    basename = os.path.splitext( filename )[ 0 ]
    for extension in SOURCE_EXTENSIONS:
      replacement_file = basename + extension
      if os.path.exists( replacement_file ):
        compilation_info = database.GetCompilationInfoForFile(          replacement_file )
        if compilation_info.compiler_flags_:
          return compilation_info
    return None
  return database.GetCompilationInfoForFile( filename )
def FlagsForFile( filename, **kwargs ):
  if database:
    compilation_info = GetCompilationInfoForFile( filename )
    if not compilation_info:
      return None
   final_flags = MakeRelativePathsInFlagsAbsolute(
      compilation_info.compiler_flags_,
      compilation_info.compiler_working_dir_ )
  else:
    relative_to = DirectoryOfThisScript()
    final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )
 return {
    'flags': final_flags,
    'do_cache': True
  }
基本上,根据你工程情况只需调整 .ycm_extra_conf.py 的 flags 部分,前面说过, flags 用于 YCM 调用 libclang 时指定的参数,通常应与构建脚本保持一致(如,CMakeLists.txt)。flags 会产生两方面影响,一是影响 YCM 的补全内容、一是影响代码静态分析插件 syntastic 显示结果(详见后文“静态分析器集成”)。另外注意,该配置文件其实就是个 python 脚本,python 把缩进视为语法,如果你是直接拷贝文中的 .ycm_extra_conf.py 小心缩进部分;
    设置二,在 .vimrc 中增加如下配置信息:
" YCM 补全菜单配色
" 菜单
highlight Pmenu ctermfg=2 ctermbg=3 guifg=#005f87 guibg=#EEE8D5
" 选中项
highlight PmenuSel ctermfg=2 ctermbg=3 guifg=#AFD700 guibg=#106900
" 补全功能在注释中同样有效
let g:ycm_complete_in_comments=1
" 允许 vim 加载 .ycm_extra_conf.py 文件,不再提示
let g:ycm_confirm_extra_conf=0
" 开启 YCM 基于标签引擎
let g:ycm_collect_identifiers_from_tags_files=1
" 引入 C++ 标准库tags
set tags+=/data/misc/software/misc./vim/stdcpp.tags
" YCM 集成 OmniCppComplete 补全引擎,设置其快捷键
inoremap <leader>; <C-x><C-o>
" 补全内容不以分割子窗口形式出现,只显示补全列表
set completeopt-=preview
" 从第一个键入字符就开始罗列匹配项
let g:ycm_min_num_of_chars_for_completion=1
" 禁止缓存匹配项,每次都重新生成匹配项
let g:ycm_cache_omnifunc=0
" 语法关键字补全            
let g:ycm_seed_identifiers_with_syntax=1
其中大部分内容从注释就能了解,粗体配置项见下文。
    YCM 集成了多种补全引擎:语义补全引擎、标签补全引擎、OmniCppComplete 补全引擎、其他补全引擎。
    0)YCM 的语义补全。YCM 不会在每次键入事件上触发语义补全,作者认为这会影响补全效率而且没什么必要(我持保留意见),YCM 只在如下两种场景下触发语义补全:一是补全标识符所在文件必须在 buffer 中(即,文件已打开);一是在对象后键入 .、指针后键入 ->、名字空间后键入 ::。如下图所示:
(YCM 的语义补全)
上图中,我先尝试补全类 MyClass 失败,接着我把 MyClass 所在的文件 MyClass.h 打开后切回 main.cpp 再次补全

回值但实现框架无return,而voidprintMsg(int)声明无返回值反在实现框架中出现了return。

关于两个缺点,先前我计划优化下protodef源码再发给原作者,后来想想,protodef借助ctags代码分析实现的,本来就存在某些缺陷,好吧,后续我找个时间写个与protodef相同功能但对C++支持更完善的插件,内部当然借助libclang啦。

另外,每个人都有自己的代码风格,比如,return语句我喜欢

return(TODO);

所以,调整了protodef.vim源码,把242行改为

calladd(full, " return(TODO);")

比如,函数名与形参列表间习惯添加个空格

voidMyClass::getSize (void);

所以,把213行改为

letproto = substitute(proto, '(\_.*$'," (" . params .Tail, '')



5.6库信息参考

有过Win32SDK开发经验的朋友对MSDN或多或少有些迷恋吧,对于多达7、8个参数的API,如果没有一套函数功能描述、参数讲解、返回值说明的文档,那么软件开发将是人间炼狱。别急,vim也能做到。

要使用该功能,系统中必须先安装对应man。安装LINUX系统函数man,先下载(https://www.kernel.org/doc/man-pages/download.html),解压后将man1/至man8/拷贝至/usr/share/man/,运行manfork确认是否安装成功。安装C++标准库man,先下载(ftp://GCC.gnu.org/pub/GCC/libstdc++/doxygen/),选择最新新 libstdc++-api-X.X.X.man.tar.bz2,解压后将 man3/ 拷贝至 /usr/share/man/,运行 man std::vector 确认是否安装成功;
    vim 内置的 man.vim 插件可以查看已安装的 man,需在 .vimrc 中配置启动时自动加载该插件:
" 启用:Man命令查看各类man信息
source $VIMRUNTIME/ftplugin/man.vim
" 定义:Man命令查看各类man信息的快捷键
nmap <Leader>man :Man 3 <cword><CR>
需要查看时,在 vim 中键入输入 :Man fork 或者 :Man std::vector (注意大小写)即可在新建分割子窗口中查看到函数参考信息,为了方便,我设定了快捷键 <Leader>man,这样,光标所在单词将被传递给 :Man 命令,不用再手工键入,如下图所示:
(库信息参考)
    另外,我们编码时通常都是先声明使用 std 名字空间,在使用某个标准库中的类时前不会添加 std:: 前缀,所以 vim 取到的当前光标所在单词中也不会含有 std:: 前缀,而,C++ 标准库所有 man 文件名均有 std:: 前缀,所以必须将所有文件的 std:: 前缀去掉才能让 :Man 找到正确的 man 文件。在 libstdc++-api-X.X.X.man/man3/ 执行批量重命名以取消所有 man文件的 std::前缀:
rename "std::" "" std::*
顺便说下,很多人以为 rename 命令只是 mv 命令的简单封装,非也,在重命名方面,rename太专业了,远非 mv 可触及滴,就拿上例来说,mv 必须结合 sed 才能达到这样的效果。
    我认为,好的库信息参考手册不仅有对参数、返回值的描述,还应有使用范例,
上面介绍的
LINUX系统函数man做到了,C++标准库man还未达到我要求。所以,若有网络条件,我更愿意选择查看在线参考,C++推荐:http://www.cplusplus.com/reference/、http://en.cppreference.com/w/Cppreference:Archives,前者范例多、后者更新勤;UNIX推荐:http://pubs.opengroup.org/onlinepubs/9699919799/functions/contents.html、http://man7.org/LINUX/man-pages/dir_all_alphabetic.html,前者基于最新SUS(SingleUNIX Specification,单一UNIX规范)、后者偏重LINUX扩展。



6工程管理

我虽不要求达不到软件工程的高度,但基本的管理还是有必要的,比如,工程文件的管理、多文档编辑、工程环境的保存与恢复。



6.1工程文件浏览

我通常将工程相关的文档放在同个目录下,通过NERDtree(https://github.com/scrooloose/nerdtree)插件可以查看文件列表,要打开哪个文件,光标选中后回车即可在新buffer中打开。

安装好NERDtree后,请将如下信息加入.vimrc中:

"使用NERDTree插件查看工程文件。设置快捷键,速记:filelist
nmap <Leader>fl :NERDTreeToggle<CR>

"设置NERDTree子窗口宽度
letNERDTreeWinSize=32

"设置NERDTree子窗口位置
letNERDTreeWinPos="right"

"显示隐藏文件
letNERDTreeShowHidden=1

"NERDTree子窗口中不显示冗余帮助信息
letNERDTreeMinimalUI=1

"删除文件时自动删除文件对应buffer
letNERDTreeAutoDeleteBuffer=1

常用操作:回车,打开选中文件;r,刷新工程目录文件列表;I(大写),显示/隐藏隐藏文件;m,出现创建/删除/剪切/拷贝操作列表。键入<leader>fl后,右边子窗口为工程项目文件列表,如下图所示:
(工程文件浏览)

6.2 多文档编辑
    vim 的多文档编辑涉及三个概念:buffer、window、tab,这三个事物与我们常规理解意义大相径庭。vim 把加载进内存的文件叫做 buffer,buffer 不一定可见;若要 buffer 要可见,则必须通过 window 作为载体呈现;同个看面上的多个 window 组合成一个 tab。一句话,vim 的 buffer、window、tab 你可以对应理解成视角、布局、工作区。我所用到的多文档编辑场景几乎不会涉及 tab,重点关注 buffer、window。
    vim 中每打开一个文件,vim 就对应创建一个 buffer,多个文件就有多个 buffer,但默认你只看得到最后 buffer 对应的 window,通过插件 MiniBufExplorer(https://github.com/fholgado/minibufexpl.vim,原始版本已停止更新且问题较多,该版本是其他人 fork 的新项目)可以把所有 buffer 罗列出来,并且可以显示多个 buffer 对应的 window。如下图所示:
(buffer 列表)
我在 vim 中打开了 main.cpp、CMakeLists.txt、MyClass.cpp、MyClass.h 这四个文件,最上面子窗口(buffer 列表)罗列出的 [1:main.cpp][4:CMakeLists.txt][5:MyClass.cpp][6:MyClass.h] 就是对应的四个 buffer。当前显示了 main.cpp 和 MyClass.h 的两个 buffer,分别对应绿色区域和橙色区域的 window,这下对 buffer 和 window 有概念了吧。图中关于 buffer 列表再说明两点:
0)* 表示当前有 window 的 buffer,换言之,有 * 的 buffer 是可见的;! 表示当前正在编辑的 window;
1)你注意到 buffer 序号 1 和 4 不连续的现象么?只要 vim 打开一个 buffer,序号自动增一,中间不连续有几个可能:可能一,打开了 1、2、3、4 后,用户删除了 2、3 两个 buffer,剩下 1、4;可能二,先打开了其他插件的窗口(如,tagbar)后再打开代码文件;
    配置:将如下信息加入 .vimrc 中:
" 显示/隐藏 MiniBufExplorer 窗口
map <Leader>bl :MBEToggle<cr>
" buffer 切换快捷键
map <C-Tab> :MBEbn<cr>
map <C-S-Tab> :MBEbp<cr>
    操作:一般通过 NERDtree 查看工程文件列表,选择打开多个代码文件后,MiniBufExplorer 在顶部自动创建 buffer 列表子窗口。通过前面配置,ctrl-tab 正向遍历 buffer,ctrl-shift-tab 逆向遍历(光标必须在 buffer 列表子窗口外);在某个 buffer 上键入 d 删除光标所在的 buffer(光标必须在 buffer 列表子窗口内):
(多文档编辑)
    默认时,打开的 window 占据几乎整个 vim 编辑区域,如果你想把多个 window 平铺成多个子窗口可以使用 MiniBufExplorer 的 s 和 v 命令:在某个 buffer 上键入 s 将该 buffer 对应 window 与先前 window 上下排列,键入 v 则左右排列(光标必须在 buffer 列表子窗口内)。如下
图所示:

(在子窗口中编辑多文档)

图中,通过vim自身的f名字查找buffer序号可快速选择需要的buffer。另外,编辑单个文档时,不会出现buffer列表。



6.3环境恢复*

vim的编辑环境保存与恢复是我一直想要的功能,我希望恢复:已打开文件、光标位置、undo/redo、书签、子窗口、窗口大小、窗口位置、命令历史、buffer列表、代码折叠。vim文档说借助viminfo(恢复书签)和session(恢复除书签外的其他项)特性很可以实现这个功能。请确保你的vim支持+mksession和+viminfo特性:

vim--version | grep session
vim --version | grep viminfo

如果编译vim时添加了--with-features=huge选项那就没问题。

一般说来,保存/恢复环境步骤如下:

第一步,保存所有文档:

:wa

第二步,借助viminfo和session保存当前环境:

:mksession!my.vim
:wviminfo! my.viminfo

第三步,退出vim:

:qa

第四步,恢复环境,进入vim后执行:

:sourcemy.vim
:rviminfo my.viminfo

具体能保存哪些项,可由sessionoptions指定,另外,前面几步可以设定快捷键,在.vimrc中增加:

"设置环境保存项
setsessionoptions="blank,buffers,globals,localoptions,tabpages,sesdir,folds,help,options,resize,winpos,winsize"

"保存快捷键
map<leader>ss :mksession! my.vim<cr> :wviminfo!my.viminfo<cr>

"恢复快捷键
map<leader>rs :source my.vim<cr> :rviminfo my.viminfo<cr>

这样,简化第二步、第四步操作。

按此操作,并不能像vim文档中描述的那样能保存所有环境,比如,书签、代码折叠、命令历史都无法恢复。这和我预期存在较大差距,暂且用用吧,找个时间在深入研究!



7工具链集成

既然我们要把vim打造成IDE,那必须得集成编译器、构建工具、静态分析器、动态调试器,当然,你可能还需要版本控制、重构工具等等,我暂时还好。



7.1编译器/构建工具集成

先说下编译器和构建工具。vim再强大也只能是个优秀的编辑器而非编译器,它能高效地完成代码编辑工作,但必须通过其他外部命令实现将代码转换为二进制可执行文件;一旦工程上规模,你不可能单个单个文件编译,这时构建工具就派上场了。



代码编译

GCC是LINUX上C/C++编译器的事实标准,几乎所有发行套件都默认安装,它很好但不是最好:编译错误提示信息可读性不够(特别对于C++模板错误信息基本就是读天书)、基于GCC的二次开发困难重重。我需要更优秀的C++编译器。

StanleyB. Lippman先生所推荐宇宙最强C++编译器——LLVM/clang。Stanley何许人也?不是吧,你玩C++居然不认识他。C++世界二号人物,当年在贝尔实验室,BjarneStroustrup构思了C++功能框架,StanleyLippman实现了第一个版本。还无感?好吧,他是《C++Primer》的作者。说了大神,再说说大神推荐的编译器。

LLVM出自伊利诺伊大学研究项目,由google和苹果公司赞助。LLVM的存在只为两个目的:一是尽可能地模块化现有代码以方便在此基础上进行二次开发、一是提供比传统构建工具链更好的用户体验。LLVM是个很大很大的项目群,几乎把从编译到调试的各个构建环节都重新实现了一遍:

  • 机器码生成方面:包含LLVMcore子项目,LLVMcore能把满足它约定的中间语言翻译为高质量的机器码;

  • 编译器方面:C/C++编译器clang、接管GCC生成的AST并进行后续机器码生成的后端编译器dragonegg;

  • 调试器方面:增强处理模板/重载/多线程等等特性的调试器LLDB、能根据程序bug生成测试用例甚至给出修正代码的符号虚拟机klee;

  • 链接器方面:能更好地处理符号链接的链接器lld;

  • 标准库方面:满足最新C++规范的高性能实现标准库libc++和libc++ABI、OpenCL标准库的高性能实现libclc;

  • 运行期环境方面:支持OpenMP规范的运行期环境、Java和.NET的虚拟机vmkit;

  • 代码优化方面:用于提升并行计算性能的polly、针对内存安全检查的调优工具SAFECode;

颤抖吧,小伙伴们!

我们重点介绍clang子项目,clang把标准C/C++代码转换为中间语言,换言之,前端clang+后端LLVM(后简称LLVM/clang)就是一款可替代GCC的优秀编译器。相较GCC,LLVM/clang有众多优势,尤其以下几点:
0
)错误信息可读性强。能指出哪行、哪列有错误,并且用波浪线突显出来;另外,尽可能给出修改建议(比如提示你是否拼写错误);最重要的是对C++模板相关语法错误提示非常友好;(注,GCC4.8开始学习clang优化错误信息可读性)
1
)编译速度快且占用资源少。编译速度是GCC的2.5倍,内存消耗只有GCC的1/5;
2
)兼容且扩充GCC。clang支持GCC的所有编译参数,也就是说,使用GCC开发的项目,你只需把makefile中使用的编译器从GCC改为clang即可,无须大面积调整构建系统脚本即可重新编译;另外,clang还对GCC的编译参数进行了人性化扩展,比如,GCC无法打开所有编译警告(-Wall、-Wextra不够滴),clang只需-Weverything;
3
)高度抽象的模块化设计。弱耦合性带来的模块高度复用、二次开发非常容易,比如,前面介绍的基于语义的C/C++代码补全插件YouCompleteMe,就是借助libclang库实现。

还在担心采用clang编译的源码移到GCC下无法编译?安啦,没问题的,你无非担心四方面:
0
)编译参数是否兼容?前面说过,clang全面兼容GCC,所以编译参数完全兼容;
1
)语言扩展是否兼容?只要不是像LINUX内核那样大规模采用各种复杂语言扩展属性,一般项目中用到的简单扩展是没问题的;
2
)标准库接口是否兼容?标准库接口是C++规范中指定的,根本不存在标准库接口不兼容一说;
3
)标准库动/静态库符号是否兼容?你只要确保采用clang的标准库头文件对应链接clang的动态链接库、采用GCC的标准库头文件对应链接GCC的动态链接库的要求就不会出现问题。

在源码安装clang前,你需先自行安装GCC,两个目的,一是你得有个编译器来编译编译器clang(呵呵,绕了吧),二是其他人的项目可能会用到GCC。

下载LLVM、clang及辅助库源码:

cd~/downloads
svn co http://llvm.org/svn/llvm-project/llvm/trunkllvm
cd llvm/tools
svn cohttp://llvm.org/svn/llvm-project/cfe/trunk clang
cd ../..
cdllvm/tools/clang/tools
svn cohttp://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra
cd../../../..
cd llvm/projects
svn cohttp://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
cd..

先关掉其他应用,尽量多的系统资源留给GCC进行编译clang源码:

mkdirbuild
cd build
../configure --enable-optimized CC=/usr/bin/GCCCXX=/usr/bin/g++

接下来,你先洗个澡,再约个会,回来应该编译好了(thinkpadT410I,CPU奔腾双核P6000,MEM4G DDRIII,耗时2小时)。确认下:

clang--version

玩C/C++你肯定要用到标准库。概念上,GCC配套的标准库涉及libstdc++和libsupc++两个子库,前者是接口层(即,上层的封装),后者是实现层(即,底层的具体实现),对应实物文件,你得需要两个子库的头文件和动态链接库(*.so)。openSUSE的安装源中有,直接安装头文件

zypperinlibstdc++47-devel

位于/usr/include/c++/4.7/,接着安装链接库

zypperinlibstdc++6

位于/usr/lib/libstdc++.so.6。呃,是滴,libstdc++、libsupc++两个子库的相关文件是合并一起安装的。

对应到clang的标准库,libc++(接口层)和libc++abi(实现层)也需要安装头文件和动态链接库(*.so)。openSUSE的安装源中并无libc++,头文件和动态链接库只能源码安装:

cd~/downloads/
svn co http://llvm.org/svn/llvm-project/libcxx/trunklibcxx
cd libcxx/lib
./buildit

头文件已经生成到~/downloads/libcxx/include/,要让clang找到必须复制到/usr/include/c++/v1/

cp-r ~/downloads/libcxx/include/ /usr/include/c++/v1/

*.so文件已生成~/downloads/libcxx/lib/libc++.so.1.0,要让clang访问必须复制到/usr/lib/,并创建软链接

ln-s ~/downloads/libcxx/lib/libc++.so.1.0~/downloads/libcxx/lib/libc++.so.1
ln -s~/downloads/libcxx/lib/libc++.so.1.0~/downloads/libcxx/lib/libc++.so
cp~/downloads/libcxx/lib/libc++.so* /usr/lib/

类似,源码安装libc++abi的头文件和动态链接库:

cd~/downloads/
svn cohttp://llvm.org/svn/llvm-project/libcxxabi/trunk libcxxabi
cdlibcxxabi/lib
./buildit

头文件已经生成到~/downloads/libcxxabi/include/,要让clang找到必须复制到/usr/include/c++/v1/

cp-r ~/downloads/libcxxabi/include/ /usr/include/c++/v1/

*.so文件已生成~/downloads/libcxx/lib/libc++abi.so.1.0,要让clang访问必须复制到/usr/lib/,并创建软链接

ln-s ~/downloads/libcxxabi/lib/libc++abi.so.1.0~/downloads/libcxxabi/lib/libc++abi.so.1
ln -s~/downloads/libcxxabi/lib/libc++abi.so.1.0~/downloads/libcxxabi/lib/libc++abi.so
cp~/downloads/libcxxabi/lib/libc++abi.so* /usr/lib/

后续可以通过如下选项进行代码编译:

clang++-std=c++11-stdlib=libc++ -Werror -Weverything -Wno-disabled-macro-expansion-Wno-float-equal -Wno-c++98-compat -Wno-c++98-compat-pedantic-Wno-global-constructors -Wno-exit-time-destructors-Wno-missing-prototypes -Wno-padded -lc++ -lc++abimain.cpp

这一大波编译选项很崩溃么@_@!我来简单说说:

  • -std=c++11:使用C++11新特性;

  • -stdlib=libc++:指定使用clang的标准库头文件/usr/include/c++/v1/;

  • -Werror:将所有编译警告视为编译错误;

  • -Weverything:打开所有编译警告选项。在GCC中,无法通过单个选项打开所有编译警告,必须繁琐的同时指定-Wall、-Wextra、以及大量分散的其他选项,为此clang新增了-Weverything。当然,有些警告意义不大,完全可忽略,如下;

  • -Wno-disabled-macro-expansion:禁止使用宏表达式,忽略此警告;

  • -Wno-float-equal:浮点类型不应使用!=和==运算符,忽略此警告;

  • -Wno-c++98-compat、-Wno-c++98-compat-pedantic:采用C++11新特性的代码无法兼容C++98,忽略此警告;

  • -Wno-global-constructors:在main()之前存在执行的代码,忽略此警告;

  • -Wno-exit-time-destructors:在main()之后存在执行的代码,忽略此警告;

  • -Wno-missing-prototypes:虽有函数定义但缺失函数原型,忽略此警告;

  • -Wno-padded:结构体大小应为4字节整数倍,忽略此警告(编译器自动调整对齐边界);

  • -lc++:指定链接/usr/lib/libc++.so标准库;

  • -lc++abi:指定链接/usr/lib/libc++abi.so标准库。注意:这两个选项非常重要,缺失将导致链接失败!



系统构建

对于只有单个代码文件的项目来说,无非是保存代码文件、shell中调用GCC编译、链接这样的简单方式即可实现;但,对于动辄几十上百个文件的工程项目,采用这种方式只会把自己逼疯,必须借助构建工具管理工程的整个构建过程。

LINUX有两类工程构建工具——Makefile系和非Makefile系,Makefile系常见构建工具有GNU出品的老牌autoconf、新生代的CMake,非Makefile系中最著名的要数SCons。KDE就是通过CMake(http://www.cmake.org/cmake/resources/software.html)构建出来的,易用性灵活性兼备,洒泪推荐。
    一般来说,你需要先写个名为 CMakeLists.txt 的构建脚本,然后执行 cmake  CMakeLists.txt 命令将生成 Makefile 文件,最后执行 make 命令即可编译生成可执行程序。
    举例来说,你工程包含 main.cpp 文件,要构建它,你需要执行如下步骤:
    第一步,编写 CMakeLists.txt,内容如下:
PROJECT(main)
SET(SRC_LIST main.cpp)
SET(CMAKE_CXX_COMPILER "clang++")
SET(CMAKE_CXX_FLAGS "-std=c++11 -stdlib=libc++ -Werror -Weverything -Wno-deprecated-declarations -Wno-disabled-macro-expansion -Wno-float-equal -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-global-constructors -Wno-exit-time-destructors -Wno-missing-prototypes -Wno-padded")
SET(CMAKE_EXE_LINKER_FLAGS "-lc++ -lc++abi")
SET(CMAKE_BUILD_TYPE Debug)
ADD_EXECUTABLE(main ${SRC_LIST})
其中,PROJECT 指定工程名、SET 是 cmake 变量赋值命令、ADD_EXECUTABLE 指定生成可执行程序的名字。括号内的大写字符串是 cmake 内部预定义变量,这是 CMakeLists.txt 脚本的重点,下面详细讲述:
SRC_LIST 指定参与编译的源码文件列表,如果有多个文件请用空格隔开,如,你工程有 main.cpp、lib/MyClass.cpp、lib/MyClass.h 三个文件,那么可以指定为:
SET(SRC_LIST main.cpp lib/MyClass.cpp)
CMAKE_CXX_COMPILER 指定选用何种编译器;
CMAKE_CXX_FLAGS 设定编译选项;
CMAKE_EXE_LINKER_FLAGS 设定链接选项。一定要将 -lc++ 和 -lc++abi 独立设定到 CMAKE_EXE_LINKER_FLAGS 变量中而不能放在 CMAKE_CXX_FLAGS,否则无法通过链接;
CMAKE_BUILD_TYPE 设定生成的可执行程序中是否包含调试信息。
另外,对于编译选项,我的原则是严己宽人。也就是说,在我本机上使用最严格的编译选项以发现尽量多 bug,发布给其他人的源码包使用最宽松的编译选项以减少环境差异导致编译失败的情况。前面罗列出来的就是严格版的 CMakeLists.txt,宽松版我会考虑:编译器改用 GCC(很多人没装 clang)、忽略所有编译警告、让编译器进行代码优化、去掉调试信息、添加安装路径等要素,具体如下:
PROJECT(main)
SET(SRC_LIST main.cpp)
SET(CMAKE_CXX_COMPILER "g++")
SET(CMAKE_CXX_FLAGS "-std=c++11 -O3")
SET(CMAKE_BUILD_TYPE Release)
ADD_EXECUTABLE(porgram_name ${SRC_LIST})
INSTALL(PROGRAMS porgram_name DESTINATION /usr/bin/)
    第二步,基于 CMakeLists.txt 生成 Makefile。在 CMakeLists.txt 所在目录执行:
cmake CMakeLists.txt
执行成功的话,你将在该目录下看到 Makefile 文件;
    第三步,基于 Makefile 生成可执行程序。相同目录下执行:
make
这一步,就是在调用编译器进行编译,如果存在代码问题,修正错误后重新执行这一步即可,不用再次执行第一、二步。
    基本上,你的新工程,可以在基于上面的 CMakeLists.txt 进行修改,执行一次第二步后,每次代码调整只需执行第三步即可。

一键编译
    工程项目的构建过程游离于 vim 之外终究不那么方便,前面章节介绍的构建过程是在 shell 中执行的,全在 vim 中执行又是如何操作。第一步的创建 CMakeLists.txt 没问题,vim 这么优秀的编辑器编辑个普通文本文件易如反掌;第二步的生成 Makefile 也没问题,在 vim 内部通过 ! 前缀可以执行 shell 命令,:!cmake CMakeLists.txt 即可;第三步的编译过程更没问题,因为 vim 自身支持 make 命令,直接在 vim 中输入 :make 命令它会调用外部 make 程序读取当前目录中的 Makefile 文件,完成编译、链接操作。当然,一次性编译通过的可能性很小,难免有些语法错误(语义错误只能靠调试器了),vim 将编译器抛出的错误和警告信息输出到 quickfix 中,执行 :cw 命令即可显示 quickfix。说了这么多,概要之,先通过构建工具(CMake 可通过 CMakeLists.txt 文件,autotools 可通过 configure 文件)生成整个工程的 Makefile,再在 vim 中执行 :make,最后显示 quickfix。
    要实现一键编译,无非是把这几步映射为 vim 的快捷键,即:
nmap <Leader>m :wa<CR>:make<CR><CR>:cw<CR>
分解说明下,m 为设定的一键编译快捷键,:wa<CR> 保存所有调整文档内容,:make<CR> 调用 make 命令,后面的 <CR> 消除执行完 make 命令屏幕上“Press ENTER or type command to continue”的输入等待提示,:cw<CR> 显示 quickfix(仅当有编译错误或警告时)。如下图所示:
(一键编译)
我新建了一个工程,编辑好 CMakeLists.txt,执行 :!cmake CMakeLists.txt,接着 <leader>m 一
键编译,quickfix窗口显示了编译错误,光标自动定位到需要你解决的第一个编译错误,回车后光标自动调整到该错误对应的代码位置,修正后重新<leader>r,编译通过并运行生成的程序。

你可能会遇到,调整过的代码能通过编译,但是,要么在工程目录中无法找到可执行程序,要么有程序但体现不出代码调整的内容(就像没调整过代码一样)。对于情况一,还算好,至少你晓得生成可程序失败了,肯定哪儿出了问题,不会继续往下新增代码;情况二,就麻烦了,你想通过运行程序检查刚才添加的代码运行是否正常,以为运行的是新程序,其实,代码调整后的新程序并未生成,运行是老程序,“哇,一切正常,往下写新业务逻辑代码”。导致这两个情况的根本原因,代码中存在链接错误导致并未正常创建新的可执行程序。badnews —— 如果编译错误,quickfix窗口会固定在底部,罗列出所有编译过程中的所有错误,如果编译正常(即使存在链接错误),quickfix窗口会出现“PressENTER or type command to continue”的输入等待提示信息,前面提过,为了省去手工输入回车,已经在<Leader>m中为:make多绑定个回车符<CR>,换言之,在编译正确链接错误的情况下,你是无法查看到quickfix窗口的;goodnews —— 有两种方式解决该问题:

方式一,将前面<Leader>m中为:make绑定的回车符<CR>去掉,即

nmap<Leader>m :wa<CR>:make<CR><CR>:cw<CR>

方式二,先删除老的可执行程序,再编译、链接,发现缺失可执行程序时,再手工执行:make,这样,可查看具体是什么链接错误了,将如下配置信息加入.vimrc中:

nmap<Leader>m:!rm -rfmain<CR>:wa<CR>:make<CR><CR>:cw<CR>

我选方式二。

到此,已实现一键编译,要实现一键编译及运行无非就在刚才的快捷键中追加绑定运行程序的外部命令即可。新快捷键设定为<leader>g,假定生成的可执行程序名为main,将如下配置信息加入.vimrc中:

nmap<Leader>g :!rm -rfmain<CR>:wa<CR>:make<CR>:cw<CR><CR>:!./main<CR>

最后,再次强调实现一键编译及运行的几个前提:vim的当前目录必须为工程目录、事前准备好Makefile文件且放于工程目录的根目录、生成的程序必须在工程目录的根目录。



7.2静态分析器集成

onetake,中意“一次成型”,最早指歌手录歌时一次性通过录制,不存在发现错误-修正错误-重新录制这样的往返动作。onetake在编程环境中,就是一次性通过编译,我个人很享受onetake带来的快感。当然,要达到onetake,不仅需要扎实的编程功底,还需要工具的辅佐——代码静态分析器。syntastic(https://github.com/scrooloose/syntastic),一款支持多语言的实时语法检查插件。在syntastic的作用下,编码中、编译前,所有语法错误都将被抓出来并呈现给你。

由于YCM已经深度集成syntastic,所以基本上你无须配置。在~/.vim/bundle/syntastic/syntax_checkers/cpp/下你可以看到可选的C++语法检查脚本,你可以通过命令:SyntasticInfo查看当前选用的检查脚本,应该是ycm.vim。整个过程分为如下几步:

第一步,发现错误。YCM内部调用libclang分析语法错误,通过管道传递给syntastic呈现。当你保存代码或者安静2秒,错误检查后台任务将自动启动,若有错误,syntastic将接收到。

第二步,呈现错误。syntastic并不非立马显示YCM发过来的错误信息,除非你触发下次击键事件,否则你看不到错误信息,换言之,干等是没结果的,你必须有次击键动作(没办法,vim内部机制所限,后台任务无法直接更新GUI,所以才采用变通的击键方式)。对于存在语法错误的代码,在行首有个红色的>>高亮显示,如果你觉得>>不够醒目,你可以参照如下方式重新设置:

letg:syntastic_error_symbol = '✗'
let g:syntastic_warning_symbol ='⚠'

第三步,查看错误。好了,现在已经知道哪行代码有问题,具体问题描述如何查看?两种方式:一种是将光标移至问题行,vim将在其底部显示简要错误描述;一种是将光标移至问题行,键入<leader>d后,vim将在其底部显示详细错误描述。

如下所示:

(静态代码分析)



8 其他辅助

大家关注的IDE核心功能前面都已逐一介绍过了,有些辅助功能我认为也有必要让你知道,不是都在提程序员人文关怀嘛,从我做起!



8.1内容查找

vim支持正则表达式,那么已经具有强劲的查供能力,在当前文件内查找,vim的/和?查找命令非常好用,但工程内查找,自带的查找用户体验还无法达到我的预期。我希望查找时直接根据光标位置确定查找关键字,查询结果通过列表形式罗列处理,选择某项光标就能跳转到对应位置,另外,可以在工程范围内查找,或者在打开文件内查找。有个叫grep.vim(https://github.com/yegappan/grep)的插件满足我的需求。

grep.vim实际上提供了在vim内部方便使用grep、fgrep、egrep、agrep、find、xargs等工具的接口。如果要在工程内进行查找,可以在vim命令行中执行:Grep,grep.vim插件会依次提示输入待查找关键字、待查找的文件类型,回车即可执行查找,结果将罗列在quickfix中(注,vim与很多外部命令、插件的交互信息都将在quickfix中呈现,这里说到的搜索结果是一个例子,另外一个前面“一键编译”章节提到的编译结果也将输出至此,可用:cw命令打开/关闭quickfix窗口)。若是要忽然大小写则执行:Grep-i,若是要递归搜索子目录则执行:Grep-r;另外,如果光标在某个字符串上,那么grep.vim插件将自动提取该字符串作为查找关键字。如果要在当前打开文件内查找,可以执行:GrepBffer命令,参数同上。为高效执行搜索操作,可以设定快捷键:

"使用Grep.vim插件在工程内全局查找,设置快捷键。快捷键速记法:searchin project
nnoremap <Leader>sp :Grep -ir<CR><CR><CR>

"使用Grep.vim插件在工程内全局查找,设置快捷键。快捷键速记法:searchin buffer
nnoremap <Leader>sb :GrepBuffer -ir<CR><CR>

:Grep默认需要手工确认搜索关键字、搜索文件类型,方便起见,我在映射命令后加了几个连续<CR>,这样就不用手工回车确认了。

举个例子,光标移到MyClassABC下,键入<leader>sp后,grep.vim自动提取MyClassABC为搜索关键字,执行工程中内查找,找到到4个匹配项并显示在quickfix中;接着键入<leader>sb 后,执行打开文件内查找,找到 1 个匹配项。如下图所示:
(内容查找)

8.2 内容替换
    有个名为 iFoo 的全局变量,被工程中 16 个文件引用过,由于你岳母觉得匈牙利命名法严重、异常、绝对以及十分万恶,为讨岳母欢心,不得不将该变量更名为 foo,怎么办?依次打开每个文件,逐一查找后替换?vim 有强大的内容替换命令:
:[range]s/{pattern}/{string}/[flags]
    在进行内容替换操作时,我关注几个因素:如何指定替换文件范围、是否整词匹配、是否逐一确认后再替换。
    如何指定替换文件范围?0)如果在当前文件内替换,[range] 不用指定,默认就在当前文件内;1)如果在当前选中区域,[range] 也不用指定,在你键入替换命令时,vim 自动将生成如下命令:
:'<,'>s/{pattern}/{string}/[flags]
2)你也可以指定行范围,如,第三行到第五行:
:3,5s/{pattern}/{string}/[flags]
3)如果对打开文件进行替换,你需要先通过 :bufdo 命令显式告知 vim 范围,再执行替换;4) 如果对工程内所有文件进行替换,先 :args **/*.cpp **/*.h 告知 vim 范围,再执行替换;
    是否整词匹配?{pattern} 用于指定匹配模式。如果需要整词匹配,则该字段应由 \< 和 \> 修饰待替换字符串(如,\<iFoo\>);无须整词匹配则不用修饰,直接给定该字符串即可;
    是否逐一确认后再替换?[flags] 可用于指定是否需要确认。若无须确认,该字段设定为 ge 即可;有时不见得所有匹配的字符串都需替换,若在每次替换前进行确认,该字段设定为 gec 即可。
    是否整词匹配和是否确认两个条件叠加就有 4 种组合:非整词且不确认、非整词且确认、整词且不确认、整词且确认,每次手工输入这些命令真是麻烦;我把这些组合封装到一个函数中,如下 Replace() 所示:
" 替换函数。参数说明:
" confirm:是否替换前逐一确认
" wholeword:是否整词匹配
" replace:被替换字符串
function! Replace(confirm, wholeword, replace)
    wa
    let flag = ''
    if a:confirm
        let flag .= 'gec'
    else
        let flag .= 'ge'
    endif
    let search = ''
    if a:wholeword
        let search .= '\<' . escape(expand('<cword>'), '/\.*$^~[') . '\>'
    else
        let search .= expand('<cword>')
    endif
    let replace = escape(a:replace, '/\&~')
    execute 'argdo %s/' . search . '/' . replace . '/' . flag . '| update'
endfunction
为最大程度减少手工输入,Replace() 还能自动提取待替换字符串(只要把光标移至待替换字符串上),同时,替换完成后自动为你保存更改的文件。现在要做的就是赋予 confirm、wholeword 不同实参实现 4 种组合,再绑定 4 个快捷键即可。如下:
" 不确认、非整词
nnoremap <Leader>R :call Replace(0, 0, input('Replace '.expand('<cword>').' with: '))<CR>
" 不确认、整词
nnoremap <Leader>rw :call Replace(0, 1, input('Replace '.expand('<cword>').' with: '))<CR>
" 确认、非整词
nnoremap <Leader>rc :call Replace(1, 0, input('Replace '.expand('<cword>').' with: '))<CR>
" 确认、整词
nnoremap <Leader>rcw :call Replace(1, 1, input('Replace '.expand('<cword>').' with: '))<CR>
nnoremap <Leader>rwc :call Replace(1, 1, input('Replace '.expand('<cword>').' with: '))<CR>
我平时用的最多的无须确认但整词匹配的替换模式,即 <leader>rw。
    请将完整配置信息添加进 .vimrc 中:
" 替换函数。参数说明:
" confirm:是否替换前逐一确认
" wholeword:是否整词匹配
" replace:被替换字符串
function! Replace(confirm, wholeword, replace)
    wa
    let flag = ''
    if a:confirm
        let flag .= 'gec'
    else
        let flag .= 'ge'
    endif
    let search = ''
    if a:wholeword
        let search .= '\<' . escape(expand('<cword>'), '/\.*$^~[') . '\>'
    else
        let search .= expand('<cword>')
    endif
    let replace = escape(a:replace, '/\&~')
    execute 'argdo %s/' . search . '/' . replace . '/' . flag . '| update'
endfunction
" 不确认、非整词
nnoremap <Leader>R :call Replace(0, 0, input('Replace '.expand('<cword>').' with: '))<CR>
" 不确认、整词
nnoremap <Leader>rw :call Replace(0, 1, input('Replace '.expand('<cword>').' with: '))<CR>
" 确认、非整词
nnoremap <Leader>rc :call Replace(1, 0, input('Replace '.expand('<cword>').' with: '))<CR>
" 确认、整词
nnoremap <Leader>rcw :call Replace(1, 1, input('Replace '.expand('<cword>').' with: '))<CR>
nnoremap <Leader>rwc :call Replace(1, 1, input('Replace '.expand('<cword>').' with: '))<CR>
    比如,我将工程的所有 *.cpp 和 *.h 中的关键字 MyClassA 按不确认且整词匹配模式替换成 MyClass,所以注释中的关键字不会被替换掉。如下所示:
(不确认且整词匹配模式的替换)
又比如,对当前文件采用需确认且无须整词匹配的模式进行替换,你会看到注释中的关键字也被替换了:
(确认且无须整词匹配模式的替换)

8.3 快速移动
    vim 有两类快速移动光标的方式:一类是以单词为单位的移动,比如,w 正向移动到相邻单词的首字符、b 逆向移动到相邻单词的首字符、e 正向移动到相邻单词的尾字符、 ge 逆向移动到相邻单词的尾字符;一类是配合查找字符的方式移动,比如,fa 正向移动到第一个字符 a 处、Fa 逆向移动到第一个字符 a 处。你要在非相邻的单词或字符间移动,你可以配合数字参数,比如,正向移动到相隔八个单词的首字符执行 8w、逆向移动到第四个 a 字符处执行 4Fa。
    有如下文本:
backpage kcal liam jack facebook target luach ajax
假定光标在行首,需要移动到 facebook 的字符 a 处,先来数下前面有 1、2 ... 5 个 a,然后用前面所的 5fa,唔,怎么在 jack 上呢?等等,好像数错了,再数次 1、2 ... 6,对滴,应该是 6fa,这下对了。我的个天,不能让哥太累,得找个插件帮忙 —— easymotion(https://github.com/Lokaltog/vim-easymotion)。
    easymotion 只做一件事:把满足条件的位置用 [A~Za~z] 间的标签字符标出来,找到你想去的位置再键入对应标签字符即可快速到达。比如,上面的例子,假设光标在行首,我只需键入 <leader><leader>fa (为避免与其他快捷键冲突,easymotion 采用两次 <leader> 作为前缀键),所有的字符 a 都被重新标记成 a、b、c、d、e、f 等等标签(原始内容不会改变),f 标签为希望移动去的位置,随即键入 f 即可到达。如下图所示:
(快速移动)
    类似,前面提过的 w、e、b、ge、F、j、k 等命令在 easymotion 作用下也能实现快速移动,其中,j 和 k 可跨行移动。同时,你还可以搭配 v 选中命令、d 删除命令、y 拷贝命令,比如,v<leader><leader>fa,快速选中光标当前位置到指定字符 a 之间的文本,d<leader><leader>fa,快速删除光标当前位置到指定字符 a 之间的文本,下图所示:
(搭配操作命令的快速移动)

9 尾声
    2011 年 9 月我写了篇《拼装的艺术:vim 之 IDE 进化实录》,近期(13 年 10 月)原计划更新下智能补全部分,后来越改越发觉原版问题太多,加之各插件推陈出新、自己对 vim 的认识加深,索性完全重新。
    两年多时间,与很多朋友有过交流,有三类问题探讨得最频繁,我的观点简要阐述如下,后续不再欢迎、理会、回复相关问题。
    为何不用 Code::Blocks 这类一站式 IDE?每个人的做事的出发点、性格观念千差万别,我不想拿 LINUX kernel 是 linus torvalds 用 microemacs(一种 emacs 变种)开发的来说事儿,就我而言,迷恋 vim 的高效编辑能力、无限扩充能力,这是其他编辑器无法超越的。此外,我享受的是过程,不是结果!
    哪个是最适合编码的编辑器?LINUX 上存在两种编辑器:神之编辑器 emacs,编辑器之神 vim。关于 emacs 与 vim 孰轻谁重之争已是世纪话题,我无意参与其中,在我眼里,它们流淌着自由的血液、继承着创新的基因,作为顶级编辑器,二者在这个领域都作到了极致,让世人重新认识了编辑的本质 —— 用命令而非字字键入 —— 去完成编辑任务。emacs,伪装成编辑器的操作系统,太杂、太重,我更喜欢专注于编辑的 vim。
    怎样的 IDE 才算好?对于初入 LINUX 开发的人而言,Code::Blocks 是最快上手的选择;对于我这类喜欢折腾、追求效率、愿意用脑力换体力的人来说,vim 搭配各类插件是好的 IDE;对于 Donald Knuth 这等宗师,他们站在整个系统的层面,LINUX 搭配各类命令行工具也是某种意义的 IDE。所以,只要你能得心应手地完成软件开发任务,并且察觉不到工具的存在,那就是最适合你的 IDE。
    末了,我不清楚这篇文章能帮到哪些人、帮到什么程度,但我自己受益匪浅!写作过程,是知识体系完整重构的过程,理清了思路、加深了记忆。如果它能引发你的思绪,或许这就是价值!

【附:完整.vimrc】

"更新时间:2013-12-1820:01:53



"定义快捷键的前缀,即<Leader>

letmapleader=";"



">>

"文件类型侦测



"开启文件类型侦测

filetypeon

"根据侦测到的不同类型加载对应的插件

filetypeplugin on



"<<



">>

"vim自身(非插件)快捷键



"定义快捷键到行首和行尾

nmaplb 0

nmaple $



"设置快捷键将选中文本块复制至系统剪贴板

vnoremap<Leader>y "+y

"设置快捷键将系统剪贴板内容粘贴至vim

nmap<Leader>p "+p



"定义快捷键关闭当前分割窗口

nmap<Leader>q :q<CR>

"定义快捷键保存当前窗口内容

nmap<Leader>w :w<CR>

"定义快捷键保存所有窗口内容并退出vim

nmap<Leader>WQ :wa<CR>:q<CR>

"不做任何保存,直接退出vim

nmap<Leader>Q :qa!<CR>



"设置快捷键遍历子窗口

"依次遍历

nnoremapnw <C-W><C-W>

"跳转至右方的窗口

nnoremap<Leader>lw <C-W>l

"跳转至方的窗口

nnoremap<Leader>hw <C-W>h

"跳转至上方的子窗口

nnoremap<Leader>kw <C-W>k

"跳转至下方的子窗口

nnoremap<Leader>jw <C-W>j



"定义快捷键在结对符之间跳转,助记pair

nmap<Leader>pa %



"<<



">>

"其他



"开启实时搜索功能

setincsearch



"搜索时大小写不敏感

setignorecase



"关闭兼容模式

setnocompatible



"vim自身命令行模式智能补全

setwildmenu



"<<



"插件管理

"将pathogen自身也置于独立目录中,需指定其路径

runtimebundle/pathogen/autoload/pathogen.vim

"运行pathogen

executepathogen#infect()



"配色方案

setbackground=dark

colorschemesolarized

"colorschememolokai

"colorschemephd



">>

"营造专注气氛



"禁止光标闪烁

setgcr=a:block-blinkon0



"禁止显示滚动条

setguioptions-=l

setguioptions-=L

setguioptions-=r

setguioptions-=R



"禁止显示菜单和工具条

setguioptions-=m

setguioptions-=T



"将外部命令wmctrl控制窗口最大化的命令行参数封装成一个vim的函数

fun!ToggleFullscreen()

callsystem("wmctrl -ir " . v:windowid . " -btoggle,fullscreen")

endf

"全屏开/关快捷键

map<silent> <F11> :call ToggleFullscreen()<CR>

"启动vim时自动全屏

"autocmdVimEnter * call ToggleFullscreen()



"<<



">>

"辅助信息



"总是显示状态栏

setlaststatus=2



"显示光标当前位置

setruler



"开启行号显示

setnumber



"高亮显示当前行/列

setcursorline

setcursorcolumn



"高亮显示搜索结果

sethlsearch



"<<



">>

"其他美化



"设置gvim显示字体

setguifont=YaHei\ Consolas\ Hybrid\ 11.5



"禁止折行

setnowrap



"设置状态栏主题风格

letg:Powerline_colorscheme='solarized256'



"<<



">>

"语法分析



"开启语法高亮功能

syntaxenable

"允许用指定语法高亮配色方案替换默认方案

syntaxon



"<<



">>

"缩进



"自适应不同语言的智能缩进

filetypeindent on



"将制表符扩展为空格

setexpandtab

"设置编辑时制表符占用空格数

settabstop=4

"设置格式化时制表符占用空格数

setshiftwidth=4

"让vim把连续数量的空格视为一个制表符

setsofttabstop=4



"缩进可视化插件IndentGuides

"随vim自启动

letg:indent_guides_enable_on_vim_startup=1

"从第二层开始可视化显示缩进

letg:indent_guides_start_level=2

"色块宽度

letg:indent_guides_guide_size=1

"快捷键i开/关缩进可视化

nmap<silent> <Leader>i <Plug>IndentGuidesToggle



"<<



">>

"代码折叠



"基于缩进或语法进行代码折叠

"setfoldmethod=indent

setfoldmethod=syntax

"启动vim时关闭折叠代码

setnofoldenable



"<<



">>

"接口与实现快速切换



"*.cpp和*.h间切换

nmap<Leader>ch :A<CR>

"子窗口中显示*.cpp或*.h

nmap<Leader>sch :AS<CR>



"<<



">>

"代码导航



"设置插件indexer调用ctags的参数

"默认--c++-kinds=+p+l,重新设置为--c++-kinds=+l+p+x+c+d+e+f+g+m+n+s+t+u+v

"默认--fields=+iaS不满足YCM要求,需改为--fields=+iaSl

letg:indexer_ctagsCommandLineOptions="--c++-kinds=+l+p+x+c+d+e+f+g+m+n+s+t+u+v--fields=+iaSl --extra=+q"



"正向遍历同名标签

nmap<Leader>tn :tnext<CR>

"反向遍历同名标签

nmap<Leader>tp :tprevious<CR>



"跳转到定义处

"nmap<Leader>gd <C-]>



"<<



">>

"标签列表



"设置tagbar子窗口的位置出现在主编辑区的左边

lettagbar_left=1

"设置显示/隐藏标签列表子窗口的快捷键。速记:taglist

nnoremap<Leader>tl :TagbarToggle<CR>

"设置标签子窗口的宽度

lettagbar_width=32

"tagbar子窗口中不显示冗余帮助信息

letg:tagbar_compact=1

"设置ctags对哪些代码元素生成标签

letg:tagbar_type_cpp = {

\'ctagstype' : 'c++',

\'kinds' : [

\'d:macros:1',

\'g:enums',

\'t:typedefs:0:0',

\'e:enumerators:0:0',

\'n:namespaces',

\'c:classes',

\'s:structs',

\'u:unions',

\'f:functions',

\'m:members:0:0',

\'v:global:0:0',

\'x:external:0:0',

\'l:local:0:0'

\],

\'sro' : '::',

\'kind2scope' : {

\'g' : 'enum',

\'n' : 'namespace',

\'c' : 'class',

\'s' : 'struct',

\'u' : 'union'

\},

\'scope2kind' : {

\'enum' : 'g',

\'namespace' : 'n',

\'class' : 'c',

\'struct' : 's',

\'union' : 'u'

\}

\}



"<<



"模板补全

"UltiSnips的tab键与YCM冲突,重新设定

letg:UltiSnipsExpandTrigger="<leader><tab>"

letg:UltiSnipsJumpForwardTrigger="<leader><tab>"

letg:UltiSnipsJumpBackwardTrigger="<leader><s-tab>"



">>

"YCM补全



"YCM补全菜单配色

"菜单

highlightPmenu ctermfg=2 ctermbg=3 guifg=#005f87 guibg=#EEE8D5

"选中项

highlightPmenuSel ctermfg=2 ctermbg=3 guifg=#AFD700 guibg=#106900



"补全功能在注释中同样有效

letg:ycm_complete_in_comments=1



"允许vim加载.ycm_extra_conf.py文件,不再提示

letg:ycm_confirm_extra_conf=0



"开启YCM标签引擎

letg:ycm_collect_identifiers_from_tags_files=1

"引入C++标准库tags

settags+=/data/misc/software/misc./vim/stdcpp.tags



"YCM集成OmniCppComplete补全引擎,设置其快捷键

inoremap<leader>; <C-x><C-o>



"补全内容不以分割子窗口形式出现,只显示补全列表

setcompleteopt-=preview



"从第一个键入字符就开始罗列匹配项

letg:ycm_min_num_of_chars_for_completion=1



"禁止缓存匹配项,每次都重新生成匹配项

letg:ycm_cache_omnifunc=0



"语法关键字补全

letg:ycm_seed_identifiers_with_syntax=1



"<<

">>

"由接口快速生成实现框架



"设置pullproto.pl脚本路径

letg:protodefprotogetter='~/.vim/bundle/protodef/pullproto.pl'



"成员函数的实现顺序与声明顺序一致

letg:disable_protodef_sorting=1



"<<



">>

"库信息参考

"启用:Man命令查看各类man信息

source$VIMRUNTIME/ftplugin/man.vim



"定义:Man命令查看各类man信息的快捷键

nmap<Leader>man :Man 3 <cword><CR>



"<<



">>

"工程文件浏览



"使用NERDTree插件查看工程文件。设置快捷键,速记:filelist

nmap<Leader>fl :NERDTreeToggle<CR>

"设置NERDTree子窗口宽度

letNERDTreeWinSize=22

"设置NERDTree子窗口位置

letNERDTreeWinPos="right"

"显示隐藏文件

letNERDTreeShowHidden=1

"NERDTree子窗口中不显示冗余帮助信息

letNERDTreeMinimalUI=1

"删除文件时自动删除文件对应buffer

letNERDTreeAutoDeleteBuffer=1



"<<



">>

"多文档编辑

"显示/隐藏MiniBufExplorer窗口

map<Leader>bl :MBEToggle<cr>



"buffer切换快捷键

map<C-Tab> :MBEbn<cr>

map<C-S-Tab> :MBEbp<cr>



"<<



">>

"环境恢复



"设置环境保存项

setsessionoptions="blank,globals,localoptions,tabpages,sesdir,folds,help,options,resize,winpos,winsize"



"保存快捷键

map<leader>ss :mksession! my.vim<cr> :wviminfo!my.viminfo<cr>



"恢复快捷键

map<leader>rs :source my.vim<cr> :rviminfo my.viminfo<cr>



"<<

"设置快捷键实现一键编译及运行

nmap<Leader>m :!rm -rf main<CR>:wa<CR>:make<CR><CR>:cw<CR>

nmap<Leader>g :!rm -rfmain<CR>:wa<CR>:make<CR><CR>:cw<CR>:!./main<CR>



">>

"查找



"使用Grep.vim插件在工程内全局查找,设置快捷键。快捷键速记法:searchin project

nnoremap<Leader>sp :Grep -ir<CR><CR><CR>

"使用Grep.vim插件在工程内全局查找,设置快捷键。快捷键速记法:searchin buffer

nnoremap<Leader>sb :GrepBuffer -ir<CR><CR>



"<<



">>

"替换



"替换函数。参数说明:

"confirm:是否替换前逐一确认

"wholeword:是否整词匹配

"replace:被替换字符串

function!Replace(confirm, wholeword, replace)

wa

letflag = ''

ifa:confirm

letflag .= 'gec'

else

letflag .= 'ge'

endif

letsearch = ''

ifa:wholeword

letsearch .= '\<' . escape(expand('<cword>'), '/\.*$^~[') .'\>'

else

letsearch .= expand('<cword>')

endif

letreplace = escape(a:replace, '/\&~')

execute'argdo %s/' . search . '/' . replace . '/' . flag . '| update'

endfunction

"不确认、非整词

nnoremap<Leader>R :call Replace(0, 0, input('Replace'.expand('<cword>').' with: '))<CR>

"不确认、整词

nnoremap<Leader>rw :call Replace(0, 1, input('Replace'.expand('<cword>').' with: '))<CR>

"确认、非整词

nnoremap<Leader>rc :call Replace(1, 0, input('Replace'.expand('<cword>').' with: '))<CR>

"确认、整词

nnoremap<Leader>rcw :call Replace(1, 1, input('Replace'.expand('<cword>').' with: '))<CR>

nnoremap<Leader>rwc :call Replace(1, 1, input('Replace'.expand('<cword>').' with: '))<CR>



"<<

新版迁移:https://github.com/yangyangwithgnu/use_vim_as_ide

http://yangyangwithgnu.github.io/
asyncrun.vim:快速执行异步Shell命令并输出到Vim Quickfix窗口 用户自定义命令的执行配置通常包含在Vim的配置文件.vimrc中,该文件位于用户的主目录下。编辑.vimrc文件可以使用Vim本身,也可以使用外部文本编辑器。在.vimrc中添加自定义命令时,需要遵循Vim的脚本语法。每一条命令或定义必须以换行符结束,以确保脚本的正确执行。Vim脚本支持变量、函数、条件判断、循环等编程结构。以下是一个简单的例子,展示如何在.vimrc中添加一个用户自定义命令:" 定义一个简单的函数,输出当前时间function! 阅读详情

相关推荐

golang开发只vim插件

用于在linux下golang开发环境的vim插件

tagbar 使用简介

tagbar是在vim界面一侧显示文件的标签的插件,类似于taglist,比如文件的类、结构体、函数、变量等。 tagbar的安装 一、点击下面的网址下载tagbar,安装文件为tagbar.vmb http://www.vim.org/scripts/script.php?script_id=3465 二、用vim打开: vim tagbar.vmb 三、底行模式输入:so %

yangqing99801的博客 1万+

vim-clang:另一个基于Clang的CC ++代码完成插件

vim-clang 使用clang来解析和完成C / C ++源文件。 安装 需要安装可执行的clang 。 将文件plugin/clang.vim放入~/.vim/plugin 。 cd ~/.vim/bundle; git clone https://github.com/justmao945/vim-clang.git 推荐使用cd ~/.vim/bundle; git clone https://github.com/justmao945/vim-clang.git 为什么 我曾经是clang_compelete的用户,这确实做得很好,但是有一天我发现我必须编写另一个插件来克服它的一些缺点... vim-clang VS 可以在vim-clang中为不同的文件类型设置用户选项。 let g:clang_c_options = '-std=gnu11' let g:cla

一个程序员的Vim

餐参考

OneSea 1476

Investigating Bi-Level Optimization for Learning and Vision from a Unified Perspective: A Survey and

文章首先从双层优化的角度统一表示了超参数优化、多任务和元学习等复杂学习和视觉问题,建立了一个基于最佳响应的单层重构和一个统一的算法框架,来制定一个基于梯度的双层优化方法,最后讨论了该框架的潜力和未来研究方向。...

dawnyi_yang的博客 1109

【UG\NX二次开发】使用内部函数展开和折叠工序导航器

NX二次开发-群精选」专栏收录了那些曾散落在聊天对话框里的精彩的知识分享,这都是被遗落的宝贵财富。让我们一起向大佬学习,充满激情地进步,不断超越自己。底部联系方式,欢迎您加入一起交流。​下次一定。

王牌飞行员_里海的博客 818

vim怎么配置只有打开特定文件高亮_vim:11行代码实现quickfix窗口悬浮预览文件

最近实现了一个小功能,精简了我的配置,去掉了一个冗余的插件。功能就是:通过上下键在quickfix窗口移动试试通过 预览窗口或者悬浮窗口显示 指定行的文件。效果如封面。我们用vim的时候普遍都有这样的一个需求,就是在当前目录下递归搜索某个字符串,然后打开字符串所在的位置。这个功能是标题提到的这个功能的前提。vim自带了一些命令,比如helpgrep命令用来搜索vim help里面的字符串并在qui...

weixin_39818691的博客 476

AI赋能数字人:打造与语音节奏完美匹配的高质量手势动画

想象一个场景:当你与一位数字人交谈时,它不仅能理解你的言语,还能恰到好处地挥动手臂、比划手势,就像真实的人类交流一样自然流畅。这正是当前AI技术致力于实现的目标——通过分析语音内容,自动生成与之相匹配的手势动作,为数字人赋予更加丰富的情感表达和非言语沟通能力。AI手势生成技术的出现,标志着数字人在模仿人类自然交流方式上迈出了重要一步。随着技术的不断成熟与应用场景的拓宽,未来我们可以期待更多富有表现力和情感互动的数字人出现在我们的日常生活中,开启人机交互的新篇章。

这里汇聚了前沿的技术分享与实用的开发技巧,带你探索从创意到企业的技术创业之路。 792

vim的make构建工具

原文 make是同步工具,可丢弃了. 可用:h:terminal或:h term_start()在vim中异步.这里用term_start. Make位置 目标 1,异步make,quickfix后更新,用户可像终端中的make一样,看到make的进度. 2,如果正确,则显示成功make消息,不会警告. 3,如果存在具特定缓冲区号和行号的quickfix项,显示found n qf entries. 4,如果用户在构建时,请求构建,则切换make缓冲. 5,如果用户在空闲时构建,请求,则删除上个构建缓冲.

个人进步之路 1339

自然语言处理6 -- 情感分析

1 概述 情感分析是自然语言处理中常见的场景,比如淘宝商品评价,饿了么外卖评价等,对于指导产品更新迭代具有关键性作用。通过情感分析,可以挖掘产品在各个维度的优劣,从而明确如何改进产品。比如对外卖评价,可以分析菜品口味、送达时间、送餐态度、菜品丰富度等多个维度的用户情感指数,从而从各个维度上改进外卖服务。 情感分析可以采用基于情感词典的传统方法,也可以采用基于深度学习的方法,下面详细讲解 ...

谢杨易的博客 5万+

基于相场法模拟金属铜的晶粒的长大过程附matlab完整代码

晶粒生长是一种重要的材料形态演化过程,对于材料的性能和制备有着非常重要的影响。相场法是一种将材料结构和热力学性质联系起来的方法,它能够有效地模拟晶粒的长大过程并预测材料的结构和性能。本文所采用的相场模型是基于Cahn-Hilliard方程和Allen-Cahn方程的二次相场模型。其中,Cahn-Hilliard方程描述了晶界的自由能密度,而Allen-Cahn方程则描述了晶粒的彼此之间的界面能密度。本篇文章将介绍如何使用相场法模拟金属铜的晶粒的长大过程,并提供完整的MATLAB代码供读者参考。

m0_47037246的博客 1382

exVim完全指南:如何将Vim打造成终极编程IDE环境

你是否曾想过将轻量级的Vim编辑器变成一个功能强大的编程IDE?exVim正是这样一个神奇的项目,它能将Vim打造成终极编程IDE环境,让开发效率提升数倍!🚀 ## 什么是exVim? exVim是一个将Vim转变为专业编程环境的完整解决方案。它引入了项目文件概念(.exvim),通过编辑这个项目文件,exVim插件会自动加载并配置,让你可以根据不同项目应用不同的Vim设置、插件设置,甚至按

gitblog_00256的博客 640

提升YOLOv8检测精度的边界框损失函数Focaler-IoU与InnerFocalerIoU的应用与效果

YOLOv8是一种常用的实时目标检测模型,基于CNN(卷积神经网络)结构并结合了一些改进技术以提高检测精度和速度。YOLOv8的核心在于边界框的预测和分类,而准确的边界框预测是提高模型整体性能的关键因素之一。在传统的IoU损失基础上,Focaler-IoU和InnerFocalerIoU进一步聚焦于边界框的边缘区域,通过调整损失权重,更好地关注难以检测的边界框,提高模型的鲁棒性和准确性。# 计算IoU# 计算Focaler-IoU损失在这里,我们定义了。

博客 1340

How to Manage your vim plugins

How to manage your vim plugin files?I think the most popular 2 ways are pathogen and Vundle.Now I will show you both1.pathogenlinks:https://github.com/tpope/vim-pathogenHow to Install pathogen: so easy

qq_19464321的博客 416

SAP GUI 经典事务在 Fiori Launchpad 中的穿越之道:原理、配置与实践指南

在许多企业的 S/4HANA 或 ECC 系统里,经典 SAP GUI 事务依旧承载着关键业务。如何把这些老将自然地嵌入到 Fiori Launchpad,让用户在统一入口里无缝切换新旧 UI,是项目中经常遇到的主题。本文结合社区博文、官方文档与实际项目经验,系统梳理这一过程的技术原理、配置步骤、性能优化以及常见坑点,并给出贴近中国读者的案例解析与改进建议。阅读完毕,你将不仅知道怎么做,还会理解。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 623

外向交货单批次赋值的标准姿势:用 BAPI_OUTB_DELIVERY_CHANGE 把 LIPS-CHARG 改得稳、改得干净

摘要: 外向交货单批次赋值涉及严格的业务约束,包括批次管理状态、单据状态、拆分规则等,直接更新表 LIPS 不可取。推荐使用 BAPI_OUTB_DELIVERY_CHANGE 接口,通过标准业务逻辑实现批次修改。关键点包括:区分单批次分配与批次拆分场景,正确设置 ITEM_CONTROL 控制结构,并确保单位/数量数据完整。附可直接执行的ABAP代码示例,实现安全可靠的批次更新流程。 (字数:149字)

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 395

exVim与主流IDE对比:为什么Vim+exVim能成为开发者的首选

exVim作为GitHub加速计划中的重要项目,是Vim编辑器的增强套件,旨在将传统Vim提升为现代化的集成开发环境。本文将深入对比exVim与主流IDE的核心差异,解析为什么越来越多开发者选择Vim+exVim组合作为日常开发工具。 ## 🚀 轻量级架构 vs 资源密集型IDE 主流IDE如IntelliJ、VS Code通常需要数百MB甚至GB级别的内存占用,而exVim基于Vim的原生

gitblog_00307的博客 496

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

打造属于自己的Vim IDE

Why Vim? Why not Vim? How to make Vim as an IDE? Recommended plugins Basic plugins Bonus Plugins Troubleshootings Further Reading References Why Vim? 为什么要使用Vim? 让我们先看看其他人怎么说: Vim or Emacs就好

lhl2017的博客 449

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Vim Plugins For Node.js

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34342578的博客 134

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

如何将Neovim变身强大IDE:nvim-ide完整配置指南

nvim-ide是一个为Neovim打造的全功能IDE层,深受VSCode启发,让你的Neovim编辑器瞬间拥有现代IDE的强大功能。本文将带你快速掌握这个神器的安装配置与核心用法,即使是Neovim新手也能轻松上手。 ## 🚀 为什么选择nvim-ide? 传统Vim/Neovim虽然强大,但配置复杂且功能分散。nvim-ide通过集成多种开发必备组件,提供了一站式的IDE解决方案: -

gitblog_00847的博客 359

Vim plugins for Go

验证完全可用,系统OS 10.9只要按照步骤操作,等待你的就是胜利的彼岸。 only a little problem, vim couldn't use link, so I must copy all $GOROOT/misc/vim to ~/.vim. That's all.  Happy New Year! Gold bless u! Vim plug

Jackie_Song的专栏 1112

如何在使用JetBrains IDE时文本编辑更高效?这个IdeaVim好用

在本文中我们将详细介绍IdeaVim 插件、其主要功能以及上手方法,欢迎下载该插件立即体验~

需要界面开发、IDE工具研发中文教程资料的小伙伴,记得私信我~ 1320

linux:vim front end some plugins

Vundle Vundle 是一个 Vim 的插件管理工具,类似于 Bundle 的功能。它可以让你通过配置 .vimrc 文件来安装多个 Vim 插件。 安装 Vundle Vundle 代码 直接下载到你的 ~/.vim/bundle/ 目录下,通过脚本就是: $ mkdir -p ~/.vim/bundle $ git clone https://github.com/VundleV

hunter 287

Go-IDE 项目教程

Go-IDE 项目教程 1. 项目的目录结构及介绍 go-ide/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── init.vim ├── install.sh └── plugins.vim .github/workflows/ci.yml: Gi...

gitblog_00239的博客 532

5分钟快速上手exVim:新手必备的Vim增强配置

exVim是一个将Vim转变为优秀编程环境的项目,它通过引入项目文件概念(.exvim),让你能够为不同项目应用不同的Vim设置、插件配置,甚至按需加载插件,堪称将Vim打造成世界上最好IDE的终极方案! ## 🚀 为什么选择exVim? exVim的核心优势在于其**纯Vim语言实现**的设计理念,确保了轻量高效的运行体验。通过编辑和保存`your_project_name.exvim`文

gitblog_01026的博客 476
上一篇: biabiamiamia百度音乐网批量歌曲下载工具
下一篇: 我给不了你梦中情人,至少还有硬盘女神:hardseed
杨洋
博客等级 码龄16年 55粉丝 7原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值