(分享) 在Eglot下启用javascript的lsp后,避免json被强制开启

问题情景: json-mode继承自js-mode,这造成一个严重问题。
在开启了js的lsp后,再次进入的json的buffer,会被默认开启js的lsp,出现混乱。

关键的,我不用(也不想用)json的lsp,只能耗费了一些时间才找到解决方法。

核心代码:

(defvar my/eglot-language-ignore-modes nil)

(defun my/eglot-current-server (orig-fn)
    (if (derived-mode-p my/eglot-language-ignore-modes)
        (setq eglot--cached-server nil)
      (funcall orig-fn)))
(advice-add 'eglot-current-server :around #'my/eglot-current-server)

使用方法:

(add-to-list 'my/eglot-language-ignore-modes 'json-mode)
1 个赞

感觉你这种方式还是会启动lsp服务,但是不会被当前buffer连接,或许可以试试我一直在用的,只允许指定的major-mode或者minor-mode激活eglot,虽然比较麻烦一些

(defvar maple-lsp-major-modes
  '(python-mode go-mode dart-mode html-mode css-mode js-mode web-mode vue-mode typescript-mode))

(defvar maple-lsp-minor-modes
  '(:not magit-blob-mode))

(defmacro maple-lsp-with(&rest body)
  "Start lsp server and execute BODY within special major modes or minor modes."
  (declare (indent 0) (debug t))
  `(when (and (memq major-mode maple-lsp-major-modes)
              (cl-loop for mode in maple-lsp-minor-modes
                       if (and (not (keywordp mode)) (boundp mode) (symbol-value mode))
                       return (not (eq (car maple-lsp-minor-modes) :not))
                       finally return t))
     ,@body))

(defun eglot-ensure@override ()
  (let ((buffer (current-buffer)))
    (cl-labels
        ((maybe-connect
           ()
           (eglot--when-live-buffer buffer
             (remove-hook 'post-command-hook #'maybe-connect t)
             (unless eglot--managed-mode
               (maple-lsp-with
                 (apply #'eglot--connect (eglot--guess-contact)))))))
      (when buffer-file-name
        (add-hook 'post-command-hook #'maybe-connect 'append t)))))

(advice-add 'eglot-ensure :override 'eglot-ensure@override)

是比较烦,我今天突然发现进入 json-mode/json-ts-mode eglot 突然自动开启了

hook eglot-ensure 是无用的。真正起作用的是下面这条:

(add-hook 'after-change-major-mode-hook #'eglot--maybe-activate-editing-mode)

这造成启动一个lsp服务后,后续的buffer变更都可能会触发问题。

因为启动了一个js的lsp,而json继承自js。

lsp-bridge 吧, 丝滑, 没有这些奇奇怪怪的问题。

真正的入口就是 eglot-ensure,如果你使用的是

(add-hook 'js-mode-hook 'eglot-ensure)

来激活eglot,因为json-mode继承自js-mode,所以eglot-ensure也会在json-mode中执行,另外,js-mode和json-mode的lsp服务是不一样的,所以你即使启动了一个js的lsp服务,打开json文件还是会启动另一个json的lsp服务

eglot启动lsp服务后会把当前major-mode加入到eglot–major-modes这个变量

(setf (eglot--major-modes server) (eglot--ensure-list managed-modes))

同样的你所使用的方法里的eglot-current-server也是通过查找major-mode和这个变量获取当前的lsp服务,并不会继续向上查找继承的major-mode

(cl-find major-mode
           (gethash (eglot--current-project) eglot--servers-by-project)
            :key #'eglot--major-modes
            :test #'memq)

至于你所说的hook eglot-ensure不起作用不知道是啥情况,但我简单测试后是可以的

(defun eglot-ensure@around(oldfunc &rest args)
  (unless (derived-mode-p 'json-mode)
    (apply oldfunc args)))

(advice-add 'eglot-ensure :around 'eglot-ensure@around)

我的方法之所以复杂,是因为除了部分major-mode外,我还需要排除一些minor-mode,这些mode是否激活无法直接hook eglot-ensure,必须在eglot--when-live-buffer内才能判断

我是尽量选择在其他方法调用前尽快跳出,避免过多的方法调用。

但的确有一个副作用,eglot-current-server 本身的调用比较频繁。

这个不应该 eglot 本身做处理么,是否可以提个 bug 给他

算不上bug吧。
1)它的原则应该是:子mode也能使用父mode的lsp。只是出现了,json这种纯文本的mode,居然是继承子程序语言mode这种特殊情况。
2)如果启用了json的lsp,也会避免出现这种场景,只是我自己不喜欢文本类的mode还使用lsp这种重量级后端而已。

有时间仔细看看

https://debbugs.gnu.org/cgi/bugreport.cgi?bug=67463

是不是解决了?