zoukankan      html  css  js  c++  java
  • 使用 Vim 搭建 C/C++ 开发环境

    刚接触 Vim 的同学往往因为无法搭建开发环境而“从入门到放弃”,本文旨在帮助这些同学搭建开发环境,聚焦于最核心的开发需求,忽略换配色调字体之类的细枝末节。如果需要开箱即用的 vim 配置(发行版),可以使用 Spacevim

    本文使用 neovim-nightly,但也适用于 Vim 8.2+,不需要读者有任何 VimL 基础,以 C/C++ 为例,但应该适用于任何语言。

    插件管理

    在 Vim 中,插件只是一些脚本,存放在特定的目录中,运行时将它们所在的目录加入到 runtimepath 中。Vim 8 内置了插件管理功能,但不支持高级的插件管理功能。Vimmers 实现了多个插件管理器,可以自动下载、更新、安装插件,还可以延迟加载、按需加载,提高启动速度。

    上古时期流行手动或使用 Vundle 管理插件,以上两种方式已经落伍了,这里介绍目前比较流行的三个插件管理器:

    • vim-plug:简单易用高效,是目前最流行的插件管理器
    • dein.vim:功能强大,但使用复杂
    • vim-pathogen:另一款流行的插件管理器,没有用过不做评价

    以上三款插件管理器风格各不相同,都有大量用户,功能相当完善,根据自己的喜好选取即可。推荐新手选择 vim-plug,对启动时间特别敏感的同学可以考虑 dein.vim,我的配置在安装 70 余个插件的情况下,启动仅需 60 余秒。

    使用 vim-plug 安装插件只需要在 .vimrc 中写入以下代码:

    call plug#begin('~/.vim/plugged') " 括号里面是插件目录
                                      " 只能在 plug#begin() 和 plug#end() 之间写安装插件的命令
    Plug 'junegunn/vim-easy-align'    " 用户名/插件名,默认从 github 下载安装
    Plug 'https://github.com/junegunn/vim-github-dashboard.git' " 从特定 URL 下载安装
    call plug#end()
    

    使用 dein.vim 安装插件:

    set runtimepath+=~/.vim/plugged/repos/github.com/Shougo/dein.vim " 将 dein.vim 添加到 runtimepath
                                                                     " 注意这是 Ex 命令,路径不要加引号
    if dein#load_state('~/.vim/plugged')        " 参数是插件目录
        call dein#begin(general#plugin_dir)
        call dein#add('~/.vim/plugged/repos/github.com/Shougo/dein.vim')  " 安装 dein.vim
    	call dein#add('junegunn/vim-easy-align') " 用户名/插件名,默认从 github 下载安装
    	call dein#end()
    	call dein#save_state()
    endif
    
    if dein#check_install() " 自动安装未安装的插件
    	call dein#install()
    endif
    

    在安装插件的代码后加上这两行设置:

    filetype plugin indent on
    syntax on
    

    这样可以确保特定于文件类型的插件正常工作。

    代码补全

    最简单的代码补全方式是利用 ctags 生成 tag 文件,补全插件解析 tag 文件进行补全,这种方式有以下两个好处:

    • 最小依赖
    • 高效可靠,适用于任何规模的项目

    基于 tag 的补全不够智能,后来又诞生了 life-changer 级别的补全插件 YouCompleteMe,可以提供 IDE 级别的代码补全。但YouCompleteMe 不是开箱即用的,需要下载依赖并编译,并且耦合度比较大,只支持特定语言(主要是 C++)。

    目前体验补全体验最好的方式是基于 LSPLanguage Server protocal)的方案。LSP 是一套通信协议,遵从 LSP 规范的客户端(各种编辑器/IDE)可以通过众多 LSP 服务端按协议标准进行通信,由客户端完成用户界面相关的事情,由服务端提编程语言相关的:补全,定义引用查找,诊断,帮助文档,重构等服务。架构图如下:

    LSP

    有了 LSP,不同的 IDE/编辑器只需要实现 LSP 客户端,专心改进用户体验,所有补全的工作都交给 LSP 服务器。使用基于 LSP 的方案,用户可以在多种语言间无缝切换,让 Vim 支持所有语言(只要有 LSP 实现),用户只需要做以下两件事:

    • 选择 LSP 客户端
    • 选择 LSP 服务器端

    目前 LSP 客户端有以下几种选择:

    coc.nvim 使用 Typescript 开发,是目前最流行、最强大的 LSP 客户端,已经发展成了一个 Vim 插件平台,存在大量基于 coc.nvim 开发的插件(coc 拓展),推荐大家使用 coc.nvim。

    coc.nvim 依赖于 node.js,但 node.js 似乎已经不再支持 32 位机,因此最新的 coc.nvim 很可能无法在 32 位机上运行,请考虑其他几种方案。

    " <Tab>选择补全候选
    inoremap <silent><expr> <TAB>
                 pumvisible() ? "<C-n>" :
                 <SID>check_back_space() ? "<TAB>" :
                 coc#refresh()
    inoremap <expr><S-TAB> pumvisible() ? "<C-p>" : "<C-h>"
    
    function! s:check_back_space() abort
        let col = col('.') - 1
        return !col || getline('.')[col - 1]  =~# 's'
    endfunction
    " gn 跳转到下一个错误,gN 跳转到上一个错误
    nmap <silent> gN <Plug>(coc-diagnostic-prev)
    nmap <silent> gn <Plug>(coc-diagnostic-next)
    
    " gd 跳转到定义,gs 跳转到引用,gt 跳转到类型定义,gK 显示文档
    nmap <silent> gd <Plug>(coc-definition)
    nmap <silent> gs <Plug>(coc-references)
    nmap <silent> gt <Plug>(coc-type-definition)
    nnoremap <silent> gK :call <SID>show_documentation()<CR>
    function! s:show_documentation()
        if (index(['vim','help'], &filetype) >= 0)
            execute 'h '.expand('<cword>')
        elseif (coc#rpc#ready())
            call CocActionAsync('doHover')
        else
            execute '!' . &keywordprg . " " . expand('<cword>')
        endif
    endfunction
    
    " <Leaderf>rv 改名,<Leaderf>rf 重构
    nmap <leader>rv <Plug>(coc-rename)
    nmap <Leader>rf <Plug>(coc-refactor)
    
    " <C-f> 和 <C-b> 滚动悬浮窗口
    nnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : "<C-f>"
    nnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : "<C-b>"
    inoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? "<c-r>=coc#float#scroll(1)<cr>" : "<Right>"
    inoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? "<c-r>=coc#float#scroll(0)<cr>" : "<Left>"
    vnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : "<C-f>"
    vnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : "<C-b>"
    autocmd User CocJumpPlaceholder call CocActionAsync('showSignatureHelp')
    

    coc.nvim 有自己的配置文件,叫做 coc-settings.json,一般存放在 .vim(neovim 的话在 ~/.config/nvim)。一般我们会在 coc-settings.json 中微调 coc.nvim 和配置 LSP。

    目前功能最强的 C++ LSP 服务器是 ccls,在 coc-settings.json 中配置 ccls:

    {
    	"languageserver": {
            "ccls": {
    			"command": "ccls",
    			"filetypes": ["c", "cc", "cpp", "c++"],
    			"rootPatterns": [".ccls", "compile_commands.json", ".git/", ".root"],
    			"initializationOptions": {
    				"cache": {
    					"directory": ".cache/ccls"
    				},
                    "highlight": {"lsRanges": true }
    			}
    		}
        }
    }
    

    以上配置仅在编辑 C/C++ 文件使用命令ccls启动 LSP 服务器,将项目根目录设置为包含rootpatterns中任意文件的目录,索引文件存放在项目根目录的隐藏目录 .cache/ccls 中。highlight字段指示 ccls 生成高亮信息,基于 LSP 的语法高亮插件会利用 LSP 服务器提供的信息进行精确的语法高亮。

    为了让 C++ LSP 服务器知道以程序以何种方法编译,必须要在项目根目录生成 编译数据库(compile_commands.json)。CMake 内置了对编译数据库的支持,只需要在执行 CMake 时加上-DCMAKE_EXPORT_COMPILE_COMMANDS=1即可;如果使用 Makefile,可以利用 Bear 生成编译数据库,通过bear make来执行 Makefile。Bear 需要执行 Makefile 才能生成编译数据库,如果项目无法正常构建,将不能生成编译数据库,没法使用 LSP 的功能。

    有些 vimmer 还基于 coc.nvim 开发了一些插件,可以在这里查看完整的拓展列表。这里给两点建议:

    • 尽量不要用 Vim 开发环境开发环境插件管理器安装 coc.nvim 拓展

    coc 基于 coc.nvim,使用 Typescript 编写,有些 coc 拓展仅支持使用 coc.nvim 安装。建议在 .vimrc 中定义列表let g:coc_global_extensions,把自己想安装的 coc 拓展写进入,coc.nvim 会在第一次打开文件时安装。

    let g:coc_global_extensions` = ['coc-vimlsp', 'coc-rust-analyzer']
    
    • 优先使用 coc 拓展配置 LSP

    coc-rust-analyzer 之类的 LSP coc 拓展通常利用 coc.nvim 实现了更多 LSP 功能,请优先使用这些拓展,只在没有对应语言的 LSP coc 拓展时手动配置 LSP。

    使用 ccls ,即使是在 Linux 这种规模的代码仓库中也可以流畅地补全代码。code-complete

    错误检查

    目前错误检查有两种方案:

    • 定时调用外部程序实现实时错误检测
    • LSP

    如果使用 coc.nvim,不需要额外配置,开箱即用。coc.nvim 使用 LSP 进行错误检查,不够灵活,无法使用 linter 实时检测代码。

    基于外部程序的方案非常灵活,比如,可以在基础的错误检测之外同时使用 clang-tidy 等工具进行检测。目前这种方案最好的插件是 ale,并且 ale 可以与 coc.nvim 共存,用 ale 做实时错误检查,coc.nvim 做补全。如果没有特殊需求,直接使用 coc.nvim 即可。

    dynamic check

    符号索引

    LSP 已经提供了符号索引的功能,可以方便地跳转到定义/引用。通常 LSP 的功能已经够用,但 LSP 存在以下缺点:

    • 仅支持单一语言,在多语言项目中无法无法工作

    比如可能存在汇编和 C 混合的项目,汇编定义了一个变量在 C 中读写,LSP 无法理解汇编,找不到变量定义的地方。

    • LSP 的符号索引功能有限

    LSP 一般不支持跳转到变量赋值的地方,不支持查找包含该头文件的源文件等。

    我们可以使用静态代码索引工具,克服 LSP 的以上缺点。目前静态代码索引最好的方案是 ctags 和 global(gtags)混合使用,具体的方法参考韦应笑的深度文章Vim 8 中 C/C++ 符号索引:GTags 篇,不再赘述。。

    Tips:

    • 建议同时使用 LSP 和静态索引工具,LSP 支持的功能用 LSP,不支持的功能或没有 LSP 时用静态索引工具,由于实现这个功能需要用 VimL 编程,这里不介绍,有兴趣的话可以参考我的配置

    • ccls 实现了更多的功能,如查看类继承体系,查看调用链,查找类的全部实例等。参考配置如下,部分功能用的比较少,就不创建快捷键了,直接使用命令。

    " This comands are defined for ccls(only supports C/C++)
    command! -nargs=0 Derived :call CocLocations('ccls','$ccls/inheritance',{'derived':v:true})
    command! -nargs=0 Base :call CocLocations('ccls','$ccls/inheritance')
    command! -nargs=0 VarAll :call CocLocations('ccls','$ccls/vars')
    command! -nargs=0 VarLocal :call CocLocations('ccls','$ccls/vars', {'kind': 1})
    command! -nargs=0 VarArg :call CocLocations('ccls','$ccls/vars', {'kind': 4})
    command! -nargs=0 MemberFunction :call CocLocations('ccls','$ccls/member', {'kind': 3})
    command! -nargs=0 MemberType :call CocLocations('ccls','$ccls/member', {'kind': 2})
    command! -nargs=0 MemberVar :call CocLocations('ccls','$ccls/member', {'kind': 4})
    nmap <silent> gc :call CocLocations('ccls','$ccls/call')<CR>
    nmap <silent> gC :call CocLocations('ccls','$ccls/call', {'callee': v:true})<CR>
    

    symbol jump

    任务系统

    在古老的 Vim 工作流中,项目的构建一直是个老大难的问题,要么手动完成,要么自己写简单的脚本完成,VSCode 引入任务系统解决了这个问题,韦易笑大佬的 asyncrun.vimasynctasks.vim 又将 VSCode 的任务系统引入到了 Vim 中,彻底改变了 Vim 的工作流。这充分体现了 Vim 的优势,Vim 用户非常乐于吸收别的编辑器的优点,让 Vim 变得更好。

    asyncrun.vim 让用户可以异步运行 shell 命令,asynctasks 让用户可以将常用的命令写入到配置文件中(~/.vim/tasks.ini 或项目根目录中的 tasks.ini),一次编写多次使用。详细的使用方法请参考插件的中文文档。基本配置如下:

    " 将终端放到 tab 中
    let g:asynctasks_term_pos = 'tab'
    " 设置 quickfix 大小
    let g:asyncrun_open = 10
    " 设置项目根目录标志
    " 实际上,许多插件都使用这种方法定位根目录,因此可以定一个变量 g:rootmarks,
    " 将所有插件的根目录标志都设置为 g:rootmarks
    let g:asyncrun_rootmarks = ['.compile_commands.json', '.ccls', '.git']
    

    以构建 CMake 项目为例,我需要以不同的模式(Debug/Release)执行 CMake,编译项目,可能还会删除二进制目录,利用这两个 life-changer 级别的插件,可以实现一键配置、编译、运行、清理目录。

    [project-build]
    command = cmake --build _builds -- VERBOSE=1
    cwd=$(VIM_ROOT)
    notify=echo
    save=2
    
    [project-run]
    command/linux=_builds/$(VIM_PRONAME)
    command/win32=_builds$(VIM_PRONAME).exe
    cwd=$(VIM_ROOT)
    output=terminal
    
    [project-clean]
    command/linux=rm -rf _builds
    command/win32=rd/s/q _builds
    notify=echo
    cwd=$(VIM_ROOT)
    
    [project-configure]
    command/linux=cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug -S. -B_builds && ln -sf _builds/compile_commands.json .
    command/win32=cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles" -S. -B_builds && copy _buildscompile_commands.json .
    cwd=$(VIM_ROOT)
    notify=echo
    save=2
    

    我的 tasks.ini 中写好常用的任务命令,您可以直接将我的tasks.ini直接复制到自己的配置中。

    在 .vimrc 中映射几个快捷键:

    " 编辑全局 tasks.ini,随时优化工作流
    command! TaskEdit exec 'vsp ' .. general#vimfiles .. '/tasks.ini'
    
    " 由于 <Tab> 的码值和 <C-I> 相同,映射这些快捷键后 <C-I> 会变慢
    nnoremap  <Tab>5 :AsyncTask file-build<cr>
    nnoremap  <Tab>6 :AsyncTask file-run<cr>
    nnoremap  <Tab>7 :AsyncTask project-configure<CR>
    nnoremap  <Tab>8 :AsyncTask project-build<CR>
    nnoremap  <Tab>9 :AsyncTask project-run<CR>
    nnoremap  <Tab>0 :AsyncTask project-clean<CR>
    

    build-project

    再回头看前面提到的错误检测,我们可以将执行 linter 的命令写成一个任务,在代码没有语法错误后调用。

    [clang-tidy]
    command=find . -type d -name '.cache' -prune -o -type d -name '_builds' -prune -o -name '*.cpp' -print | xargs clang-tidy -checks=level2 -p default/compile_commands.json
    cwd=(VIM_ROOT)
    notify=echo
    

    可能您还想统计本项目的代码量,也可以通过 asynctask.vim 完成:

    [cloc]
    command=cloc --exclude-dir=_builds,.* .
    cwd=(VIM_ROOT)
    notify=echo
    

    只有想象力丰富,asyncrun.vim 几乎没有完不成的工作!

    您可能会有这样的疑问,假如我定义了 100 个任务,平时只用其中的少数几个任务,岂不是要经常打开 tasks.ini 查询?这样的困扰根本不存在,Vim 的另一个强大之处就是插件可以配合工作,我们会在后面介绍解决这个问题的办法。

    语法高亮

    基于正则表达式的语法高亮在 C++ 这种语法非常复杂的语言上表现的很差,2021 年可以彻底抛弃掉这种老掉牙的高亮方案了。请使用 nvim-treesitter,它是目前最好的高亮方案(只支持 neovim-nightly),如果用 Vim 的话请使用 vim-lsp-cxx-highlight

    vim-lsp-cxx-highlight 基于 LSP 实现精确的高亮,但存在性能问题,打开文件时有点晃眼,前面 coc-settings.json 中已经配置好了 vim-lsp-cxx-highlight。

    nvim-treesitter 是 neovim 移植的 treesitter(是的,从别的编辑器超过来的),基于语义高亮代码,性能强,容错好。

    highlight

    配置代码如下:

    lua <<EOF
    require'nvim-treesitter.configs'.setup {
      ensure_installed = {'c', 'cpp', 'toml', 'json', 'lua', 'python', 'bash', 'rust'},
      highlight = {
        enable = true,
      }
    }
    -- integrate with rainbow
    require "nvim-treesitter.highlight"
    local hlmap = vim.treesitter.highlighter.hl_map
    hlmap.error = nil
    hlmap["punctuation.delimiter"] = "Delimiter"
    hlmap["punctuation.bracket"] = nil
    EOF
    

    Tip:您可以在 vimrc 中进行判断,在 Vim 中使用 vim-lsp-cxx-highlight,在 neovim-nightly 中使用 nvim-treesitter,可以参考我配置中的 init.vim 和 autoload/tools.vim。

    文件操作

    许多 Vim 外的编辑器用户喜欢使用文件树定位项目文件,但 Vimmer 更喜欢使用模糊查找插件定位文件。尽管如此,文件树也并非一无用处,在浏览自己不熟悉的项目时,文件树插件可以帮助我们了解项目结构。Vim 自带文件树插件,也有许多 vimmer 编写的插件,这里介绍最经典的 NERDtree

    NERDtree 虽然是最经典的文件树插件,但在许多介绍 Vim 的文章中被骂的狗血临头。许多人批评 NERDtree 性能差,在 Linux 这种规模的项目中会直接卡死,但应付中小型项目绰绰有余。

    Leaderf 是国人开发的一款模糊查找插件,性能最强,并且支持许多插件。配置如下:

    let g:Lf_PreviewResult = {
    			 'File': 0,
    			 'Buffer': 0,
    			 'Mru': 0,
    			 'Tag': 1,
    			 'BufTag': 1,
    			 'Function': 1,
    			 'Line': 0,
    			 'Colorscheme': 0,
    			 'Rg': 1,
    			 'Gtags': 1
    			}
    let g:Lf_PreviewInPopup = 1                       " 在 popup 窗口中预览结果
    let g:Lf_PreviewCode = 1                          " 预览代码
    let g:Lf_RootMarkers = ['.root', 'compile_command.json', '.git'] "你的根目录标志
    let g:Lf_WorkingDirectoryMode = 'A'              " 设置 LeaderF 工作目录为项目根目录,如果不在项目中,则为当前目录。
    let g:Lf_ShortcutF = "<Leader>f"
    let g:Lf_ShortcutB = "<Leader>bl"
    nnoremap <silent><Leader>p :LeaderfFunctionAll<CR> " 搜索函数
    nnoremap <silent><Leader>l :LeaderfBufTagAll<CR>   " 搜索缓冲区中的 tag
    nnoremap <silent><Leader>d :LeaderfTag<CR>         " 搜索项目中的 tag
    nnoremap <silent><leader>h :LeaderfHelp<CR>        " 搜索 vim help
    nnoremap <Leader>rg :Leaderf rg<Space>             " 调用 ripgrep 查找字符串
    

    现在,只要按下 <Leader>f ,即使是 Linux 这种级别的项目,也能在一瞬间切换到目标文件。

    LeaderF

    既然 LeaderF 的模糊搜索功能如此强大,能不能让 LeaderF 搜索我们定义的 asynctask.vim 任务?答案当然是可以的!

    function! s:lf_task_source(...)
    	let rows = asynctasks#source(&columns * 48 / 100)
    	let source = []
    	for row in rows
    		let name = row[0]
    		let source += [name . '  ' . row[1] . '  : ' . row[2]]
    	endfor
    	return source
    endfunction
    
    function! s:lf_task_accept(line, arg)
    	let pos = stridx(a:line, '<')
    	if pos < 0
    		return
    	endif
    	let name = strpart(a:line, 0, pos)
    	let name = substitute(name, '^s*(.{-})s*$', '1', '')
    	if name != ''
    		exec "AsyncTask " . name
    	endif
    endfunction
    
    function! s:lf_task_digest(line, mode)
    	let pos = stridx(a:line, '<')
    	if pos < 0
    		return [a:line, 0]
    	endif
    	let name = strpart(a:line, 0, pos)
    	return [name, 0]
    endfunction
    
    function! s:lf_win_init(...)
    	setlocal nonumber
    	setlocal nowrap
    endfunction
    
    let g:Lf_Extensions = get(g:, 'Lf_Extensions', {})
    let g:Lf_Extensions.task = {
    			 'source': string(function('s:lf_task_source'))[10:-3],
    			 'accept': string(function('s:lf_task_accept'))[10:-3],
    			 'get_digest': string(function('s:lf_task_digest'))[10:-3],
    			 'highlights_def': {
    			     'Lf_hl_funcScope': '^S+',
    			     'Lf_hl_funcDirname': '^S+s*zs<.*>zes*:',
    			 },
    			 }
    nnoremap <silent><leader>T :Leaderf task<CR> "<leader>T 模糊搜索任务
    

    Tips:使用 nerdfontvim-devicons 可以在 LeaderF、NERDtree 等插件中显示漂亮的文件图标。

    调试

    调试一直是 Vim 的弱点,最近 DAPDebug Adapter Protocol)的提出带来了一些改变。YouCompleteMe 的主要开发者 puremourning 创建了 vimspector,这是目前最强的 Vim 调试插件,仍处于开发阶段,您如果有兴趣的话可以参考我的博客 Vim 最强调试插件:vimspector

    Git

    Tpope 的 vim-fugitive 让 Git 工作流在 Vim 中顺畅无比,使用 vim-gitgutter 在侧边栏展示 Git 状态。

    command! -bang -nargs=* -complete=file Make AsyncRun -program=make @ <args> " 异步 git push
    " git-gutter
    let g:gitgutter_map_keys = 0
    nmap ghp <Plug>(GitGutterPreviewHunk) " 预览修改(diff)
    nmap ghs <Plug>(GitGutterStageHunk)   " 暂存修改
    nmap ghu <Plug>(GitGutterUndoHunk)    " 丢弃修改
    nmap [c <Plug>(GitGutterPrevHunk)     " 上一处修改
    nmap ]c <Plug>(GitGutterNextHunk)     " 下一处修改
    

    Tip:vim-fugitve 还可以用来处理 git conflict,这里不介绍。

    格式化

    注释请使用 vim-format,它易于拓展,可以支持所有文件类型。vim-format 会根据文件类型执行对应的格式化命令,C/C++ 默认使用 clang-format,所以您只需要将 .clang-format 放到项目根目录即可。

    定义一个快捷键快速格式化代码。

    nnoremap <Leader>bf :Autoformat<CR>
    

    Tip:您还可以利用自动命令在写入文件时自动格式化,利用替换命令在写入文件时自动清除行尾空白。

    注释

    目前最流行的注释/反注释是 nerdcommentervim-commentary。nerdcommenter 相比于 vim-commentary 功能更强,拓展性更好,因此推荐使用 nerdcommenter。

    " Add spaces after comment delimiters by default
    let g:NERDSpaceDelims = 1
    
    " Align line-wise comment delimiters both sides
    let g:NERDDefaultAlign = 'both'
    
    " Allow commenting and inverting empty lines (useful when commenting a region)
    let g:NERDCommentEmptyLines = 1
    
    " Enable trimming of trailing whitespace when uncommenting
    let g:NERDTrimTrailingWhitespace = 1
    
    " Enable NERDCommenterToggle to check all selected lines is commented or not
    let g:NERDToggleCheckAllLines = 1
    
    " Usefull when comment argument
    let g:NERDAllowAnyVisualDelims = 0
    let g:NERDAltDelims_asm = 1
    

    nerdcommenter 默认的快捷键请参考文档。请不要再蜗牛一样地用:help命令查看文档,用<Leader>h模糊搜索!

    Tip:您会发现注释/反注释后光标仍停留在原来的位置,如果您希望光标停留在可视区域结尾,可以添加上以下代码:

    let g:NERDCreateDefaultMappings = 0
    
    " It is impossible to determine execute mode in hooks. Thus, I wrap raw NERDComment()
    " to pass mode infomation to hooks and create mappings manually.
    "
    " NERDCommenterAltDelims is not wrapped and it would execute hooks. So I
    " delete variable g:NERDCommenter_mode in NERDCommenter_after() to disable
    " hooks executed by NERDCommenterAltDelims
    function! s:NERDCommenter_wrapper(mode, type) range
        let g:NERDCommenter_mode = a:mode
        execute a:firstline .. ','  .. a:lastline 'call NERDComment(' .. string(a:mode) .. ',' .. string(a:type) .. ')'
    endfunction
    
    " modes: a list of mode(n - normal, x - visual)
    function! s:create_commenter_mapping(modes, map, type)
        for l:mode in split(a:modes, 'zs')
            execute l:mode .. 'noremap <silent> <Leader>' .. a:map .. ' :call <SID>NERDCommenter_wrapper(' .. string(l:mode) .. ', ' .. string(a:type) .. ')<CR>'
        endfor
    endfunction
    
    function! CreateCommenterMappings()
        " All mappings are equal to standard NERDCommenter mappings.
        call s:create_commenter_mapping('nx', 'cc', 'Comment')
        call s:create_commenter_mapping('nx', 'cu', 'Uncomment')
        call s:create_commenter_mapping('n', 'cA', 'Append')
        call s:create_commenter_mapping('nx', 'c<space>', 'Toggle')
        call s:create_commenter_mapping('nx', 'cm', 'Minimal')
        call s:create_commenter_mapping('nx', 'cn', 'Nested')
        call s:create_commenter_mapping('n', 'c$',  'ToEOL')
        call s:create_commenter_mapping('nx', 'ci', 'Invert')
        call s:create_commenter_mapping('nx', 'cs', 'Sexy')
        call s:create_commenter_mapping('nx', 'cy', 'Yank')
        call s:create_commenter_mapping('n', 'cA',  'Append')
        call s:create_commenter_mapping('nx', 'cl', 'AlignLeft')
        call s:create_commenter_mapping('nx', 'cb', 'AlignBoth')
        call s:create_commenter_mapping('nx', 'cu', 'Uncomment')
        call s:create_commenter_mapping('n', 'ca',  'AltDelims')
        nmap <leader>ca <plug>NERDCommenterAltDelims
    endfunction
    
    " NERDCommenter hooks
    function! NERDCommenter_before()
        let g:nerdcommmenter_visual_flag = v:false
        if get(g:, 'NERDCommenter_mode', '') =~# '[vsx]'    " executed in visual mode
            let l:marklist = getmarklist('%')
            for l:mark in l:marklist
                if l:mark['mark'] =~ "'>"
                    let g:nerdcommmenter_cursor = l:mark.pos
                    let g:nerdcommmenter_visual_flag = v:true
                    break
                endif
            endfor
        endif
    endfunction
    
    function! NERDCommenter_after()
        if g:nerdcommmenter_visual_flag
            call setpos('.', g:nerdcommmenter_cursor)
        endif
        let g:nerdcommmenter_visual_flag = v:false
        unlet! g:NERDCommenter_mode
    endfunction
    
    

    nerdcommenter

    结语

    本文介绍了用 Vim 搭建开发环境的思路,但 Vim 的魅力不在于“千篇一律”,而在于“各不相同”,每个 Vimmer 都有自己的 Vim,根据自己的习惯不断改进工作流。总之,希望本文可以帮助大家走进 Vim 的世界。

  • 相关阅读:
    android apk 反编译
    js 读 xml 非ie 可以支持 chrome 浏览器 与 android webView
    php+mySQl 环境搭建
    Activity 生命周期
    div 隐藏 显示 占空间 不占空间
    android 异步加载
    android 文件操作
    透明 GridView 背景透明
    eclipse 版本理解
    WebKit 上的JS直接使用Java Bean
  • 原文地址:https://www.cnblogs.com/kongj/p/14391151.html
Copyright © 2011-2022 走看看