好久不用lsp-mode,本来想装上试试,发现一堆依赖,我又不用package.el,放弃了。直接在github上看了看代码,一坨。。。
如果是我,我可能会尝试两条路,一是继续跟踪看上面这个函数怎么执行的,二是改一下lsp-mode的代码,让completion不支持动态注册,然后看补全是什么状况,确定是不是跟triggerCharacter有关。
好久不用lsp-mode,本来想装上试试,发现一堆依赖,我又不用package.el,放弃了。直接在github上看了看代码,一坨。。。
如果是我,我可能会尝试两条路,一是继续跟踪看上面这个函数怎么执行的,二是改一下lsp-mode的代码,让completion不支持动态注册,然后看补全是什么状况,确定是不是跟triggerCharacter有关。
对的,确实是这里 (lsp--capability-for-method "textDocument/completion")
返回的散列表中没有completionProvider
的信息,c++下是有的。你的方法更巧妙一些,加上就可以解决了。
确实如楼上所说,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之后,包能用也就不随便升级了,感觉省心了不少,哈哈
我提在lsp-dart下了,我再去lsp-mode下提一个
是的, 一旦 git 锁定版本, 只要不天天升级, 可以稳定用很长时间。
长时间不升级,担心有BUG没解或者新功能用不上啊。
结果上次mac下的gnupg升级害死人
我说的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,看看他们啥反应。
哈哈哈哈, lsp-bridge 早期是支持 company、corfu 的, 一个是补全数据不会动态更新, 二是插入补全项以后频繁闪烁出bug, 导致我最后直接重写 acm-mode 了。
大佬的英文好, 争取加入 company 支持, 倒逼 corfu 更新。
没错,我就是这么想的。company会不会支持另说,但至少得去试试。
前几天去company-mode那儿提了个issue,corfu-mode作者也在。根据他们的建议,我在:exit-function里禁用redisplay之后,闪烁的问题减轻了好多。当然插入再删除然后再插入这个有些多余的逻辑还是存在,不过看起来主要问题不在这儿。
对于什么情形下Emacs会做redisplay还没研究,不确定是:exit-function的哪部分导致的。