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

感谢反馈, enjoy it. :wink:

lsp-bridge 最新版本更新了:

  1. Semantic Tokens 的功能目前可以做到开箱即用, 在对应语言开启 lsp-bridge-semantic-tokens-mode 选项即可, 之前需要手动配置各种 Semantic Tokens 所需的 Face

  2. 修复Elisp后端跳转定义后不能返回之前的跳转位置

  3. 优化了 Rust 语言补全候选词的显示算法, 去掉函数候选词前面的 pub const 等重复字符串,补全看起来更优雅

  4. 添加了对LSP Server进度显示的支持

  5. 添加对 TailwindCSS 的支持, 前端的同学现在可以混合 TaildwindCSS 和其他LSP Server, 比如 HTML 和 CSS等

  6. 增加了对局部格式化 RangeFormatting 协议的支持

  7. 修复了很多远程服务器代码补全时遇到的小问题

  8. 增加了nextls, graphql-lsp等新的语言服务器, 现在支持的语言服务器超过 80 个,官方多语言混合服务器支持超过 12 个

欢迎大家更新到最新的 lsp-bridge 版本,欢迎反馈问题,我周末有时间都会很快修复的。

13 个赞

怎么重现的?

我看错误是在 elisp 这里, 应该是你自定义的 lsp-bridge-get-multi-lang-server-by-project 函数的错误?

