company-lsp 终于来了

主要是怕打错

1 个赞

我感觉最有可能的是记不住变量和函数名。。。。

即使错,也要错成一样,这就是自动补全的好。自动补全不能用的地方,我尽量复制粘贴。

2 个赞

以为你没回复我呢,抱歉这么晚才看到,过节时没碰代码。

补全是有的,我设置的company-auto-complete-charssymbolpunctuation两个,symbol可以自动出来,但直接在对象后按.出不来,需要手动在.后面执行M-x -ls才出得来。

顺便,lsp-enable-comletion-at-point是默认值,在customize group中显示是oncompany-capf也在company-backends中。

可以列出你的配置代码和用来测试补全的c++代码吗?最好列一下M-x company-diag的结果。

回头看了一下你原来的问题:

  1. company-auto-complete-char不是用来实现你想要的功能的。company-lsp默认应该支持cquery的.自动触发补全。

  2. 要实现补全参数的效果,company-lsp需要被提前加载。可以使用如下配置:

    (add-hook 'c++-mode-hook 
       (lambda ()
          (require 'company-lsp)
          (lsp-cquery-enable)))
    
  3. lsp-enable-comletion-at-pointcompany-capf对于company-lsp没有任何作用

c++代码:

struct foo_foo {
    int bar_bar;
};

void foo()
{
    foo_foo foo000;
    foo000.  //光标停留此处
}

.cquery文件:

# Driver
clang++

# Language
-xc++

M-x company-diag内容:

Emacs 25.3.1 (x86_64-pc-linux-gnu) of 2018-02-09 on bisson
Company 0.9.4

company-backends: (company-bbdb company-nxml company-css company-eclim company-semantic company-xcode company-cmake company-capf company-files
              (company-dabbrev-code company-gtags company-etags company-keywords)
              company-oddmuse company-dabbrev company-lsp)

Used backend: company-capf
Major mode: c++-mode
Prefix: ""
Completions:
  #("operator=(${1:const foo_foo &})$0" 0 1 (lsp-completion-item #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("label" "operator=" "kind" 2 "detail" "foo_foo & operator=(const foo_foo &)" "documentation" "" "sortText" "..........1" "insertText" "operator=(${1:const foo_foo &})$0" "filterText" "operator=" "insertTextFormat" 2)) face (completions-first-difference)) 1 33 (lsp-completion-item #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("label" "operator=" "kind" 2 "detail" "foo_foo & operator=(const foo_foo &)" "documentation" "" "sortText" "..........1" "insertText" "operator=(${1:const foo_foo &})$0" "filterText" "operator=" "insertTextFormat" 2)))) " foo_foo & operator=(const foo_foo &) (Method)"
  #("~foo_foo()" 0 1 (lsp-completion-item #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("label" "~foo_foo" "kind" 4 "detail" "void ~foo_foo()" "documentation" "" "sortText" "..........0" "insertText" "~foo_foo()" "filterText" "~foo_foo" "insertTextFormat" 1)) face (completions-first-difference)) 1 10 (lsp-completion-item #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("label" "~foo_foo" "kind" 4 "detail" "void ~foo_foo()" "documentation" "" "sortText" "..........0" "insertText" "~foo_foo()" "filterText" "~foo_foo" "insertTextFormat" 1)))) " void ~foo_foo() (Constructor)"
  #("foo_foo::" 0 1 (lsp-completion-item #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("label" "foo_foo" "kind" 22 "detail" "foo_foo::" "documentation" "" "sortText" "........../" "insertText" "foo_foo::" "filterText" "foo_foo" "insertTextFormat" 1)) face (completions-first-difference)) 1 9 (lsp-completion-item #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("label" "foo_foo" "kind" 22 "detail" "foo_foo::" "documentation" "" "sortText" "........../" "insertText" "foo_foo::" "filterText" "foo_foo" "insertTextFormat" 1)))) " foo_foo::"
  #("bar_bar" 0 1 (lsp-completion-item #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("label" "bar_bar" "kind" 5 "detail" "int bar_bar" "documentation" "" "sortText" "..........." "insertText" "bar_bar" "filterText" "bar_bar" "insertTextFormat" 1)) face (completions-first-difference)) 1 7 (lsp-completion-item #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("label" "bar_bar" "kind" 5 "detail" "int bar_bar" "documentation" "" "sortText" "..........." "insertText" "bar_bar" "filterText" "bar_bar" "insertTextFormat" 1)))) " int bar_bar (Field)"

.emacs中相关配置:

(require 'cquery)
(setq cquery-executable "/usr/bin/cquery")
(defun my//enable-cquery-if-compile-commands-json ()
  (when
      (or (locate-dominating-file default-directory "compile_commands.json")
          (locate-dominating-file default-directory ".cquery"))
    (lsp-cquery-enable)))
(add-hook 'c-mode-common-hook
          (lambda ()
            (setq tab-width 4)
            (smart-tabs-mode 1)
            (my//enable-cquery-if-compile-commands-json)))

(custom-set-variables
 '(company-auto-complete t)
 '(company-auto-complete-chars (quote (95 46)))
 '(company-backends
   (quote
    (company-bbdb company-nxml company-css company-eclim company-semantic company-xcode company-cmake company-capf company-files
                  (company-dabbrev-code company-gtags company-etags company-keywords)
                  company-oddmuse company-dabbrev company-lsp)))
 '(company-lsp-async t)
 '(company-lsp-cache-candidates nil)
 '(company-lsp-enable-recompletion t)
 '(lsp-enable-flycheck nil)
 '(lsp-enable-indentation nil)
 '(lsp-highlight-symbol-at-point nil)
 '(package-selected-packages
   (quote
    (company-lsp cquery lsp-mode smart-tabs-mode fill-column-indicator flycheck-clang-analyzer flycheck ## clang-format cl-lib company geiser smart-mode-line smartparens))))

顺便,我看上面有人设置了自动不补全参数,我没看到在哪里设置啊。。。

1 个赞

你的company-backends里没有使用company-lsp。建议你把它加到第一位。并且在(lsp-cquery-enable)之前加上(require 'company-lsp)

1 个赞

我之前用 (defun my//enable-cquery-if-compile-commands-json () 是防止(user-error "cannot find project root") 现在打算用

(defun cquery//enable ()
  (ignore-errors (lsp-cquery-enable)))

我倒是觉得这样挺好,这样不是所有项目都会索引吧,想索引自己加个cquery识别的文件就行了。

lsp-python emacs-cquery 都有项目根目录识别问题。提了一个讨论issue [Discussion] project root detection logic · Issue #293 · emacs-lsp/lsp-mode · GitHub

转贴一下:

对于 lsp-python

(lsp-define-stdio-client lsp-python "python"
			 (lsp-make-traverser #'(lambda (dir)
						 (directory-files
						  dir
						  nil
						  "\\(__init__\\|setup\\)\\.py")))
			 '("pyls"))

__init__.py判断根很不好,因为子目录也可能有__init__.py

倒是可以像projectile那样,用.git作为project root

我认为没有一个万全的方法,总有例外的情况。即使以 .git 作为判断依据,那些不进入版本控制的三方资源有可能直接一个 git repo 放在工程目录底下:

find . -type d -name ".git"
./.git
./vendor/phpunit/phpunit/.git
...

是不是可以考虑增加一个手动的方法(作为补充),例如 (lsp-open-project-root DIR),明确把 DIR 作为 root,不管它上层是什么情况。


想了一下,这样也有问题:如果同时指定了 foo 和 foo/bar 为 project root,然后打开 foo/bar/qux.c 算哪个项目的?这种情况只能启动多个 Emacs 实例了,一个实例一个 project。

我現在用了一個比較噁心的辦法

(cl-defun cquery--get-root ()
  "Return the root directory of a cquery project."
  (when cquery-project-root-function
    (-when-let (root (funcall cquery-project-root-function))
      (cl-return-from cquery--get-root root)))
  (cl-loop for root in cquery-project-roots do
           (when (string-prefix-p (expand-file-name root) buffer-file-name)
             (cl-return-from cquery--get-root root)))
  (or
   (and (require 'projectile nil t) (ignore-errors (projectile-project-root)))
   (expand-file-name (or (locate-dominating-file default-directory "compile_commands.json")
                         (locate-dominating-file default-directory ".cquery")
                         (user-error "Could not find cquery project root")))))

(defcustom cquery-project-root-function
  nil
  "A function used to find the project root.

The following methods are applied in order to get the project root.
* `cquery-project-root-function'
* `cquery-project-roots'
* projectile
* `.cquery' or `compile_commands.json'
"
  :type 'function
  :group 'cquery)

(defcustom cquery-project-roots
  nil
  "A list of project roots that will be matched against the source filename first
to get the project root, before consulting `projectile' or `project'.

This is useful when your project has subprojects. Otherwise `projectile' and
`project' may think the file resides in a subproject and thus the file
does not belong to the current workspace.
"
  :type '(repeat directory)
  :group 'cquery)

如果是我,我就添加一个用户命令,让用户手动设置当前buffer对应的文件属于哪个project,检测不靠谱的时候,不如让用户自己选择

file local variable 这种吧,原来用来判断的还是可以留着

现在 lsp-mode 的问题是一旦一个文件设置了对应的 workspace ,很难平滑退出……切换project不行的

你显然没认真看前面各位的回帖。

README.md__init__.py 还不靠谱。以 cquery 为例,third_party 下的每个目录都是一个完整的 git 项目,都有 README.md

⋊> find ~/repos/c-c++/cquery -name "README.md"
~/repos/c-c++/cquery/README.md
~/repos/c-c++/cquery/third_party/doctest/doc/html_generated/strapdown.js/README.md
~/repos/c-c++/cquery/third_party/doctest/README.md
~/repos/c-c++/cquery/third_party/loguru/README.md
~/repos/c-c++/cquery/third_party/msgpack-c/README.md
~/repos/c-c++/cquery/third_party/rapidjson/bin/jsonschema/README.md
~/repos/c-c++/cquery/third_party/rapidjson/contrib/natvis/README.md
~/repos/c-c++/cquery/third_party/sparsepp/README.md

你也许会说,向上递归查找,找到最顶层 README.md。问题是,谁规定了项目目录之外不能有 README.md,谁保证只有项目顶层有 README.md?就我这个例子,~/repos 是用来搜集代码的,我就可能放一个 ~/repos/README.md~/repos/c-c++/README.md 作为目录摘要。

.git/.svn/... 这些还相对靠谱一些。但还是有例外的情况,比如我就想窝在 ~/repos/c-c++/cquery/third_party/msgpack-c 这个子项目里面怎么办,所以还得有个手工设定的方法。

嗯,我现在自己的配置用

    (setq cquery-project-roots '("~/Dev/llvm-project" "~/Dev/llvm"))

因为不希望 ~/Dev/llvm/{projects,tools}/*/.git 子项目被识别为单独的LSP workspace

projectile 有个

    (require 'projectile)
    (add-to-list 'projectile-globally-ignored-directories ".cquery_cached_index")

(这些杂事如果要把cquery加到 spacemacs +tools/cquery or +lang/c-c++ 都需要清理的吧…)我elisp很弱,期待别人把这个捡起来… Add +tools/cquery layer by MaskRay · Pull Request #10236 · syl20bnr/spacemacs · GitHub

为什么会由多个项目根目录? 怎么搞得这么复杂? 是为了同时打开多个项目? 还是一个项目分散在多个目录?

这里讨论的是当存在 sub-project 时,不希望 sub-project 下的文件在 lsp-mode workspace 角度被视为 sub-project 的一部分。

因为 language server 检索整个项目(包含sub-project),打开的文件也要和根 project 对应才能用上 lsp信息

一个项目分散在多个目录

cquery 也是能处理的。但其中一个目录要配置成主要的 (lsp-mode.el 中的 :rootUri),并且下面放 .cquerycompile_commands.json 包含其他根目录所有文件的编译命令。