Vim 自动补全神器:YouCompleteMe

发布于 2024-10-16 00:45:09 字数 12406 浏览 26 评论 0

第一次听说这个插件还是在偶然的情况下看到别人的博客,听说了这个插件的大名。本来打算在实训期间来完成安装的,无奈网实在 不给力 ,也就拖到了回家的时候。在开始准备工作的时候就了解到这个插件 不是 很容易安装,安装的时候果然名不虚传。(关于这方面的内容,请查看另一篇文章)不过,有付出总有回报,安装之后用上这个插件,真心为这个插件的强大所折服。

那这个插件有何不同?

YouCompleteMe 的特别之处

基于语义补全

总所周知, Vim 是一款文本编辑器。也就是说,其最基础的工作就是 编辑文本 ,而 不管 该文本的内容是什么。在 Vim 被程序员所使用后,其慢慢的被肩负了与 IDE 一样的工作, 文本自动补全 (ie, acpomnicppcompleter ),代码检查( Syntastic )等等工作。

针对 文本自动补全 这个功能来说,主要有两种实现方式。

基于文本

我们常用的 omnicppcompleteracp ,vim 自带的 c-x, c-n 的实现方式就是基于文本。更通俗的说法,其实就是一个字:

其通过文本进行一些正则表达式的匹配,再根据生成的 tags(利用 ctags 生成)来实现自动补全的效果。

基于语义

顾名思义,其是通过分析 源文件 ,经过 语法分析 以后进行补全。由于对源文件进行分析,基于语义的补全可以做到很精确。但是这显然是 vim 所不可能支持的。而且经过这么多年发展,由于语法分析有很高的难度,也一直没有合适的工具出现。直到,由 apple 支持的 clang/llvm 横空出世。 YouCompleteMe 也正是在 clang/llvm 的基础上进行构建的。

整合实现了多种插件

  • clang_complete
  • AutoComplPop
  • Supertab
  • neocomplcache
  • Syntastic (类似功能,仅仅针对 c/c++/obj-c 代码)

支持语言

  • c
  • c++
  • obj-c
  • c#
  • python

对于其他的语言,会调用 vim 设置的 omnifunc 来匹配,因此同样支持 phpruby 等语言。

已知的有

使用效果图

使用感受

  • 和 IDE 一样,自动补全,
  • 根据 include 的文件进行补全
  • 不用再 蹩脚 的生成 tags
  • 补全非常精准,而且速度很快,不会有延迟(以前在大项目上,acp 用起来实在是很卡)
  • 支持类似 tags 的跳转,跳到定义处以及使用处
  • 出错提示很智能,并且用起来真的是如 丝般柔滑 ,不用输入 :w 进行强制检测

安装

说完了那么多好处,就要说到 安装 了。不同于以往其他 vim 插件,YCM 是一款 编译型 的插件。在下载完后,需要 手动编译 后才能使用。对应其他的插件来说,仅仅就是把。vim 的文件丢到相应文件夹下就可以。而这也加大了使用 YCM 的难度。

