Eglot 不能补全函数参数吗?

Company 支持补全参数,比如 company-go 效果:

24

但是使用 Eglot + gopls 只能补全到 strings.Repeat,后面括号参数和参数表都没有。看了下 company-go 的代码,貌似是自己实现的:

想问下 Eglot 用户,都没这个功能?还是有的 LSP 实现能做到。

Rust 的 language server + Eglot 可以做到,所以应该不是 Eglot 的问题。

gopls (go1.12.5) 返回的补全项内容如下:

{
  "command": {
    "command": "editor.action.triggerParameterHints",
    "title": ""
  },
  "textEdit": {
    "newText": "Println(${1:a ...interface{\\\\}})",
    "range": {
      "end": {
        "character": 12,
        "line": 6
      },
      "start": {
        "character": 5,
        "line": 6
      }
    }
  },
  "insertTextFormat": 2,
  "filterText": "Println",
  "sortText": "00011",
  "detail": "(n int, err error)",
  "kind": 3,
  "label": "Println(a ...interface{})"
}

其中 “textEdit”.“newTexT” 的内容已经是 snippet 格式了,这是 LSP 协议要求的:

可见 server 端发来就是这样,所以只要在 Emacs 这一端在插入补全项的时候,取的是 “textEdit”.“newTexT” 并且有调用 yas-expand-snippet 就能展开参数。lsp-mode(company-lsp) 是这么做的,不知道 eglot 是怎么处理。


我印象中早期的 LSP 协议是不包含 snippet 的,那时候需要在 Emacs 端解析 “label” 字段,转成 snippet,然后再展开。我写过这些解析函数(参考 company-lsp 自带的 company-lsp--rust-completion-snippet):

'(("go"         . company-lsp--go-completion-snippet)
  ("reason"     . company-lsp--reason-completion-snippet)
  ("python"     . company-lsp--python-completion-snippet)
  ("javascript" . company-lsp--jsts-completion-snippet)
  ("typescript" . company-lsp--jsts-completion-snippet)
  ("dart"       . company-lsp--dart-completion-snippet))

这些函数写完之后大多没怎么使用。加上各个语言的后端进度不一,所以我也不太清除现在还有哪些需要手写 snippet (除了 gopls 已经确定实现了)。

1 个赞

我这里 gopls 结合 Eglot 返回不同,注意我得到是 :insertTextFormat 1,你的 2,根据 Specification 的解释,1 代表 PlainText,2 代表 Snippet。

(_
  :label "Fprintln"
  :kind 3
  :detail "func(w io.Writer, a ...interface{}) (n int, err error)"
  :documentation "Fprintln formats using the default formats for its operands and writes to w."
  :sortText "00002"
  :filterText "Fprintln"
  :insertTextFormat 1
  :textEdit (:range
             (:start
              (:line 20 :character 5)
              :end
              (:line 20 :character 11))
             :newText "Fprintln")
  :command (:title "" :command "editor.action.triggerParameterHints"))

Go、gopls、Eglot 用的都是最新版本。不知道是谁的问题。

哦,原来 Eglot 展开参数的功能依赖 YASnippet,我没安装没开启yas-minor-mode,所以没这个功能。

更新:开启 yas-minor-mode 之后,支持 Snippet 了(insertTextFormat 也提示 2),但是 gopls 返回的 newText 有问题,如 func Repeat(s string, count int) string 只返回

:newText "Repeat(${1:})"

结果就是补全了括号,不只这一个函数,其它的函数都有这个问题,Fprintf 等,都是 (${1:})


注意 Eglot 本身并没有把 YASnippet 列为依赖。此外 Company 本身就能补全参数,而且不依赖 YASnippet。

那你那边 gopls 能不能补全参数名?无论是用什么前端(lsp-mode、eglot 或者其它编辑器),根据 Eglot 给出的 gopls 返回值,貌似是 gopls 的返回有问题 :newText "Fprintf(${1:})"

我分别用 eglot 和 lsp-mode 试了一下,gopls 开启 -logfile=/Users/foo/gopls.log -rpc.trace 直接从 server 端打印信息,结果发现两个客户端请求到的数据竟然真的不一致:

  • eglot
"newText":"Errorf(${1:})"
  • lsp-mode
"newText":"Errorf(${1:format string}, ${2:a ...interface{\\}})"

难道 eglot 实现没跟上,请求的是旧版 LSP 协议?

1 个赞

我向 Eglot 报告了这个问题,上游也修复了。

`eglot-workspace-configuration` 的写法

我注意到 eglot-workspace-configuration 的写法有些奇怪,这是官方给的例子

((python-mode
  . ((eglot-workspace-configuration
      . ((:pyls . (:plugins (:jedi_completion (:include_params t))))))))
 (go-mode
  . ((eglot-workspace-configuration
      . ((:gopls . (:usePlaceholders t)))))))

我起初以为这样才是对的:

((:gopls . ((:usePlaceholders . t))))

但是后来我发现这两个不同的值,经过 JSON 编码之后居然一样:

(json-encode '((:usePlaceholders . t)))
;; => "{\"usePlaceholders\":true}"

(json-encode '(:usePlaceholders t))
;; => "{\"usePlaceholders\":true}"

'((:usePlaceholders . t))
;; => ((:usePlaceholders . t))

'(:usePlaceholders t)
;; => (:usePlaceholders t)

估计这就是两种写法都行的原因。

1 个赞

参数补全的前端感觉还比较原始,补全的时候如果光标点了别的地方,或者按TAB跳过参数以后,placeholder就“降格”成普通字符了。用overlay+text-proterty应该不难实现IDE那种效果。是我用法不对吗?

以开箱即用作为目标的 eglot 为什么不默认设置 usePlaceholders 呢,而是要让用户来配置。

我想如果客户端发送了 workspace/configuration,而 server 端不支持的话,最多返回一个错误,处理起来也不会很麻烦。

现在让用户自己决定是否开启,反而影响了使用体验。首先,用户要去了解开启了有没有用;其次,有可能上一秒还无效,server 更行一下就有效了。


'((:usePlaceholders . t)) 既然是 alist 就不需要冒号了。不过加了也没影响,json-encode 会把冒号去除掉:

我也一直抱怨 placeholder 这个缺点,一旦“失焦”就回不去了。如果位置点太多,我对自己能否一气呵成按顺序填写完、是很没有信心的。

与其吐槽抱怨 不如开个新帖 交流下思路 分享点代码

gopls用起来怎么样? 是在笔记本电脑上用有没有卡顿的情况?

usePlaceholders 是 gopls 专有的设置,默认为 false。估计因为 eglot 不包含任何特定 Server 的代码。讲道理应该是 Server 和 Client 自动沟通好,自动设置这个选项。至于其它的单纯的用户选项,还是应该由用户自己设置。

刚入坑,没啥经验,目前的痛点

  • 没法自动调整 import
  • Diagnostics 体验不理想(比如缺少个返回值,整个函数都给画上红线;有时得 Revert Buffer 来强制刷新)
  • 每一个 Go Package 都开一个 eglos,貌似很容易开很多个,尤其是频繁用 M-.

应该到官方提需求

半年多前使用过一段时间, 问题太多, 放弃了, 看来还得再等等. 以前感觉emacs+lsp挺有希望, 现在有点怀疑了.

TextDocumentClientCapabilitiescompletion.completionItem.snippetSupport。有些人不喜歡placeholder [投票] `foo` `foo(` or `foo()` 函數補全方式 - #8,来自 zhouchongzxc ,language server可能也需要這個選項(有些language client不支援控制snippetSupport)

你很烦耶?能不能少抬几句杠?