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

我用最简配置无法重现你说的问题:

$ emacs -Q --eval "\
  (progn
    (setq user-emacs-directory
          (car (seq-filter
                #'file-exists-p
                (list (format \"~/.emacs.d/%s/\" emacs-version)
                      (format \"~/.emacs.d/%s/\" emacs-major-version)
                      \"~/.emacs.d/\"))))

    (setq package-user-dir (concat user-emacs-directory \"elpa/\"))
    (package-initialize)
    (add-to-list 'load-path \"~/repos/emacs-lsp-bridge\")

    (setq custom-safe-themes t)
    (setq-default custom-enabled-themes '(tango))

    (defun reapply-themes()
      (load-theme 'tango))

    (add-hook 'emacs-startup-hook 'reapply-themes)

    (require 'yasnippet)
    (yas-global-mode 1)

    (require 'lsp-bridge)
    (global-lsp-bridge-mode))"

可能你的最简配置并未如你期望那般”最简“。

请问你加 --daemon 了吗?

我使用你的这个启动配置还是卡住了。你的是 emacs-mac 还是 emacs-plus?

虽然还是没有弄清原因,把 after-init-hook 改成 window-setup-hook 解决了。

的确放在 after-init-hook 会出错:

$ emacs --daemon
Debug on Error enabled globally
[yas] Prepared just-in-time loading of snippets successfully.
Debugger entered--Lisp error: (wrong-number-of-arguments (((alpha . 0.8) (c2 . "#000000") (c1 . "unspecified-bg") x-gtk-resize-child-frames t) (r g b) (format "#%02x%02x%02x" (ash r -8) (ash g -8) (ash b -8))) 0)
  (closure ((alpha . 0.8) (c2 . "#000000") (c1 . "unspecified-bg") x-gtk-resize-child-frames t) (r g b) (format "#%02x%02x%02x" (ash r -8) (ash g -8) (ash b -8)))()
  apply((closure ((alpha . 0.8) (c2 . "#000000") (c1 . "unspecified-bg") x-gtk-resize-child-frames t) (r g b) (format "#%02x%02x%02x" (ash r -8) (ash g -8) (ash b -8))) nil)
  acm-color-blend("unspecified-bg" "#000000" 0.8)
  (set-face-background 'acm-default-face (acm-color-blend (face-attribute 'default :background) blend-background (if is-dark-mode 0.8 0.9)))
  (progn (set-face-background 'acm-default-face (acm-color-blend (face-attribute 'default :background) blend-background (if is-dark-mode 0.8 0.9))))
  (if (or force (equal (face-attribute 'acm-default-face :background) 'unspecified)) (progn (set-face-background 'acm-default-face (acm-color-blend (face-attribute 'default :background) blend-background (if is-dark-mode 0.8 0.9)))))
  (let* ((is-dark-mode (string-equal (acm-get-theme-mode) "dark")) (blend-background (if is-dark-mode "#000000" "#AAAAAA"))) (set-face-attribute 'acm-buffer-size-face nil :height (face-attribute 'default :height)) (if (or force (equal (face-attribute 'acm-default-face :background) 'unspecified)) (progn (set-face-background 'acm-default-face (acm-color-blend (face-attribute 'default :background) blend-background (if is-dark-mode 0.8 0.9))))) (if (or force (equal (face-attribute 'acm-select-face :background) 'unspecified)) (progn (set-face-background 'acm-select-face (acm-color-blend (face-attribute 'default :background) blend-background 0.6)))) (if (or force (equal (face-attribute 'acm-select-face :foreground) 'unspecified)) (progn (set-face-foreground 'acm-select-face (face-attribute 'font-lock-function-name-face :foreground)))))
  acm-init-colors(t)
  acm-reset-colors(tango)
  apply(acm-reset-colors tango)
  load-theme(tango)
  reapply-themes()
  run-hooks(after-init-hook delayed-warnings-hook)
  command-line()
  normal-top-level()

Error: server did not start correctly

我前面不用 after-init-hook 是因为 emacs -Q 会忽略它,所以改为 window-setup-hook

lsp-bridgeacm-reset-colors 放在 load-theme 之后执行,以确保设置/更改 theme 的时候生效:

(advice-add #'load-theme :after #'acm-reset-colors)

如果在 after-init-hook 的时候 load theme,就会因为参数 "unspecified-bg" 无法识别而卡住。这个问题其实我在 acm-terminal 解决过😂:

所以,问题来了,为什么会出现 "unspecified-bg"?因为被当成终端模式了,为什么会被当作终端模式?因为 daemon 启动时,Emacs 处于一个混沌的状态,详见我这个帖子:Daemon 模式下的一个坑:window-system 变量从 nil 到有

所以,你的问题目前最简单的解决方案就是在 window-setup-hook 调用 load theme,因为该 hook 不会在 daemon 启动时执行,而是在 emacsclient 连接时执行,此时就可以明确 Emacs 处于 GUI 状态了,也就不会出现参数错误。(EDIT: 其实 emacsclient 启动时,在 window-setup-hook 中 load theme 仍然会出错,即依然无法判断是否 GUI,要等到 after-make-frame-functions 之后才能真正知道是否处于 GUI。但是在 client 中出错不影响 daemon,所以就蒙混过关了😂)

lsp-bridge 可以暂时不解决这个问题(本来目前就不支持终端嘛,何必去解决不存在的问题?),等我合并 acm-terminal 一并处理。 @manateelazycat

9 个赞

大佬分析的全面 :+1:

我又想了想,还是先提个补丁比较好,毕竟同时使用 GUI 和 Daemon 是很正常的,两者不矛盾。


daemon 的情况还是有点复杂,在 after-init-hook 中(即 daemon 初始化时),无法判断 GUI 还是 CLI。不能让用户放弃使用 after-init-hook,先提交一个补丁保证 GUI 下正常初始化:

(if (daemonp)
    (add-hook 'server-after-make-frame-hook
              (lambda ()
                (acm-reset-clors) ;; 错过 `after-init-hook`, 这里补调一次。
                (advice-add #'load-theme :after #'acm-reset-colors)))
    (advice-add #'load-theme :after #'acm-reset-colors))
  1. daemon 方式启动,在 after-init-hook 中无法判断是否 GUI,推迟调用 acm-reset-colors,直到 server-after-make-frame-hook

  2. 正常启动,在 after-init-hook 中可判断是否 GUI,立即调用 acm-reset-colors

lsp-bridge 的后端速度太快了, 在手速非常快的时候, 会存在竞争条件: 用户选择第二个候选词的时候 lsp-bridge 同时也返回最新的补全数据。

原来老的逻辑是, 返回新的候选词数据就会把光标强制选中到第一个, 遇到上面的竞争条件就会发生, 用户看到菜单是 candidate_a, 但是 lsp-bridge 太快就会导致补全那一瞬间补全的是 candidate_b (因为最新的补全数据 candidate_b 可能排第一位)。 这个bug仅存在于手速超快的时候。

今天想了想, 针对手速超快的情况, 每次更新候选词之前先记录一下上次选中的候选词内容, 更新补全菜单以后再次重新查找上次选中的候选词的新位置, 这样就可以解决手速快补全错误的bug.

因为这个bug太隐蔽, 会影响那些手速特别快的同学, 建议大家更新一下。

2 个赞

有几个使用问题请教一下:

  1. 中等代码量的Java工程,开了lsp-bridge后,调用补全的时候(就是输入变量前3个字符后,补全还未弹出)继续打字会有很大的显示延迟(2s+),补全本身的弹出也有2s+的延迟,怎么定位慢的原因?Emacs版本29.0.50,笔记本是14寸Macbook dtls指定了参数:-Xms8G -XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true,开启了orderless,未使用tabnine
  2. 补全框的宽度是否可以配置,默认的宽度java方法定义基本显示不全
  3. 进行method补全的时候,有办法实现按method name,然后按参数数量或者简单点儿按method signatrue的长度排序?

第一个问题是, Emacs29导致 frame 都慢, 这是 Emacs29 的bug, 日常生产不要用 Emacs29 这种开发版本, API不稳定, bug多, 外围插件不会跟这种版本

第二个问题是, acm-backend-lsp-candidate-max-length 这个弄长点就好了

第三个问题目前还没有做这么精细化, 每种语言和每个人的喜好不一样, 先收集大家意见, 现在的排序是按照 LSP Server 的推荐排序弄的, LSP Server 会分析你过往输入的代码来动态排序候选词, 不一定按照字母或者长度去排序

第一个问题,jvm改一下GC参数试试: -Xms8G -XX:+UseG1GC -XX:MaxGCPauseMillis=200,看看能不能提升下jdtls的性能。

lookup documentation 的弹窗字体比较小,有什么办法可以改吗?

lsp-bridge-lookup-doc-tooltip-text-scale

试了一下,没起作用。字号没变化

补丁合并了, 感谢大佬。

这个问题彻底修复好了, 原因是我这几个月以来一直用的是一个十多年前的 markdown-mode.el (被我个人配置文件做了 shadow 覆盖), 我硬是没有测试出来。

1 个赞

更新后, 试一下 lsp-bridge-lookup-doc-tooltip-font-height 选项。

这个bug我遇到过好多次。。。

可以帮忙测试一下, 看看最新版还有问题吗?

我这边测试已经好了, 如果LSP服务器更新太快, 更新后菜单会选中更新前的候选词, 而不是第一个。