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

谢谢。现在 windows 下能启动了。

各种小问题。后面在 msys2 中开 emacs 中,可以启动 lsp-bridge 了,但是好像路径又有点问题。

放弃了。

准备代码编辑在 Linux 下完成了。后面调试运行再在 Windows 上面整吧 :joy:

谢谢大佬,之前那个错误确实是你提到的这个问题。

后面发现还需要设置一下 lsp-bridge-python-command 这一个好像 Windows 下挺乱的,我找的几个配置设置的都不太一样,最后是按我自己终端设置的。

终于搞定了。 :grinning:

求助,我使用 lsp-bridge 做补全,在 vue 文件里,写下 console ,它会自动给我插入这句话 import console from 'console';,我是要在浏览器里运行的,这句话没必要,还会导致编译错误,我还需要手动删除

大佬,请问 lsp-bridge 中带的文件路径自动补全,是其它的插件,还是 lsp-bridge 自己写的?

我想自定义一些文件补全的功能(比如在指定的文件夹里面查找),但是水平差,定位不到相应的代码位置 :melting_face:

插件代码在 https://github.com/manateelazycat/lsp-bridge/blob/master/acm/acm-backend-path.el

1 个赞

@manateelazycat 我最近在看diagnostics列表打开后不自动更新的问题,lsp-bridge-references--popuplsp-bridge-diagnostic--list共享了lsp-bridge-ref-popup不太好动啊。有什么实现推荐么?

现在每天启动emacs都要遇到一次lsp没补全的情况

lsp-bridge两个buffer都没东西,但是只要重启emacs再来一次就好了,不知道怎么确认问题在哪,pyright也在运行,每天都要来一次,但不知道是不是重启第一次会这样,暂时没法稳定重现,该怎么排查 :joy:

打开了日志,貌似是默认开启多服务器启用的ruff报错了,一个什么clone的报错,忘记截图了,但不是一定会报错,只能说有几率,暂时禁用多服务器,先看看还会不会出现该问题

大佬,补全菜单的图标可以自定义吗 :grinning:

在请教一下,在使用过程中,一段时间后编辑就会不跟手明显卡顿,重启又好了,这种怎么排查呢

emacs -Q排查自己的配置,看论坛的输入框内容

懒猫大大, 请问下, lsp-bridge的补全能用到aweshell里吗?

acm前端肯定能, 但是要写 shell 补全后端需要有人写。

我最近都超级忙, 最多谁愿意写, 我告诉他思路, 我自己没有时间满足大家所有的需求。

大佬共享一下思路吗?

大佬有空的话可以写下思路, 我试着弄一下

爬楼时看到现在已经可以在 org-source-block 中使用 lsp-bridge 了,不过有坛友提出这样就不能使用 capforg-roam 进行标题补全了,对我来说这一个功能还是相当重要的。在网上以"lsp-bridge org-roam"为关键词搜索无果后我花了一点时间糊了一个简陋的 acm-backend-org-roam,基本上实现了 org-roam-complete-link-at-point

我不太熟悉 elisp ,只是粗略地看了一下 acm.el, 照着 acm-backend-tempel.el 等文件和之前的 commit 一通乱改实现的效果,可能存在隐藏的问题或者不合适的改动。现在有一些已知的功能没有实现,或者使用了非常 hacky 的方式,希望大佬们能够给出更加合理的解决方案。

代码如下:

acm-backend-org-roam.el,可以放在 lsp-bridge/acm 下。

;;; acm-backend-org-roam.el -*- lexical-binding: t; no-byte-compile: t; -*-

