lsp-bridge -- 速度最快的语法补全插件

我以为像你这种大佬,应该不会有太多的出差需求才对啊。搞个4k显示器,不香吗。

我天天出差呀。

那难怪了,紫薯布丁

@manateelazycat 发了一个 lsp-bridge 的 PR,应该能处理 lsp-bridge -- 速度最快的语法补全插件 - #1780,来自 EthanLin 里提到的问题,你看会不会有别的影响?

1 个赞

感觉没啥副作用, 这个补丁确实解决了问题, 感谢大佬帮助! :wink:

大佬不敢当😮,我有些问题想请教:

  • 如果 lsp server 返回的补全类型(trigger_kind)是 2 ,并且没有额外的additionalTextEdits 信息,那么就是把候选项 append 到单词末尾,如果返回是 1, 那么就是用候选词替换光标下的词吗?
  • 另外,如果我不开启 lsp server, 只用 lsp-bridge 提供的补全,当前默认基本就是 Search word 或者 file path, 但如果我想自己加一个补全后端到 lsp-bridge 里,比如说想添加一个从英文缩写到中文全称的后端,类似 abbrev (效果是,输入 DNA, 补全列表里能显示中文的 “脱氧核糖核酸”,然后按 tab 就自动把 DNA 替换成中文,如果输入 DNA:那么列表也显示中文的 “脱氧核糖核酸”,但此时我按 tab 它能把中文 append 到 DNA: 后面而不是替换),从哪段代码能够控制补全时是替换当前词还是在当前词后面 append 内容?

我先回答第一个问题, LSP协议规定 insertTextFormat 是1的时候代表候选词是纯文本,2代表是模板代码, 一般会调用 yasnippet 对LSP返回的模板代码进行展开, 具体可以看 Specification

至于你说的, 候选词是否是直接插入, 还是要替换, 其实要看LSP返回的 textEdit 参数, textEdit 参数会详细的告诉 LSP Client 删除文档的范围以及插入的内容, 比如写 vue 的时候, fun._ 的补全有可能会变成 fun?._ 的形式, 删除到 . 之前, 而什么时候直接插入, 还是删除到点之前再插入 ?._ 完全是 LSP Server返回的语义信息 textEdit 来决定的。

在开发 lsp-bridge 的实践中, 虽然每种语言都遵守了 JSON 的格式和大部分协议标准, 但是补全返回的这一块, 每个语言都有轻微的差别(或者说行为不完全一样), 比如 c、 golang、 vue、 clojure 等语言有各种小细节不一致, 最终导致 lsp-bridge 的实现要反过来照顾 LSP Server 的互不兼容的地方。

第一个问题的道理就是上面的道理, 但是纯文本、 模板、 删除再插入细节、 自动导入加上多个语言服务器的兼容, 最终的实现只能看函数 lsp-bridge/acm-backend-lsp.el at adec95fb4f84c4ed919a8d064da5a2ec5e91cd10 · manateelazycat/lsp-bridge · GitHub

第二个问题的解题思路:

  1. 首先模仿 english 后端写一下框架代码
  2. acm补全都在 acm-complete 函数中实现, acm-complete 会根据当前候选词对应的后端来调用对应的 acm-backend-*-expand 函数
  3. 在你自己后端的 expand 函数中, 我觉得你可以判断一下触发 acm-complete 的按键是 Tab 还是其他按键, 至于是插入候选词还是替换缩略词, 其实就是你后端 expand 函数自己控制的

不考虑性能之类的,单纯从补全内容的角度来说,citre的优点是可以跨语言补全,同时如果lsp不支持补全的东西,如果能够基于正则去获取关键词,用ctags写一个正则来获取关键词很容易。虽然现在lsp不能补全但是可以用正则进行补全的东西应该很少了吧。

请问如何设置某个mode下启用什么server

主要是我的html文件只启动了vscode-html-language-server而没有emmet

非常感谢🙏🏻,看了 acm-backend-lsp-candidate-expand 函数,大概理解 completion 消息部分的主要几个关键字和解析方式了

1 个赞

收到,谢谢!

lsp-bridge 里基本是用 bounds-of-thing-at-point 来获得光标当前的字符作为 keyword 去匹配候选项词,但这会把中文连着英文的情况都选上,比如输入 “使用emacs“ 这时候尽管 emacs 是文件里已经有的词,但输入时也不会匹配到候选项,因为 prefix 会把 “使用” 也算上,有考虑改吗?如果可以改,有推荐的只获取英文单词的函数吗?

我当前是自己定义一个函数

(defun acm-get-input-prefix-bound ()
  (let ((bound (bounds-of-thing-at-point 'symbol)))
    (when bound
      (let* ((keyword (buffer-substring-no-properties (car bound) (cdr bound)))
             (offset (string-match "[[:ascii:]]+" keyword)))
        (cons (+ (car bound) offset) (cdr bound))))))

然后把 bounds-of-thing-at-point 都替换成这个

我是这么 hack 的. 然后在 project 里用 add-dir-local-variable 设置 mlb-enable-lspacm-enable-citre 的值

(defcustom mlb-enable-lsp t
  "Enable lsp server."
  :local t
  :type 'boolean
  :group 'my)

(put 'mlp-enable-lsp 'safe-local-variable #'booleanp)

(defun mlb-has-lsp-server-p (fn &rest args)
  "Around advice for `lsp-bridge-has-lsp-server-p'."
  (when mlb-enable-lsp
    (apply fn args)))

(advice-add #'lsp-bridge-has-lsp-server-p :around #'mlb-has-lsp-server-p)

可以发个补丁,谢谢。

请问我想根据最后一个输入的字符来决定使用不同的补全方法,应该修改那个函数?就像在company mode中我可以写一个backend来按照prefix 来补全

比如说当我输入“test”时我会提交给lsp server来补全,但当输入"test$"时我会提交给另一个进程来补全。

目前是根据mode来发送不同后端的,如果要做到最后一个字符,需要自己写代码判断。

问一下,为啥有这种需求呢?

谢谢回复,应用场景比较特殊。R的language server不能读取data.frame中的列数据。这部分的补全是由ess的进程提供(I am not getting completeion with $ · Issue #340 · REditorSupport/languageserver · GitHub)。我之前用company+lsp的时候就是自定义了个backend判断prefix最后字符是不是$,如果是$就交给ess进程。

我如果要达到类似效果的话,请问我应该去改写哪个函数?

今天更新后,只要输入非英文,比如中文,就会提示错误

Error in post-command-hook (lsp-bridge-monitor-post-command): 
(wrong-type-argument number-or-marker-p nil)

不知大家是否也遇到这样的问题。

当回滚到 adec95f 就一切正常,没有错误提示,也能正常输入中文。