Company 支持补全参数,比如 company-go
效果:
但是使用 Eglot + gopls 只能补全到 strings.Repeat
,后面括号参数和参数表都没有。看了下 company-go
的代码,貌似是自己实现的:
想问下 Eglot 用户,都没这个功能?还是有的 LSP 实现能做到。
Company 支持补全参数,比如 company-go
效果:
但是使用 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 已经确定实现了)。
我这里 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 端打印信息,结果发现两个客户端请求到的数据竟然真的不一致:
"newText":"Errorf(${1:})"
"newText":"Errorf(${1:format string}, ${2:a ...interface{\\}})"
难道 eglot 实现没跟上,请求的是旧版 LSP 协议?
我向 Eglot 报告了这个问题,上游也修复了。
我注意到 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)
估计这就是两种写法都行的原因。
参数补全的前端感觉还比较原始,补全的时候如果光标点了别的地方,或者按TAB跳过参数以后,placeholder就“降格”成普通字符了。用overlay+text-proterty应该不难实现IDE那种效果。是我用法不对吗?
以开箱即用作为目标的 eglot 为什么不默认设置 usePlaceholders 呢,而是要让用户来配置。
我想如果客户端发送了 workspace/configuration
,而 server 端不支持的话,最多返回一个错误,处理起来也不会很麻烦。
现在让用户自己决定是否开启,反而影响了使用体验。首先,用户要去了解开启了有没有用;其次,有可能上一秒还无效,server 更行一下就有效了。
'((:usePlaceholders . t))
既然是 alist 就不需要冒号了。不过加了也没影响,json-encode
会把冒号去除掉:
gopls用起来怎么样? 是在笔记本电脑上用有没有卡顿的情况?
usePlaceholders 是 gopls 专有的设置,默认为 false。估计因为 eglot 不包含任何特定 Server 的代码。讲道理应该是 Server 和 Client 自动沟通好,自动设置这个选项。至于其它的单纯的用户选项,还是应该由用户自己设置。
刚入坑,没啥经验,目前的痛点
import
M-.
。应该到官方提需求
半年多前使用过一段时间, 问题太多, 放弃了, 看来还得再等等. 以前感觉emacs+lsp挺有希望, 现在有点怀疑了.
TextDocumentClientCapabilities
的completion.completionItem.snippetSupport
。有些人不喜歡placeholder [投票] `foo` `foo(` or `foo()` 函數補全方式 - #8,来自 zhouchongzxc ,language server可能也需要這個選項(有些language client不支援控制snippetSupport)
你很烦耶?能不能少抬几句杠?