lsp-bridge -- 性能最快的语法补全插件

五一假期这几天准备折腾一下 LSP Bridge, 这个项目的目标是实现LSP补全达到 VSCode 的流畅级别。

这个项目的主要工作包括:

  1. 在Emacs和LSP Server建立一个桥梁,用Python来解析LSP Server返回的JSON信息
  2. 当LSP Server返回的JSON数据过期或者解析太慢时完全不影响Emacs, 实现Emacs补全不卡手
  3. 利用Qt来绘制补全窗口,包括图标绘制、左右对齐、Web文档提示等

目前已经实现对JSON协议的解析和消息通讯,因为每种编程语言的 initialize 参数不一样、消息发送的顺序也不一样,导致发送 textDocument/completion 协议后并没有得到LSP Server的返回消息。

对LSP有研究的同学,欢迎提供LSP Client参数配置、消息线索或者文档链接,期望五一节后能给大家带来全新、不卡的LSP Client.

初期先对Python语言进行开发。

54 个赞

知道 pyright LSP 参数详细配置的同学可以分享一下

我用 pyright-langserver --stdio 启动 pyright 服务器,已经向 pyright 发送了initialize消息,为啥 pyright 不回应我的 initialize 消息呢?

Content-Length: 2892

{"jsonrpc":"2.0","method":"initialize","params":{"processId":null,"rootPath":"/home/andy/lsp-bridge","clientInfo":{"name":"emacs","version":"GNU Emacs 28.1 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.33, cairo version 1.17.6)\n of 2022-04-04"},"rootUri":"file:///home/andy/lsp-bridge","capabilities":{"workspace":{"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"]},"applyEdit":true,"symbol":{"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]}},"executeCommand":{"dynamicRegistration":false},"didChangeWatchedFiles":{"dynamicRegistration":true},"workspaceFolders":true,"configuration":true,"codeLens":{"refreshSupport":true},"fileOperations":{"didCreate":false,"willCreate":false,"didRename":true,"willRename":true,"didDelete":false,"willDelete":false}},"textDocument":{"declaration":{"linkSupport":true},"definition":{"linkSupport":true},"implementation":{"linkSupport":true},"typeDefinition":{"linkSupport":true},"synchronization":{"willSave":true,"didSave":true,"willSaveWaitUntil":true},"documentSymbol":{"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}},"resolveSupport":{"properties":["edit","command"]},"dataSupport":true},"completion":{"completionItem":{"snippetSupport":true,"documentationFormat":["markdown","plaintext"],"resolveAdditionalTextEditsSupport":true,"insertReplaceSupport":true,"deprecatedSupport":true,"resolveSupport":{"properties":["documentation","detail","additionalTextEdits","command"]},"insertTextModeSupport":{"valueSet":[1,2]}},"contextSupport":true},"signatureHelp":{"signatureInformation":{"parameterInformation":{"labelOffsetSupport":true}}},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"hover":{"contentFormat":["markdown","plaintext"]},"foldingRange":{"dynamicRegistration":true},"callHierarchy":{"dynamicRegistration":false},"publishDiagnostics":{"relatedInformation":true,"tagSupport":{"valueSet":[1,2]},"versionSupport":true},"linkedEditingRange":{"dynamicRegistration":true}},"window":{"workDoneProgress":true,"showMessage":null,"showDocument":{"support":true}}},"initializationOptions":null,"workDoneToken":"1","workspaceFolders":[{"uri":"file:///home/andy/lsp-bridge","name":"lsp-bridge"},{"uri":"file:///home/andy/emacs-application-framework","name":"emacs-application-framework"},{"uri":"file:///home/andy/lazycat-emacs","name":"lazycat-emacs"}]},"id":1}

按照LSP协议,我先发送一个 initialize 消息给LSP Server, LSP Server 会给我发送 initialize respond 消息,我接着就可以发送其他LSP消息来获取信息(比如定位跳转或补全信息)。

有没有同学知道原因? 或者调试 pyright 的方法?

有没有可能是 pyright 的配置不对? 比如说 include 项目地址?

话说这个 bridge 可以独立出来也和 eglot 或者 lsp-mode 一起使用吗

不会, lsp-bridge 主要利用多线程来解决性能问题,同时创建Emacs风格的Client(那些 breadbumb、hightlight symbol、flycheck/flymake、hover symbol等等那些乱七八糟的功能都去掉)。

我就是看不惯现在的这些LSP实现,太慢了,才独立出来自己弄得,不会和这两个项目一起使用。

2 个赞

主要是 LSP Server 太精贵, 一旦参数出错了,它就不说话,正在改 LSP Server 的代码来定位问题。

支持一下,一直用你的 nox,感觉已经很流畅了,lsp-bridge 如果能彻底解决卡顿那就完美了。感觉 lsp 只要能补全能跳转,就够了,其它功能像格式化,诊断都有更好的工具去做。

确实,还有就是内存占用要是能降一降就好了,现在用 lsp-mode 每次打开一个项目都是好几百兆,这和开个 vscode 有啥区别。

说到内存占用,其实像pyright 和 clangd 都不算高。

你试试 haskell-language-server,开一个实例,什么都不干,就是 1G+,吓死人。

感谢 @zbelial 大佬的帮忙,万里长征第一步搞定了。

对,格式化、诊断Emacs都有很好的生态。

lsp-bridge 最主要的目标:

  1. 超流畅的性能,类似 snails 的 ticker 原理,永远不要卡手
  2. 正常的UI, 比如补全对话框支持图标、左右对齐和免配置
  3. 核心功能做好:补全、跳转、重命名,其他华丽胡哨的功能都不要做,避免卡手

LSP 协议的基本消息类型 Request、Notification和Response 已经支持,目前已经可以正常的和LSP Server 进行 initialize、 workspace/didChangeConfiguration、client/registerCapability、textDocument/didOpen、 textDocument/codeAction消息了。

明天研究补全和定义跳转的消息协议,这两个流程研究清楚了,就可以重构代码去联动 Emacs Hook 自动发消息给 LSP Server。

只要 LSP Server 能够响应补全和定义跳转消息,就可以结合EAF的技术实现类似 VSCode 的补全窗口。

补全窗口,我准备用纯Qt来实现,以实现最高的性能。

5 个赞

知道补全和定义跳转协议流程的大佬,欢迎分享流程经验哈。

这个我肯定知道哈,哈哈哈哈

代码补全、查找定义、查找引用、重命名四个最核心的协议层已经实现, 等我今天好好的想一下 Emacs <-> lsp-bridge <-> lsp-server 之间交互的整体架构,这两天写一个可以用的 demo 出来。

12 个赞

那是因为不是每个lsp server都支持多项目同时分析的,所以需要一个项目开一个server。

架构设计想清楚了:

  1. lsp-bridge多线程缓冲设计,绝对不卡手
  2. 支持单文件补全或者Git目录自动探测
  3. 一个项目支持多个server,比如EAF python,vue和js文件可以同时用不同的lsp server

争取最终效果完全不要让用户设置任何代码,打开代码就可以立即补全,跳转定义,查看引用和重命名。

34 个赞