(defgroup acm-backend-org-roam nil
  "Org-roam backend for acm."
  :group 'acm)

(defcustom acm-enable-org-roam nil
  "Popup Org-roam completions when this option is turn on."
  :type 'boolean
  :group 'acm-backend-org-roam)

(defcustom acm-backend-org-roam-candidates-number 10
  "Maximal number of Org-roam candidate of menu."
  :type 'integer
  :group 'acm-backend-org-roam)

(defun acm-backend-org-roam-candidates (keyword)
  (when (and acm-enable-org-roam
             (featurep 'org-roam))
    (let* ((titles (org-roam--get-titles))
           (match-titles (seq-filter (lambda (s) (acm-candidate-fuzzy-search keyword s)) titles)))
      (acm-candidate-sort-by-prefix
       keyword
       (mapcar
        (lambda (title)
          (list :key title
                :icon "ref"
                :label title
                :displayLabel title
                :annotation "Org-Roam"
                :backend "org-roam"))
        (cl-subseq match-titles 0 (min (length match-titles) acm-backend-org-roam-candidates-number)))))))

(defun acm-backend-org-roam-candidate-expand (candidate-info bound-start)
  (let (roam-p start end)
    (when (org-in-regexp org-roam-bracket-completion-re 1)
      (setq roam-p (not (or (org-in-src-block-p)
			    (string-blank-p (match-string 1))))
	    start (match-beginning 2)
	    end (match-end 2))
      (delete-region bound-start (point))
      (insert (if roam-p "" "roam:") (plist-get candidate-info :label))
      (forward-char 2))))

(provide 'acm-backend-org-roam)

;;;acm-backend-org-roam.el ends here

修改 acm.el

diff --git a/acm/acm.el b/acm/acm.el
index 7c1f9d6..35cf936 100644
--- a/acm/acm.el
+++ b/acm/acm.el
@@ -106,6 +106,7 @@
 (require 'acm-backend-ctags)
 (require 'acm-backend-codeium)
 (require 'acm-backend-copilot)
+(require 'acm-backend-org-roam)
 (require 'acm-quick-access)
 
 ;;; Code:
@@ -247,6 +248,7 @@
 (defvar acm-buffer " *acm-buffer*")
 (defvar acm-menu-frame nil)
 (defvar acm-menu-frame-popup-point nil)
+(defvar acm-menu-frame-popup-point-symbol nil)
 (defvar acm-menu-frame-popup-position nil)
 
 (defvar acm-menu-number-cache 0)
@@ -427,6 +429,8 @@ Only calculate template candidate when type last character."
 
 (defun acm-update-candidates ()
   (let* ((keyword (acm-get-input-prefix))
+         (keyword-symbol (let ((acm-input-bound-style "symbol"))
+                           (acm-get-input-prefix)))
          (char-before-keyword (save-excursion
                                 (backward-char (length keyword))
                                 (acm-char-before)))
@@ -448,7 +452,13 @@ Only calculate template candidate when type last character."
          template-first-part-candidates
          template-second-part-candidates
          ctags-candidates
-         citre-candidates)
+         citre-candidates
+         org-roam-candidates)
+    (when (and acm-enable-org-roam
+	       (eq major-mode 'org-mode)
+	       (org-in-regexp org-roam-bracket-completion-re 1)
+	       (not (org-in-src-block-p))
+	       (setq org-roam-candidates (acm-backend-org-roam-candidates keyword-symbol))))
     (when acm-enable-tabnine
       (setq tabnine-candidates (acm-backend-tabnine-candidates keyword)))
 
@@ -479,6 +489,7 @@ Only calculate template candidate when type last character."
                                lsp-candidates
                                ctags-candidates
                                citre-candidates
+                               org-roam-candidates
                                (acm-backend-search-file-words-candidates keyword)
                                (acm-backend-telega-candidates keyword)))
 
@@ -572,7 +583,8 @@ The key of candidate will change between two LSP results."
          (candidates (or candidate (acm-update-candidates)))
          (menu-candidates (cl-subseq candidates 0 (min (length candidates) acm-menu-length)))
          (current-select-candidate-index (cl-position previous-select-candidate (mapcar 'acm-menu-index-info menu-candidates) :test 'equal))
-         (bounds (acm-get-input-prefix-bound)))
+         (bounds (acm-get-input-prefix-bound))
+         (bounds-symbol (bounds-of-thing-at-point 'symbol)))
     (cond
      ;; Hide completion menu if user type first candidate completely, except when candidate annotation is `emmet' or `snippet'.
      ((and (equal (length candidates) 1)
@@ -623,6 +635,7 @@ The key of candidate will change between two LSP results."
 
         ;; Record menu popup position and buffer.
         (setq acm-menu-frame-popup-point (or (car bounds) (point)))
+        (setq acm-menu-frame-popup-point-symbol (or (car bounds-symbol) (point)))
 
         ;; `posn-at-point' will failed in CI, add checker make sure CI can pass.
         ;; CI don't need popup completion menu.
@@ -698,8 +711,10 @@ The key of candidate will change between two LSP results."
 (defun acm-complete (&optional not-hide)
   (interactive)
   (let* ((candidate-info (acm-menu-current-candidate))
-         (bound-start acm-menu-frame-popup-point)
          (backend (plist-get candidate-info :backend))
+         (bound-start (if (string-equal backend "org-roam")
+                          acm-menu-frame-popup-point-symbol
+                        acm-menu-frame-popup-point))
          (candidate-expand (intern-soft (format "acm-backend-%s-candidate-expand" backend))))
 
     (if (fboundp candidate-expand)

最后别忘了 (setq acm-enable-org-roam t) 启用该功能, acm-backend-org-roam-candidates-number 调整最大数量。

以下是已知的问题:

中文补全

acm-input-bound-style 默认值为 "ascii",为了尽可能不影响其他后端,我用一些比较 hacky 的方法实现中文补全。我定义了大量以 -symbol 结尾的变量作为一时之计,目前没有发现什么的问题,但是显然这不是最好的方案。
相关的变量和函数为 acm-input-bound-style, acm-get-input-prefix-bound, acm-get-input-prefix
acm-update-candidates 中的 keyword 匹配候选项,不修改无法匹配中文。
acm-update 中的 bounds 确认需要删除的已输入 keyword,不修改会重复输入。

其他格式的链接中补全

org-mode 支持 External Links,如 [[file:~/code/main.c::255]],在[[file:*]] 情况下:
org-roam 实现的 capf 中,指针在 * 处似乎是默认不补全的,一开始我以为 org-roam-completion-link-at-point 有特殊的处理,其实它直接把双中括号内的所有字符串发送给 capf。因此,如果你有类似 file:foo 名字的 node capf 是会正常补全的,[[LINK][DESCRIPTION]] 同理。 org-roam-completion-link-at-point 仅仅防止重复输入 roam:
acm 以标点符号为间隔计算 keyword,相当于忽略了冒号及前面的内容。
因为不清楚怎么实现更好暂时搁置了这一功能。

性能问题

org-roam--get-titles 函数调用了数据库获得候选项,应该可以使用外部程序和缓存提高性能。不过我现在 node 数量较少感受不明显。

其他问题

icon、CI等。

思路:

  1. 把类似 inshellisense 这样的终端补全后端包在 acm 的多线程 Python 后端中, 可以参考多线程的路径补全后端 https://github.com/manateelazycat/lsp-bridge/blob/dfc5c3210ec1d6c421ef38e51e30f568ad4a3a54/core/search_paths.py#L1
  2. 编写 acm elisp 前端, 参考路径补全的 acm 前端: https://github.com/manateelazycat/lsp-bridge/blob/dfc5c3210ec1d6c421ef38e51e30f568ad4a3a54/acm/acm-backend-path.el#L1
  3. 在终端模式下打开 lsp-bridge, 而新的后端添加到 acm-update-candidates 函数中 https://github.com/manateelazycat/lsp-bridge/blob/dfc5c3210ec1d6c421ef38e51e30f568ad4a3a54/acm/acm.el#L428

这样就可以实现终端像IDE那样智能补全了。

首先感谢补丁, 回答一下问题:

  1. acm-get-input-prefix 的问题, 可以考虑在函数 acm-get-input-prefix 中针对 org-roam 的环境进行判断, 如果是 org-roam 输入区域 (参考你上面写的 (org-in-regexp org-roam-bracket-completion-re 1) ), 可以自动把 acm-input-bound-style 修正为 symbol, 这样代码更干净一点

  2. 性能问题, org-roam–get-titles 如果是读取数据库的话, 建议像 sdcv 后端 lsp-bridge/core/search_sdcv_words.py at dfc5c3210ec1d6c421ef38e51e30f568ad4a3a54 · manateelazycat/lsp-bridge · GitHub 那样, 在 Python 多线程端读取数据库补全内容, 这样从 Python 多线程后端绕一圈的好处是, 永远不会因为手指头快过于数据库查询速度而卡住输入

  3. 图标问题: 从 Material Design Icons - Icon Library - Pictogrammers 找到合适的图标, 然后添加一个新的图标到 acm-icon-alist 中重启Emacs, 然后执行 acm-icon-fetch-all 会自动下载图标, 最后提交到 github 即可

欢迎提交PR到 github, 我合并补丁的速度还是很快的。

非常感谢懒猫的回复以及写的 lsp-bridge ,用了这个包之后,Emacs 变得非常流畅。
我已经根据意见修改了 acm-get-input-prefix 相关内容:

diff --git a/acm/acm.el b/acm/acm.el
index 7c1f9d6..cb38823 100644
--- a/acm/acm.el
+++ b/acm/acm.el
@@ -373,7 +374,13 @@ So we use `minor-mode-overriding-map-alist' to override key, make sure all keys
 
 (defun acm-get-input-prefix ()
   "Get user input prefix."
-  (let ((bound (acm-get-input-prefix-bound)))
+  (let* ((acm-input-bound-style (if (and acm-enable-org-roam
+					 (eq major-mode 'org-mode)
+					 (not (org-in-src-block-p))
+					 (org-in-regexp org-roam-bracket-completion-re 1))
+				    "symbol"
+				  "ascii"))
+	 (bound (acm-get-input-prefix-bound)))
     (if bound
         (buffer-substring-no-properties (car bound) (cdr bound))
       "")))

不确定这种写法是否标准,修改以后是可以正常获得补全,但是展开时仍存在问题,因为 acm-update 独立获取了 bounds,见最后一行:

(defun acm-update (&optional candidate)
  (acm-quick-access-init)
  (let* ( ;; variables...
         (bounds (acm-get-input-prefix-bound))) ;; <- 这里

需要再修改 acm-update 中的代码,相当于把条件写了三处地方,如果封装成一个和 acm 核心功能无关的判断函数又感觉怪怪的。在进一步修改之前,我想再次征求一下意见。

目前我想到以下几种方案:

  1. 同时修改上面所有地方。
  2. 修改 acm-get-input-prefix-bound,需要考虑到代码中的其他部分,如 lsp-bridge-set-prefix-style
  3. 修改 acm-updateacm-update-candidates ,比如在 acm-update 中获取 bounds,再传给 acm-update-candidates

以上方案都存在一个问题:org-roam 中有另一个默认关闭的功能 org-roam-complete-everywhere 允许用户在链接外的任意位置补全(示例),也许会有用户想要这个功能。我看了一些回复和 commit,默认使用 symbol 会出现一些问题。 要解决这个问题,可以像之前的回复提到的让每一个后端自定义边界函数。

这是 org-roam 中 capf 的实现:

(defun org-roam-complete-link-at-point ()
  (let (roam-p start end)
    (when (org-in-regexp org-roam-bracket-completion-re 1)
      (setq roam-p (not (or (org-in-src-block-p)
                            (string-blank-p (match-string 1))))
            start (match-beginning 2)
            end (match-end 2))
      (list start end
            (org-roam--get-titles)
            :exit-function
            ;; body
            ))))

(add-hook 'completion-at-point-functions f nil t)

这一函数内部用正则获取边界后返回 (START END COLLECTION . PROPS) ,前两个参数就是所用的边界。对于最终用户来说,仍旧不需要进行额外的配置,而对于开发者来说,可以减少许多 workaround。
当然,实现这个想法会影响到许多代码,我自己用不到 org-roam-complete-everywhere ,暂时打算先用前面几种方案实现基础功能。

我自己写代码的速度还不够快 :joy:,目前 fork 了仓库,想等功能稳定和充分测试后再提交 PR。