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

应该不是gopls的问题。

不太清楚怎么折腾lsp-server,但想到可以用strace看看gopls的输出,就去试了一下。

sudo strace -s 65536 -p pid

lsp-server的输出各种东西一股脑的来。但多试几次还是能看到自己的补全项。

发现在lsp-bridge启动的gopls上,标准库中的time确实是补全的第一项,sortText = 0;而"go.starlark.net/lib/time"是第二项,sortText = 1。试了几次均是如此,而补全时却没有标准库的time这一项。

{
    "jsonrpc": "2.0",
    "result": {
        "isIncomplete": true,
        "items": [
            {
                "label": "time",
                "kind": 9,
                "detail": ""time"",
                "preselect": true,
                "sortText": "00000",
                "filterText": "time",
                "insertTextFormat": 2,
                "textEdit": {
                    "range": {
                        "start": {
                            "line": 5,
                            "character": 3
                        },
                        "end": {
                            "line": 5,
                            "character": 7
                        }
                    },
                    "newText": "time"
                },
                "additionalTextEdits": [
                    {
                        "range": {
                            "start": {
                                "line": 1,
                                "character": 0
                            },
                            "end": {
                                "line": 1,
                                "character": 0
                            }
                        },
                        "newText": "nimport "time"n"
                    }
                ]
            },
            {
                "label": "time",
                "kind": 9,
                "detail": ""go.starlark.net/lib/time"",
                "sortText": "00001",
                "filterText": "time",
                "insertTextFormat": 2,
                "textEdit": {
                    "range": {
                        "start": {
                            "line": 5,
                            "character": 3
                        },
                        "end": {
                            "line": 5,
                            "character": 7
                        }
                    },
                    "newText": "time"
                },
                "additionalTextEdits": [
                    {
                        "range": {
                            "start": {
                                "line": 1,
                                "character": 0
                            },
                            "end": {
                                "line": 1,
                                "character": 0
                            }
                        },
                        "newText": "nimport "go.starlark.net/lib/time"n"
                    }
                ]
            },
            {
                "label": "timeformat",
                "kind": 9,
                "detail": ""golang.org/x/tools/go/analysis/passes/timeformat"",
                "sortText": "00002",
                "filterText": "timeformat",
                "insertTextFormat": 2,
                "textEdit": {
                    "range": {
                        "start": {
                            "line": 5,
                            "character": 3
                        },
                        "end": {
                            "line": 5,
                            "character": 7
                        }
                    },
                    "newText": "timeformat"
                },
                "additionalTextEdits": [
                    {
                        "range": {
                            "start": {
                                "line": 1,
                                "character": 0
                            },
                            "end": {
                                "line": 1,
                                "character": 0
                            }
                        },
                        "newText": "nimport "golang.org/x/tools/go/analysis/passes/timeformat"n"
                    }
                ]
            },
... ...

还挺有意思的,要不是最近太忙了,不然该看看代码解解迷了 :joy:

我推送了一个补丁, Handle different libraries provide the same function. · manateelazycat/lsp-bridge@f101ba5 · GitHub

你更新看看, 确实是 lsp-bridge 的问题, 没有处理不同库提供相同函数的问题。

卧槽,太快了吧。 试了一下,可以了 :+1: :+1: :+1:

1 个赞

感谢反馈, 你说的这个问题确实在设计的时候忽略了。

Doom Emacs 中输入过快会导致鼠标错位

gif

当在终端以 emacs -nw 启动emacs的时候会稳定出现,当输入速度过快时,会出现错位。

复现步骤

  1. emacs -nw hello.cpp
  2. 引入万能头文件 bits/stdc++.h 与 using namespace std 输入 int main (|) (|表示光标位置)
  3. 输入 string,以一个比较快的输入速度。
  4. BUG复现

emacs -Q -nw 测试

未能复现

1 个赞

好像只要使用evil 就可以复现。

emacs -Q -nw 复现步骤

  1. test.el

(dolist (item (mapcar (lambda (x)
                        (concat "~/.emacs.d/.local/straight/repos/" x))
                      '("acm-terminal" "emacs-popon" "yasnippet"
                        "lsp-bridge" "posframe" "markdown-mode" "evil" "goto-chg")))
  (add-to-list 'load-path item))

(require 'evil)
(evil-mode)

(require 'lsp-bridge)
(global-lsp-bridge-mode)
(setq lsp-bridge-enable-hover-diagnostic t)
(setq lsp-bridge-enable-org-babe t)
(dolist (item '("cpp" "c"))
  (add-to-list 'lsp-bridge-org-babel-lang-list item))
(require 'yasnippet)
(yas-global-mode 1)
(unless (display-graphic-p)
  (with-eval-after-load 'acm
    (require 'acm-terminal)))

(electric-pair-mode)
(setq tab-width 4)
  1. emacs -Q -nw -l test.el test.cpp
  2. 照之前描述的方法做,即可稳定复现
1 个赞

我不用evil,如果有bug,欢迎发补丁。

好的,我努力 :smiley:

@manateelazycat 我想问一下把applyEdit放在arguments里的是哪一个language server的行为,我扫了一眼spec好像没看到有。

;; lsp-bridge/lsp-bridge-code-action.el:326

(dolist (argument arguments)
  (lsp-bridge-workspace-apply-edit argument temp-buffer))))

我正在修eslint的功能,因为eslint的lsp现在不能直接跑。我现在把eslint hack到能跑了,但是code action还是不对。eslint返回的command是个plist

    {
        "title": "Disable import/order for this line",
        "command": {
            "title": "Disable import/order for this line",
            "command": "eslint.applyDisableLine",
            "arguments": [
                {
                    "uri": "file:///home/testeslint/index.ts",
                    "version": 3,
                    "ruleId": "import/order"
                }
            ]
        },
        "kind": "quickfix"
    },

从这行进去之后

;; lsp-bridge/lsp-bridge-code-action.el:334

((plistp command)
 (lsp-bridge-code-action--fix-do command temp-buffer)))

就被这段截胡了,没有发成executeCommand

;; lsp-bridge/lsp-bridge-code-action.el:323

(arguments
 (if-let ((handler (plist-get lsp-bridge-code-action-command-handlers (intern command))))
     (funcall handler action temp-buffer)
   (dolist (argument arguments)
     (lsp-bridge-workspace-apply-edit argument temp-buffer))))
1 个赞

应该是 Java LSP Server, 可以查看 Support jdtls overrideMethodsPromptSupport · manateelazycat/lsp-bridge@337769b · GitHub 这个补丁。

这个补丁是大佬 @zsxh 写的, 你们可以讨论一下, 看看怎么调整优先级?

啊回复错了……

而且出于eslint lsp设计的原因 Consider support for workspaceFolders and related capability? · Issue #909 · microsoft/vscode-eslint · GitHub

它要求handle_workspace_configuration_request里返回这样的一段

      "workspaceFolder": {
        "name": "testeslint",
        "uri": "file:///home/testeslint"
      }

虽然我们可以在所有用到的地方放一个写死的json设置,但是也太麻烦了。

?我们也可以单纯地在handle_workspace_configuration_request里加个check,但是有点丑。还是说我们开始subclass LspServer 去定制这个返回信息比较好

得看下vscode是怎么处理eslint.applyDisableLine这个command的

可以在 handle_workspace_configuration_request 加一个判断, 如果是 eslint 这个 LSP Server, 额外的加一段适配 eslint 的代码吧, 虽然添加到 json 也可以工作, 但是从内容看 workspaceFolder 的内容是动态生成的, 不适合弄到 json 里面去。

workspaceFolder 里面的 name 叫 ‘testeslint’ 还是可以随便取? uri 是当前文件的 path ?

就是直接发回去

[Trace - 12:06:28 PM] Sending request 'workspace/executeCommand - (9)'.
Params: {
    "command": "eslint.applySingleFix",
    "arguments": [
        {
            "uri": "file:///home/testeslint/index.ts",
            "version": 3,
            "ruleId": "import/order"
        }
    ]
}

jdtls 有个类似的command,你参考下, lsp-bridge-code-action-command-handlers 用来注册那些非标准command的处理器

1 个赞

行,那比较简单

name感觉默认是就那个project的文件夹的名字,uri是到文件夹absoluate path,把worksplace_folder变成uri应该就行了

eslint应该还是标准的workspace/executeCommand。参数就是拿到的code action里的 commandarguments。原封不动的发回server去就行了

这块应该可以直接处理,

# lsp-bridge/core/handler/execute_command.py:13

if self.file_action.code_action_response is not None:
    for action in self.file_action.code_action_response:    # type: ignore
        try:
            if action["command"] == command:
                arguments = action["arguments"]
                break
            elif action["command"]["command"] == command:
                arguments = action["command"]["arguments"]
                break
        except:
            pass

return dict(command=command, arguments=arguments)

关键是你得让lsp client看到eslint.applyDisableLine知道后续要干什么

我开了一个PR,但是我不用Java。大佬能帮忙测测不

好了 eslint可以跑了。:tada: 但是我碰到另外一个问题不知道是不是设置的问题。

在multiserver里使用多个codeAction的时候,从server2后返回的codeAction会覆盖掉server1之前的值。但是volar_emmet也没有特别的配置,就只是"code_action": ["volar", "emmet-ls"],

1 个赞