安装准备

  • 最新版的 Vim(7.3.584+),编译时添加+python 标志(已经安装的可以通过 vim --version 查看)
  • cmake(mac 可以通过 homebrew 安装, brew install cmake ,ubuntu 可以通过 sudo apt-get install cmake
  • 安装 vundle 插件,用于安装管理 vim 的插件

mac 下快速安装

.vimrc 中添加下列代码

Bundle 'Valloric/YouCompleteMe'

保存退出后打开 vim,在正常模式下输入

:BundleInstall

等待 vundle 将 YouCompleteMe 安装完成

而后进行编译安装:

cd ~/.vim/bundle/YouCompleteMe
./install --clang-completer

如果 不需要 c-family 的补全,可以去掉 --clang-completer 。如果 需要c# 的补全,请加上 --omnisharp-completer

正常来说,YCM 会去下载 clang 的包,如果已经有,也可以用系统 --system-libclang

就这样,安装结束。打开 vim,如果没有提示 YCM 未编译,则说明安装已经成功了。

手动编译安装

安装的脚本并不是什么时候都好用,至少对我来说是这样的。安装完之后出现了问题,参考 issue#809

在用 :BundleInstall 安装完成或者使用

git clone --recursive https://github.com/Valloric/YouCompleteMe.git

获取最新的仓库,而后使用 git submodule update --init --recursive 确认仓库的完整性后,开始安装流程。

  1. 下载最新的 clang 二进制文件 YCM 要求 clang 版本 > 3.2,一般来说都是 下载最新的
  2. 安装 python-dev。(ubuntu 下使用 sudo apt-get install python-dev ,mac 下默认提供,否则请安装 command line tools
  3. 编译

    cd ~ 
    mkdir ycm_build 
    cd ycm_build 
    cmake -G "Unix Makefiles"-DPATH_TO_LLVM_ROOT=~/ycm_temp/llvm_root_dir .~/.vim/bundle/YouCompleteMe/cpp 
    make ycm_support_libs

这里需要 注意 的是,~/ycm_temp/llvm_root_dir 中包含的是根据第一步下载的压缩包解压出来的内容(包括 includebin 等等文件)

这样就完成了,开始感受 YCM 提供的完全不逊色于大型 IDE 所提供的自动补全功能吧。

配置

不同于很多 vim 插件,YCM 首先需要编译,另外还需要有配置。在 vim 启动后,YCM 会找寻当前路径以及上层路径的 .ycm_extra_conf.py 。在 ~/.vim/bundle/YouCompleteMe/cpp/ycm/.ycm_extra_conf.py 中提供了默认的模板。也可以参考我的(就在模板上改改而已)。不过这个解决了标准库提示找不到的问题。

一般来说,我会在 ~ 目录下放一个默认的模板,而后再根据不同的项目在当前目录下再拷贝个 .ycm_extra_conf.py

# This file is NOT licensed under the GPLv3, which is the license for the rest
# of YouCompleteMe.
#
# Here's the license text for this file:
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org/>

import os
import ycm_core

# These are the compilation flags that will be used in case there's no
# compilation database set (by default, one is not set).
# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR.
flags = [
'-Wall',
'-Wextra',
#'-Werror',
#'-Wc++98-compat',
'-Wno-long-long',
'-Wno-variadic-macros',
'-fexceptions',
'-stdlib=libc++',
# THIS IS IMPORTANT! Without a "-std=<something>" flag, clang won't know which
# language to use when compiling headers. So it will guess. Badly. So C++
# headers will be compiled as C headers. You don't want that so ALWAYS specify
# a "-std=<something>".
# For a C project, you would set this to something like 'c99' instead of
# 'c++11'.
'-std=c++11',
# ...and the same thing goes for the magic -x option which specifies the
# language that the files to be compiled are written in. This is mostly
# relevant for c++ headers.
# For a C project, you would set this to 'c' instead of 'c++'.
'-x',
'c++',
'-I',
'.',
'-isystem',
'/usr/include',
'-isystem',
'/usr/local/include',
'-isystem',
'/Library/Developer/CommandLineTools/usr/include',
'-isystem',
'/Library/Developer/CommandLineTools/usr/bin/../lib/c++/v1',
]


# Set this to the absolute path to the folder (NOT the file!) containing the
# compile_commands.json file to use that instead of 'flags'. See here for
# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html
#
# Most projects will NOT need to set this to anything; you can just change the
# 'flags' list of compilation flags. Notice that YCM itself uses that approach.
compilation_database_folder = ''

if os.path.exists( 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 ):
  # The compilation_commands.json file generated by CMake does not have entries
  # for header files. So we do our best by asking the db for flags for a
  # corresponding source file, if any. If one exists, the flags for that file
  # should be good enough.
  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:
    # Bear in mind that compilation_info.compiler_flags_ does NOT return a
    # python list, but a "list-like" StringVec object
    compilation_info = GetCompilationInfoForFile( filename )
    if not compilation_info:
      return None

    final_flags = MakeRelativePathsInFlagsAbsolute(
      compilation_info.compiler_flags_,
      compilation_info.compiler_working_dir_ )

    # NOTE: This is just for YouCompleteMe; it's highly likely that your project
    # does NOT need to remove the stdlib flag. DO NOT USE THIS IN YOUR
    # ycm_extra_conf IF YOU'RE NOT 100% SURE YOU NEED IT.
    #try:
    #  final_flags.remove( '-stdlib=libc++' )
    #except ValueError:
    #  pass
  else:
    relative_to = DirectoryOfThisScript()
    final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )

  return {
    'flags': final_flags,
    'do_cache': True
  }

YouCompleteMe 提供的其他功能

YCM 除了提供了基本的补全功能,自动提示错误的功能外,还提供了类似 tags 的功能:

  • 跳转到定义 GoToDefinition
  • 跳转到声明 GoToDeclaration
  • 以及两者的合体 GoToDefinitionElseDeclaration

可以在.vimrc 中配置相应的快捷键。

nnoremap <leader>gl :YcmCompleterGoToDeclaration<CR>
nnoremap <leader>gf :YcmCompleterGoToDefinition<CR>
nnoremap <leader>gg :YcmCompleterGoToDefinitionElseDeclaration<CR>

另外,YCM 也提供了丰富的配置选项,同样在.vimrc 中配置。具体请 参考

let g:ycm_error_symbol ='>>'let g:ycm_warning_symbol ='>*'

同时,YCM 可以打开 location-list 来显示警告和错误的信息 :YcmDiags 。个人关于 ycm 的配置如下:

" for ycm
let g:ycm_error_symbol = '>>'
let g:ycm_warning_symbol = '>*'
nnoremap <leader>gl :YcmCompleter GoToDeclaration<CR>
nnoremap <leader>gf :YcmCompleter GoToDefinition<CR>
nnoremap <leader>gg :YcmCompleter GoToDefinitionElseDeclaration<CR>
nmap <F4> :YcmDiags<CR>

YCM 提供的跳跃功能采用了 vim 的 jumplist ,往前跳和往后跳的快捷键为 Ctrl+O 以及 Ctrl+I

总结

YouCompleteMe 是我用过的最爽的一个自动补全的插件了。之前使用 acp 时,遇到大文件基本上就卡死了,以至于都不怎么敢使用。由于 YCM 使用的时 C/S 结构,部分使用 vim 脚本编写,部分认为原生代码,使得跑起来速度飞快。

抛弃 Vim 自带的坑爹的补全吧, 抛弃ctags 吧, 抛弃cscope 吧,YCM 才是 终极补全神器

在安装过程中,我也遇到了不少的坑。一会会发一篇解决这些坑的文章。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

怎言笑

暂无简介

文章
评论
669 人气
更多

推荐作者

微信用户

文章 0 评论 0

零度℉

文章 0 评论 0

百度③文鱼

文章 0 评论 0

qq_O3Ao6frw

文章 0 评论 0

Wugswg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文