(setq lsp-bridge-get-multi-lang-server-by-project #'my/bridge-multi-server-detect)
  (defun my/bridge-multi-server-detect (project-path filepath)
    "Detect right server config for multi servers.."
    (save-excursion
      ;; detect for web dev
      (let* ((tailwindcss-p (directory-files
                             project-path
                             'full
                             "tailwind\\.config\\.\\(j\\|cj\\|mj\\|t\\)s\\'"))
             (ext (file-name-extension filepath))
             (jsx-p (or (string= ext "jsx")
                        (memq major-mode '(jtsx-jsx-mode  js-jsx-mode))))
             (tsx-p (or (string= ext "tsx")
                        (memq major-mode '(jtsx-tsx-mode
                                           jtsx-typescript-mode
                                           tsx-ts-mode))))
             (html-p
              (or (memq ext '("htm" "html"))
                  (memq major-mode '(mhtml-mode html-mode html-ts-mode web-mode))))
             (css-p
              (or (memq ext '("css"))
                  (memq major-mode '(css-mode css-ts-mode less-css-mode scss-mode)))))
        (cond
         ((and tailwindcss-p jsx-p) "jsreact_tailwindcss")
         ((and tailwindcss-p tsx-p) "tsreact_tailwindcss")
         ((and tailwindcss-p html-p) "html_emmet_tailwindcss")
         ;; ((and tailwindcss-p html-p) "html_tailwindcss")
         (html-p "html_emmet")
         ((and tailwindcss-p css-p) "css_emmet_tailwindcss")
         ;; ((and tailwindcss-p css-p) "css_tailwindcss")
         (css-p "css_emmet")))))

我自定义的detect函数没问题啊,我git init之后就能正常使用了。

这里的 project_path 是根据 get_project_path 函数来获取文件所在的项目 root 路径, 分别根据 lsp-bridge-get-project-path-by-filepath, .dir-locals.el 或者 git init 来获取。

如果都不符合就返回文件的路径。

lsp-bridge 不论是单服务器还是多服务器, 理论上都是支持单文件补全的(只要设置的lsp 服务器支持单文件即可), 所以 lsp-bridge 不能根据 project_path 不是一个目录就去报错。

你遇到的错误是你自定义的函数调用了 directory-files , directory-files 这个函数如果发现路径不是目录就会直接报错。

上面是我的思考逻辑, 如果有更好的建议欢迎讨论。

哦哦,所以我得先检查 project-path是否为directory然后在调用directory-file,这样应该就行了。可是这不符合直觉啊,默认设为文件的目录应该更好吧。

主要是一些语言支持单文件就可以补全, 比如 Python, 这种类似的语言很多, 如果强制发现 project-path 不是目录就报错, 就会让这些单文件就可以补全的语言觉得莫名其妙。

我主要是没有想到两全其美的办法。

我觉得 languageId 的检测还是要找办法优化,要不然的话就得写一堆的 taiwindcss-{css,html,sveltve, vue,astro,…}.json 和 emmet-{css,html,javascriptreact,…}.json 来进行组合。

@manateelazycat 我想了一下,multi-server其实有两个层面的问题:

  1. 是要不要启动 emmet/tailwindcss 的问题,这可以通过 已经提供的 lsp-bridge-get-multi-lang-server-by-project 来解决,比如检测当前 project 是否有 tailwindcss.config.js 配置文件来决定启动 javascriptreact.json 或者 javascriptreact-tailwindcss.json

  2. 是如何动态传入languageId 给 emmet/tailwindcss 来适应不同的文件,默认这些附加的server的 languageId 属性是应该一个数组,比如 [html,css,javascriptreact, vue, ....] , 写死在具体的json文件中。python端一旦检测到不是string,是数组,就调用 elisp的一个predicate 函数获取具体的返回值就行了,返回值只要合法中就可以启动。 predicate 函数应当类似于:

(defun lsp-bridge-multi-server-languageid-detect (projcet-path, filepath, server-name)
   (cl-case server-name
     ('tailwindcss
       (cond 
        ((and (eq ext "html") ....). "html")
        (.....))
     ('emmet-ls
       (cond
        ((and ....))
)

反正这种 server 没几个,让用户自己去判断好了。这样最大的好处就是只需要一个 tailwindcss.json就行了,用户配置multi-server的时候无脑写,不用去留心到底是哪个 tailwindcss-css/html/javascriptreact…

在Single LSP Server的实现中, 是支持 languageIds 的, 根据不同的扩展名返回不同的 languageId, 参考 lsp-bridge/langserver/vscode-css-language-server.json at 4489fa4f5ee353ea94c02f1a4fdc5eb360ee29fd · manateelazycat/lsp-bridge · GitHub

Multi LSP Server中主要是 TailwindCSS LSP Server 比较特殊, 它需要动态的返回 languageId, 不能完全依据文件扩展名来判定。

我也看了你写的多语言自定义函数, 确实比较复杂, 也许你可以详细写一下你遇到的困难, 我看看有没有增强的办法, 你自己写的解决方法, 但是我不能明白你遇到的困难和场景, 导致我现在我看不懂你想表达的意思。

我理想中的配置是这样的:

  1. taiwindcss.json 只需要一个, “languageId”:字段 把 tailwindcss-intellisense/packages/tailwindcss-language-service/src/util/languages.ts at master · tailwindlabs/tailwindcss-intellisense · GitHub 所有支持的languageId 写成数组,写死在json文件中。

  2. 自定义任何multi-server.json 比如 javascriptreact-tailwindcss.json, 都用到那一个tailwindcss.json.

  3. python端启动tailwindcss.json时候检测是languageId是一个数组,就调用用户自定义的predicate函数获取具体文件的id string,只要这个string 在数组内,就用它来启动。 前面的predicate函数我写错了,应该要三个参数,两个path 外加一个python传递给函数的server-name才行,这样的话 python那边就不用搞那些 {“jsx” : “javascriptreact” …} 之类的东西了,以后如果还有其他的类似server,就只需要添加一个写满所有支持languageId的json文件即可,

感觉是一劳永逸的解决方法啊,不过我不会python…。。

这个文件我看了, 但是我没有看懂 TailwindCSS 的逻辑呀。

比如我有一个文件的扩展名, 怎么转换到连接里面哪些扩展名?

不,那是用户的事,用户自己写predicate函数来判断。 python端只需要检测到languageId是一个数组,就调用函数接收一个string返回值然后启动就行。 用户怎么判断,那就百无禁忌,astro项目,svelte项目啊各有各的特点,svelte文件可以直接用后缀判断,其他的有些不能用后缀判断的,那就看项目特点了,有没有特殊的配置文件啦,在特定的目录下的文件比如 src/ 都返回某一类值。。或者最笨的办法就是路径判断,,某一个project 路径match到了就返回某一个string… 额,其实 应该要提供的option的是一个predicate 函数列表,官方可以写一个default的,比如就根据后缀来predicate,如果返回nil就不启动server,用户自己可以追加自己的predicate函数在后面,一直到整个列表都没有返回值才宣告server failed。我觉得用后缀应该就能解决一大半问题了。

理一下哈:

  1. LSP 协议要求打开文件的时候传递 languageId lsp-bridge/core/lspserver.py at 4489fa4f5ee353ea94c02f1a4fdc5eb360ee29fd · manateelazycat/lsp-bridge · GitHub
  2. 大多数LSP Server 的 languageId 都是固定的, 写到 langserver.json 就好了
  3. 但是 TailwindCSS LSP Server 的 languageId 不能写死, 因为他支持很多扩展名

目前 lsp-bridge 的机制是:

  1. 有特殊的就写到 TAILWINDCSS_LANGUAGE_ID_DICT 里做字典对应
  2. 没有特殊的就反馈文件的扩展名给 TailwindCSS LSP Server

我的问题是, 难道 TAILWINDCSS_LANGUAGE_ID_DICT 这种字典对应关系都不行吗? 还是说不同的前端框架项目, 同样一个文件扩展名传递给 TailwindCSS 的 languageId 都不一样?

我主要是没有理解你的需求。

“同样一个文件扩展名传递给 TailwindCSS 的 languageId 都不一样”, 这就是核心问题,所以我说你就让用户自己去判断不同的项目,文件,该启动怎样的参数,官方就提供一个根据后缀来判断的default predicate,而且这个predicate还要放在列表的最后面,让用户的predicate在前面判断。

emmet-ls 支持 的 filetypes = { “css”, “eruby”, “html”, “javascript”, “javascriptreact”, “less”, “sass”, “scss”, “svelte”, “pug”, “typescriptreact”, “vue” } 其实还相对简单,tailwindcss才是终极魔王。

你其实就是想让我给一个 Elisp 的接口, 传入 project-path 和 file-name , 然后根据用户自定义的 elisp 函数, 返回一个 languageId ?

只要 TailwindCSS 的 languageId 返回对了, 其他 Web LSP Server 和 TailwindCSS 的混合补全就走 lsp-bridge 正常的 multi-server.json ?

不,这个接口还需要一个额外的参数,就是server-name,是三个参数。然后我就可以 用project-path filepathserver-name(是tailwindcss还是emmet-ls还是将来出现的其他server“)来精准的返回languageId了,说白了哪怕我用最笨的路径match都能百分之一百的启动对。而且后续维护也省心,不需要的python那里搞一堆字典来match,如果将来的有新的server了,只需要增加一个single server.json 就行了。

给我5分钟吧

我去写关于json的pr