求助:emacs lsp-dart的company补全无法弹出

好久不用lsp-mode,本来想装上试试,发现一堆依赖,我又不用package.el,放弃了。直接在github上看了看代码,一坨。。。

如果是我,我可能会尝试两条路,一是继续跟踪看上面这个函数怎么执行的,二是改一下lsp-mode的代码,让completion不支持动态注册,然后看补全是什么状况,确定是不是跟triggerCharacter有关。

对的,确实是这里 (lsp--capability-for-method "textDocument/completion") 返回的散列表中没有completionProvider的信息,c++下是有的。你的方法更巧妙一些,加上就可以解决了。 :+1:

确实如楼上所说,dart server返回的信息中,没有completionProvider相关信息,所以lsp-completion-at-point中查到trigger-characters为空,我一直在想怎么直接拦截掉dart server返回的信息加下completionProvider,不过楼上的方法更简单一些。

--- [00:06:23.732173] Recv response (27290) from 'dart-analysis-server' for project complete-flutter-course
{
   "id": 27290,
   "jsonrpc": "2.0",
   "result": {
      "capabilities": {
         "callHierarchyProvider": true,
         "codeActionProvider": {
            "codeActionKinds": [
               "source",
               "source.organizeImports",
               "source.fixAll",
               "source.sortMembers",
               "quickfix",
               "refactor"
            ]
         },
         "colorProvider": {
            "documentSelector": [
               {
                  "language": "dart",
                  "scheme": "file"
               }
            ]
         },
         "completionProvider": {
            "resolveProvider": true,
            "triggerCharacters": [
               ".",
               "=",
               "(",
               "$",
               "\"",
               "'",
               "{",
               "/",
               ":"
            ]
         },
         "definitionProvider": true,
         "documentFormattingProvider": true,
         "documentHighlightProvider": true,
         "documentOnTypeFormattingProvider": {
            "firstTriggerCharacter": "}",
            "moreTriggerCharacter": [
               ";"
            ]
         },
         "documentRangeFormattingProvider": true,
         "documentSymbolProvider": true,
         "executeCommandProvider": {
            "commands": [
               "edit.sortMembers",
               "edit.organizeImports",
               "edit.fixAll",
               "edit.sendWorkspaceEdit",
               "refactor.perform",
               "move_top_level_to_file"
            ],
            "workDoneProgress": true
         },
         "foldingRangeProvider": true,
         "hoverProvider": true,
         "implementationProvider": true,
         "inlayHintProvider": {
            "resolveProvider": false
         },
         "referencesProvider": true,
         "renameProvider": true,
         "selectionRangeProvider": true,
         "semanticTokensProvider": {
            "full": {
               "delta": false
            },
            "legend": {
               "tokenModifiers": [
                  "documentation",
                  "constructor",
                  "declaration",
                  "importPrefix",
                  "instance",
                  "static",
                  "escape",
                  "annotation",
                  "control",
                  "label",
                  "interpolation",
                  "void"
               ],
               "tokenTypes": [
                  "annotation",
                  "keyword",
                  "class",
                  "comment",
                  "method",
                  "variable",
                  "parameter",
                  "enum",
                  "enumMember",
                  "type",
                  "source",
                  "property",
                  "namespace",
                  "boolean",
                  "number",
                  "string",
                  "function",
                  "typeParameter"
               ]
            },
            "range": true
         },
         "signatureHelpProvider": {
            "retriggerCharacters": [
               ","
            ],
            "triggerCharacters": [
               "("
            ]
         },
         "textDocumentSync": {
            "change": 2,
            "openClose": true,
            "willSave": false,
            "willSaveWaitUntil": false
         },
         "typeHierarchyProvider": true,
         "workspace": {
            "fileOperations": {
               "willRename": {
                  "filters": [
                     {
                        "pattern": {
                           "glob": "**/*.dart",
                           "matches": "file"
                        },
                        "scheme": "file"
                     },
                     {
                        "pattern": {
                           "glob": "**/",
                           "matches": "folder"
                        },
                        "scheme": "file"
                     }
                  ]
               }
            },
            "workspaceFolders": {
               "changeNotifications": true,
               "supported": true
            }
         },
         "workspaceSymbolProvider": true
      },
      "serverInfo": {
         "name": "Dart SDK LSP Analysis Server",
         "version": "3.0.7"
      }
   }
}

3.0.7 是可以返回 triggerCharacters 的 (test by lsp-bridge)

建议你检查 lsp client 发送给 lsp server 的 initialize 消息。

有时间的话,还是去给 lsp-mode 和 dart 提个 issue 比较好,临时方案只是解决燃眉之急。

我大概看了下lsp-mode,看起来是因为lsp-mode支持completionProvider的动态注册,dart就没在initialize的响应中把triggerCharacters返回,而是后面又单独通过client/registerCapability注册的(上面的回复中有日志也能看到)。再然后为啥补全不工作就不清楚了。应该是lsp-mode的锅。

lsp-bridge应该也不支持动态注册吧?所以dart直接就在initialize的响应中返回了。eglot/lspce也都不支持,"."补全就可以工作。

