Subject: bug#72343: [PATCH] Fix eglot-server-programs for TeX modes

From: João Távora <[email-masked]>
Date: Tue, 6 Aug 2024 23:52:08 +0100

On Tue, Aug 6, 2024 at 7:48PM Augusto Stoffel <[email-masked]> wrote:

> I know about the list, but unfortunately "tex" as a language id makes no
> sense.  It's like saying a program is written in "lisp" without saying
> which Lisp dialect.

In that example, the "lisp" identifier is probably enough to derive that
the file in question is a tree of Lisp forms, much like lisp-data-mode
treats Lisp.

> Again, every TeX program belongs to a specific dialect: plain TeX, LaTeX
> and ConTeXt being the main ones.  Accordingly, tex-mode is not a real
> major mode; it's just an "abstract base mode" from which the actual TeX
> modes derive (see the docstring for details).

OK.  That doesn't seem to necessitate that the tex-mode is a parent of
latex-mode or plain-tex-mode.  [Also, I see no need to write "again" for
a point that you've just made once].

> except for the
> caveat that ultimately the user might know better and it's useful to
> rely on the user's choice of major mode / language id.

How so a "caveat" if eglot-server-programs is a user-customizable
variable where the user can explicitly set whatever languageId she
wants??

> I didn't ask but I don't see what could go wrong, given that texlab is
> for latex and bibtex only.

It would be safer to ask.  As far as I am concerned, currently we send
"tex" to texlab and it likes it, or at least doesn't dislike it.  For
all we know, or at least, I know.  "tex" is in the LSP spec, "plain-tex" is
not.  Then I suggest -- for this specific problem:

1. Asking for the spec to correct itself
2. Interpreting "tex" as really "plain-tex" in your server.  You should
   probably be doing that anyway for old Eglot versions or  any client
   who follows the spec.
3. Ignoring my advice and sending "plain-tex" from Eglot anyway
(i.e. installing your patch without :language-id)

For the _other_ orthogonal problem, I suggest:

1. Doing the simplification in eglot--lookup-mode, and then make the
left-hand-side list be like

(latex-mode (plain-tex-mode :language-id "tex") ; see previous problem
            also-derived-from-tex-mode
            tex-mode
            not-derived-from-tex-mode)

Note that tex-mode is kept there, so this is compatible with any
tex-mode derivations we don't know of.  Some testing required, of
course.  I can perform some of it for C/C++ modes.

This patch is after my sig.  It also fixes a broken docstring.

2. Doing the ranking in eglot--languageID
3. fixing tex-mode's relation with its children modes

João

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 5845aff39b7..beff9b17a25 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -292,7 +292,7 @@ eglot-server-programs
     (scala-mode . ,(eglot-alternatives
                     '("metals" "metals-emacs")))
     (racket-mode . ("racket" "-l" "racket-langserver"))
-    ((tex-mode context-mode texinfo-mode bibtex-mode)
+    ((latex-mode (plain-tex-mode :language-id "tex") tex-mode
context-mode texinfo-mode bibtex-mode)
      . ,(eglot-alternatives '("digestif" "texlab")))
     (erlang-mode . ("erlang_ls" "--transport" "stdio"))
     ((yaml-ts-mode yaml-mode) . ("yaml-language-server" "--stdio"))
@@ -1238,28 +1238,23 @@ eglot--lookup-mode
   "Lookup `eglot-server-programs' for MODE.
 Return (LANGUAGES . CONTACT-PROXY).

-MANAGED-MODES is a list with MODE as its first element.
-Subsequent elements are other major modes also potentially
-managed by the server that is to manage MODE.
-
-LANGUAGE-IDS is a list of the same length as MANAGED-MODES.  Each
-elem is derived from the corresponding mode name, if not
-specified in `eglot-server-programs' (which see).
+LANGUAGES is a list ((MANAGED-MODE . LANGUAGE-ID) ...).  MANAGED-MODE is
+a major mode also potentially managed by the server that is to manage
+MODE.  LANGUAGE-ID is string identifying the language to the LSP server.
+It's derived from the corresponding mode name, or explicitly specified
+in `eglot-server-programs' (which see).

 CONTACT-PROXY is the value of the corresponding
 `eglot-server-programs' entry."
-  (cl-flet ((languages (main-mode-sym specs)
-              (let* ((res
-                      (mapcar (jsonrpc-lambda (sym &key language-id
&allow-other-keys)
-                                (cons sym
-                                      (or language-id
-                                          (or (get sym 'eglot-language-id)
-                                              (replace-regexp-in-string
-                                               "\\(?:-ts\\)?-mode$" ""
-                                               (symbol-name sym))))))
-                              specs))
-                     (head (cl-find main-mode-sym res :key #'car)))
-                (cons head (delq head res)))))
+  (cl-flet ((languages (specs)
+              (mapcar (jsonrpc-lambda (sym &key language-id &allow-other-keys)
+                        (cons sym
+                              (or language-id
+                                  (or (get sym 'eglot-language-id)
+                                      (replace-regexp-in-string
+                                       "\\(?:-ts\\)?-mode$" ""
+                                       (symbol-name sym))))))
+                      specs)))
     (cl-loop
      for (modes . contact) in eglot-server-programs
      for specs = (mapcar #'eglot--ensure-list
@@ -1268,7 +1263,7 @@ eglot--lookup-mode
      thereis (cl-some (lambda (spec)
                         (cl-destructuring-bind (sym &key
&allow-other-keys) spec
                           (and (provided-mode-derived-p mode sym)
-                               (cons (languages sym specs) contact))))
+                               (cons (languages specs) contact))))
                       specs))))

 (defun eglot--guess-contact (&optional interactive)