vscode_intellisence

简介

最近C++代码越写越多,也有不少同学问我这种炫酷的C++代码提示是怎么在Mac上实现的。2022年了现在我日常会使用VSCode来作为主力编辑器(包括断点调试和提交Git Commits),NVim则用于终端编辑代码(例如远程连接到CERN的服务器)。我会以MacOS系统为例配置这两种方案,大家根据自己需求,选择其中一种即可,个人推荐VSCode.

Sec. 1 VSCode

首先是要安装 C/C++ 拓展,启动VSCode以后在拓展里搜索安装即可。然后让我们找个(或者新建)一个空的文件夹,例如我选择了 ~/Code/clang/playground,使用VSCode打开它。接着新建 .vscode 文件夹,在该文件夹下新建文件 c_cpp_properties.json 文件:

{
    "configurations": [
        {
            "name": "Mac",
            "includePath": [
                "${default}",
                "${workspaceFolder}"
                "/usr/local/opt/root/include/root", // change path to your root include path
            ],
            "compilerPath": "/usr/bin/g++",
            "cppStandard": "gnu++17",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}

其中 includePathcompilerPath 是值得注意的。

  1. 当你引用了一些第三方库或者头文件,你都要写到 “includePath” 中,例如在上面的例子我添加了root库的inlude路径(如果你不知道它的路径,可以使用 root-config –incdir 找到)。
  2. 你可能需要更改 “compilerPath” ,你可以在终端里输入 which g++ 来看到g++的路径。

现在让我们来测试一下它是否能正常工作,在 playground 文件夹下新建一个 main.cc 文件,你应该能正常使用代码补全与智能提示了(如果不行请返回到上一步,也欢迎通过邮箱联系我!),内容如下:

#include <TFile.h>
#include <TCanvas.h>
#include <TGraph.h>
#include <TAxis.h>

int main() {
    int n = 20;
    // create canvas and graph
    auto c1 = new TCanvas("c1","A Simple Graph Example",200,10,700,500);
    auto gr = new TGraph(n);
    // set mock sin data
    for(int i=0; i<n ;i++)
        gr->SetPoint(i, i*0.1, 10*sin(i*0.1+0.2));
    // set graph styles
    gr->SetLineColor(2);
    gr->SetLineWidth(4);
    gr->SetMarkerColor(4);
    gr->SetMarkerStyle(21);
    gr->SetTitle("a simple graph");
    gr->GetXaxis()->SetTitle("X title");
    gr->GetYaxis()->SetTitle("Y title");
    gr->Draw("ACP");

    // write canvas as a pdf file
    c1->SaveAs("curve.pdf");
}

编写完成以后,使用g++来编译并且运行它,编译过的程序相比使用root运行能够极大地提升运行速度(这个结论是已经实验室的同学验证)

# compile main program, you may need change to -std=c++14
# based on your platform and gcc version
g++ -std=c++17 main.cc -o main.o `root-config --cflags --libs`
# run it!
./main.o

至此我们已经把依赖root的代码编写环境搭建完毕了,绝大部分的C++工作与粒子物理实验分析都可以通过这种方式来完成。当然你也完全可以自己在这个基础上集成更多的第三方库(e.g. Pythia8, FastJet, etc.) 以实现更复杂的功能。

下面的内容都是可选项:当遇到很复杂的算法和完全摸不到头脑的BUG时,可以考虑使用断点调试。你需要在 .vscode 目录下额外新建两个文件,分别是 launch.jsontasks.json

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "g++ - Build and debug active file",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}/${fileBasenameNoExtension}.o",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "lldb", // or gdb based on your platform
            "preLaunchTask": "C/C++: g++ build active file"
        }
    ]
}
{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++ build active file",
            "command": "/usr/bin/g++",
            "args": [
                "-g",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}.o",
                "-std=c++17", // -std=c++14 based on your platform
                "-I/usr/local/opt/root/include/root", // change path to your root include path
                "-L/usr/local/opt/root/lib/root", // change path to your root lib path
                "-lCore",
                "-lImt",
                "-lRIO",
                "-lNet",
                "-lHist",
                "-lGraf",
                "-lGpad",
                "-lROOTVecOps",
                "-lTree",
                "-lRint",
                "-lPostscript",
                "-lMatrix",
                "-lPhysics",
                "-lMathCore",
                "-lThread",
                "-pthread",
                "-lm",
                "-ldl",
                "-rdynamic",
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }
    ],
    "version": "2.0.0"
}

如果你不需要使用root库,那可以把args里从 "-I/usr/local/opt/root/include/root/" 开始后面的几行删去。现在打开你的 main.cc 文件,尝试在行数上打个断点,点击VSCode左侧面板的运行按钮,就可以直接运行并且Debug代码啦!甚至你可以设置条件断点,例如:

vscode_debug

最终的的目录结构应该是这个样子,它们的作用分别是:

playground
│
└─── .vscode
│      c_cpp_properties.json
│      lauhch.json
│      tasks.json
│
└─── main.cc
└─── main.o
└─── curve.pdf
  • c_cpp_properties.json 这个文件主要是用来配置代码补全与智能提示的,一般而言你只需要配置 include path 即可。
  • tasks.json 这个文件主要是当你点击VSCode左侧的运行按钮时,其实也是需要编译的,它掌管着编译所需要提供的参数路径等。
    1. 第一是库的 include path,e.g. -I/path_of_include
    2. 第二是库的 lib path,e.g. -L/path_of_lib
    3. 第三是告诉 linker,e.g. -lpythia8