只从我开发 lsp client 的经验说哈。

第一, 确实如大佬所说, 凡是lsp client不承诺可以动态注册的, lsp server 都在 initialize 信息尽量返回自己的能力

第二, 我在开发 lsp-bridge 的时候发现 company 和 corfu 都遵照 capf 的协议,而且特别依赖光标前 prefix 的比较和过滤, 一旦 lsp server 频繁返回补全信息, 又和 capf 假象的交互方式不一样的时候, company 和 corfu 都有可能会接到补全信息但不更新的情况, 这也是我开发 acm-mode 的主要原因

lsp-bridge 本身支持动态注册, 但是我考虑不同 lsp server 的能力和性格还是有差异, 所以我发送 initailze 信息的时候把很多要求都加上了, 所以一定程度避免这种问题。

我的建议是,需要先看 lsp-mode 接受到的 initalize 响应是否包含 triggerCharacters, 然后再排除 company/corfu 的问题, 如果 triggerCharacters 不管是初始化还是运行时动态返回时确实返回了, 大概率是 capf 规范和 LSP 响应模式不匹配导致的。

歪个楼, 大佬现在用啥方式在管理包呀?

我还是用我写的那个比较简陋的pie,主要还是需要啥功能可以自己随便改。换到pie之后,包能用也就不随便升级了,感觉省心了不少,哈哈

1 个赞

我提在lsp-dart下了,我再去lsp-mode下提一个

1 个赞

是的, 一旦 git 锁定版本, 只要不天天升级, 可以稳定用很长时间。

长时间不升级,担心有BUG没解或者新功能用不上啊。

结果上次mac下的gnupg升级害死人 :joy:

我说的git其实是完全可控的升级, 升级出现bug可以任意回滚, 可以自己控制升级任意包版本。

package.el 那种全部一股脑升级就是每天开盲盒, 升级挂了回不去稳定的状态。

capf跟lsp补全确实不太搭,我觉得主要是需要:exit-function来做的一些善后处理跟回车补全时的逻辑有重叠,导致体验变差。

我曾经去corfu那儿提议加一个:complete-function,回车时调用这个方法处理补全,不用:exit-function了,被拒了,我也懒得去bb了。

是的, capf 的设计是假设补全数据已经全部准备好了,用户后续的输入都是对准备好的补全列表进行正则过滤和搜索,而且 :exit-function 的逻辑是先把补全项插入以后, 再调用 :exit-function 接口进行善后处理。

而 LSP 协议考虑了很多因素,包括性能(比如不要一次性返回所有补全项的文档这种细节), LSP的协议是随时随地都在返回新的数据, 这样就导致 capf 这种先天假设补全数据不会变的设想没法满足 LSP 协议的要求, 而且 LSP 协议对补全项的插入, 特别是有模板的情况, 都会详细的返回一次性插入正确(比如 xxx.yyy 变成 xxx?.yyy 这种情况), 而 capf 的 :exit-function 死板的逻辑就会导致, 先插入再删除再插入这种闪烁的现象发生, 而且 lsp client 作者要按照 capf API硬套,再插入模板补全项的时候要进行复杂的计算, 很容易出现各种bug。

还是大佬这种开发过 lsp client 的人理解呀, 交流起来是顺畅的。

我现在很少和那些认同 capf 协议的人bb了, 原因是, 我发现大多数 Emacser 根本就没有开发过 LSP Client, 你跟他摆事实讲道理, 告诉他为什么 capf 和 LSP 协议不搭, 他不但没有理解, 反而天天说我就要自定义 company/corfu 的细节, 而且认为自己能花费大量时间折腾好 company/corfu 可以炫耀自己的能力。

所以大佬说的对, 不要和 corfu 的作者bb, 固执的认为 capf 就是标准, 而不关心 lsp 协议, 没必要和大多数不懂的人浪费时间。

没错!我就是对这个闪烁不爽才想着改造一下capf,而且在lspce中确实反复几次解决过:exit-function的bug。(我fork了company-mode,加了:complete-function,并且lspce也支持了这个新函数,感觉还是有效果的)

我打算再去company-mode那儿碰碰钉子,提议加:complete-function,看看他们啥反应。

2 个赞

哈哈哈哈, lsp-bridge 早期是支持 company、corfu 的, 一个是补全数据不会动态更新, 二是插入补全项以后频繁闪烁出bug, 导致我最后直接重写 acm-mode 了。

大佬的英文好, 争取加入 company 支持, 倒逼 corfu 更新。 :wink:

没错,我就是这么想的。company会不会支持另说,但至少得去试试。

1 个赞

前几天去company-mode那儿提了个issue,corfu-mode作者也在。根据他们的建议,我在:exit-function里禁用redisplay之后,闪烁的问题减轻了好多。当然插入再删除然后再插入这个有些多余的逻辑还是存在,不过看起来主要问题不在这儿。

对于什么情形下Emacs会做redisplay还没研究,不确定是:exit-function的哪部分导致的。