Subject: Re: bug#68246: 30.0.50; Add non-TS mode as extra parent of TS modes

From: João Távora <[email-masked]>
Date: Mon, 15 Jan 2024 15:27:05 +0000

On Mon, Jan 15, 2024 at 2:10 AM Dmitry Gutov <[email-masked]> wrote:

> It's not like we don't have an existing solution for this: if there are
> two different modes to configure, change the settings for both modes, or
> alter two hooks. Less magical and more verbose, but being explicit can
> be good.

I don't think there's anything magical about a base mode.  But I like your
solution too.

> Here's a draft patch of how a "language" could work. It doesn't alter
> every entry, but it is backward compatible.

I think something like this can work, yes.

-      (funcall (alist-get mode major-mode-remap-alist mode))
+      ;; XXX: When there's no mapping for `:<language>', we could also
+      ;; look for a function called `<language>-mode'.
+      (funcall (alist-get mode major-mode-remap-alist (if (keywordp mode)
+                                                          #'fundamental-mode
+                                                        mode)))
+      (when (keywordp mode)             ;Perhaps do that unconditionally.
+        (run-hooks (intern (format "%s-language-hook" (buffer-language)))))
       (unless (eq mode major-mode)

Regarding the "XXX", this is basically the same questions in the
two headings, I think, which is whether to consider the <foo> in existing
<foo>-mode as language.  I think we can do it yes.  Eglot and other
packages [*] have been doing it for quite some time.  It will fail very
rarely, only for major modes outside Emacs (like "tuareg-mode" for
Ocaml) and we can probably fix that in-tree.

The only thing that leaves me some doubts is the 'set-buffer-language'
entry point.  It's a new thing not strictly required.  Normally the
databases are edited (via whatever means) and then the buffer is
reverted for a mode change.  So I don't think we need to introduce
this user convenience just yet (though, like the other user conveniences
you have imagined, I'm not necessarily opposed to it).

Also 'buffer-language' could be 'get-mode-language',  so you don't
have to have an actual buffer handy to get this association.  The
implementation would just be a reverse search in major-mode-remap-alist

Other than that, I think the solution is workable.

The other package [*] that does exactly the same thing as Eglot, and
invented it independently is markdown-mode:

(defun markdown-get-lang-mode (lang)
  "Return major mode that should be used for LANG.
LANG is a string, and the returned major mode is a symbol."
  (cl-find-if
   #'markdown--lang-mode-predicate
   (nconc (list (cdr (assoc lang markdown-code-lang-modes))
                (cdr (assoc (downcase lang) markdown-code-lang-modes)))
          (and (fboundp 'treesit-language-available-p)
               (list (and (treesit-language-available-p (intern lang))
                          (intern (concat lang "-ts-mode")))
                     (and (treesit-language-available-p (intern
(downcase lang)))
                          (intern (concat (downcase lang) "-ts-mode")))))
          (list
           (intern (concat lang "-mode"))
           (intern (concat (downcase lang) "-mode"))))))

It uses this to know what major-mode to use to fontify GitHub style
markdown code blocks (which have a little language cookie after the
three backticks).  Like Eglot's similar code, I think it could be trivially
rewritten if something like your patch were in place.

Bug#68217 is also relevant here.  Eglot calls into markdown-mode.el
to fontify LSP documentation snippets and sometimes the mode picked
by markdown-mode.el to do the fontification is not the same the user
is using for the buffer.  It most clearly should be.  So
get-language-for-mode and get-(preferred)-mode-for-language are two
evidently needed helpers.

João