关于如何正确编译 C++ 程序(当你开始好奇编译的参数到底是怎么起作用的),这里有一份推荐阅读的很好的资料 Things to remember when compiling/linking C/C++ software

Sec. 2 NVim

vim_intellisence

Good news to Vimer! 这部分仅推荐给Vim爱好者使用(可选内容)。经过我的测试发现,NVim + VimPlug + coc.nvim + clangd 能够获得极好的代码编辑体验!流畅、轻量、稳定,结合 TMux 可以获得本地/服务器上相同的编辑体验。

这里只说一下MacOS上的安装,Linux用户推荐使用包管理器(如果没有权限就appimage版本然后创建软连接),具体请阅读各部分Github Readme上给出的安装方法(注意下面的命令对于linux并不通用)。

# install nvim
brew install nvim
# install vim-plug
sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \
       https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
# install nodejs, required by coc.nvim
brew install node
# install yarn, required by coc.nvim
brew install yarn

接着就是使用 vim-plug 来安装 coc.nvim,注意NVim的配置文件是在 ~/.config/nvim/init.vim 中的。以下是基础的配置,你可以作为参考,我还安装了 ctrlp 快速搜索文件插件,与 gruvbox 配色。

call plug#begin('~/.local/share/nvim/plugged')
Plug 'kien/ctrlp.vim'
Plug 'morhetz/gruvbox'
Plug 'neoclide/coc.nvim', {'branch': 'release'}
call plug#end()

set encoding=utf-8

syntax enable
set background=dark
colorscheme gruvbox

set nu
set cursorline
set hlsearch 
set mouse=v

" default indent
set tabstop=2
set softtabstop=2
set shiftwidth=2
set fileformat=unix
set autoindent
set expandtab

" indent for python
au BufNewFile,BufRead *.py
  \ set tabstop=4 |
  \ set softtabstop=4 |
  \ set shiftwidth=4 |
  \ set textwidth=79 |
  \ set expandtab |
  \ set autoindent |
  \ set fileformat=unix |

" ignore files and directories
set wildignore+=*.so,*.swp,*.pyc,*.zip,*.tar.gz,*.o,*.out,*.lo,*.la
set wildignore+=*/tmp/*,*/bin/*,*/obj/*,*/packages/*,*/node_modules/*,*/.git/*,*/build/*
set wildignore+=*/logs/*,*/data/*

" coc.nvim
nmap <silent> gd <Plug>(coc-definition)
nmap <silent> gy <Plug>(coc-type-definition)
nmap <silent> gi <Plug>(coc-implementation)
nmap <silent> gr <Plug>(coc-references)
" Using tab completion
inoremap <silent><expr> <tab> pumvisible() ? coc#_select_confirm() : "\<C-g>u\<TAB>"
inoremap <silent><expr> <cr> "\<c-g>u\<CR>"
" Symbol renaming.
nmap <leader>rn <Plug>(coc-rename)
" Formatting selected code.
xmap <leader>f  <Plug>(coc-format-selected)
nmap <leader>f  <Plug>(coc-format-selected)

保存完毕以后,在终端打开nvim(输入nvim回车即可,你会看到一些错误信息,别在意,插件安装完毕以后就消失了),输入 :PlugInstall 开始安装这些插件。

你可以在 Language-Server 中找到支持代码补全和提示的语言,这里我们只考虑C++。再次打开nvim,输入 :CocConfig,将以下内容保存好:

{
  "languageserver": {
    "clangd": {
      "command": "clangd",
      "rootPatterns": ["compile_flags.txt", "compile_commands.json"],
      "filetypes": ["c", "cc", "cpp", "c++", "objc", "objcpp"]
    }
  }
}

完成所有的安装和配置以后,让我们回到之前的 ~/Code/clang/playground 文件夹中(参看上一小节)。新建一个 compile_flags.txt 文件,它包括了需要引用的头文件和C++版本:

-std=c++17
-I/usr/local/opt/root/include/root

你现在可以尝试用nvim来编辑文件,例如上一小节中的 main.cc 文件,可以有很cooooool的补全和提示,编译与运行则与VSCode相同。

如果你也需要在CERN的LXPLUS上工作,以下是我抽取到的 compile_flags.txt,对应 CMS_Release_10_6_26 (若版本不同可以使用 scram clean && scram b –verbose 来查看并抽取所有的编译需要的头文件):

-xc++
-std=c++1z
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/cms/cmssw/CMSSW_10_6_26/src
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/lcg/root/6.14.09-pafccj6/include

-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/cms/coral/CORAL_2_3_21-pafccj7/include/LCG
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/lcg/root/6.14.09-pafccj6/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/pcre/8.37-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/boost/1.67.0-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/bz2lib/1.0.6-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/clhep/2.4.0.0-pafccj2/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/gsl/2.2.1-pafccj2/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/hepmc/2.06.07-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/libuuid/2.22.2-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/openssl/1.0.2d-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/sigcpp/2.6.2-pafccj/include/sigc++-2.0
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/tbb/2019_U3-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/cms/vdt/0.4.0-pafccj2/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/xerces-c/3.1.3-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/xz/5.2.2-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/zlib-x86_64/1.2.11-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/eigen/e4c107b451c52c9ab2d7b7fa4194ee35332916ec-pafccj2/include/eigen3
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/md5/1.0.0-pafccj/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/OpenBLAS/0.3.5-nmpfii2/include
-I/cvmfs/cms.cern.ch/slc7_amd64_gcc700/external/tinyxml2/6.2.0-pafccj2/include