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

From: Augusto Stoffel <[email-masked]>
Date: Wed, 07 Aug 2024 09:02:32 +0200

On Tue,  6 Aug 2024 at 23:52, João Távora wrote:

> 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.

Sure, but how is that useful information for a language server?

>> 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??

Relegating to users the task of debugging language ids doesn't sound
very user-friendly...

>> 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.

Yes, and Digestif is also fine with it.  Clearly when seeing the "tex"
language id, both servers fall back to "latex", which is by far the most
common TeX dialect.

(For the record, the only thing that's currently broken in the
Eglot-Digestif interaction is the plain TeX case.)

>  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

Productivity-wise I don't have a very good experience talking to them
(even less so than on the Emacs mailing lists...), so I will pass on
that one.

> 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.

This would definitely be wrong, since editors (including the current
version of Eglot but also others e.g. vim), send out the "tex" language
id when they mean "latex".

What I _could_ do is to interpret "tex" to mean "some kind of tex" and
trigger a heuristic to guess the specific dialect.  (This is a
TODO-comment since the first versions of Digestif, so the "could" in the
previous sentence has a purely hypothetical connotation.)

> 3. Ignoring my advice and sending "plain-tex" from Eglot anyway
> (i.e. installing your patch without :language-id)

You're clearly against this so it is not going to happen, but I hope I
managed to explain to you why I would have done it.

> 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)

Since you want to send "tex" language id for plain tex, the following
suffices:

    ((latex-mode 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)

I have no opinion on that one, except to say that it looks really
complicated, although that's probably out of necessity.