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

From: Stefan Monnier <[email-masked]>
Date: Thu, 18 Jan 2024 09:17:15 -0500

> away).  The language-specific version of major-mode-remap-alist looks
> necessary after all.

It doesn't have to be specifically about languages.
It can just be a "default" set of "major mode" remappings.

>>> @@ -3206,10 +3209,10 @@ interpreter-mode-alist
>>>        ("emacs" . emacs-lisp-mode)))
>>>     "Alist mapping interpreter names to major modes.
>>>   This is used for files whose first lines match `auto-mode-interpreter-regexp'.
>>> -Each element looks like (REGEXP . MODE).
>>> +Each element looks like (REGEXP . MODE-OR-LANGUAGE).
>>>   If REGEXP matches the entire name (minus any directory part) of
>>>   the interpreter specified in the first line of a script, enable
>>> -major mode MODE.
>>> +MODE-OR-LANGUAGE.
>> There's a similar need for "content type" rather than "language".  If we
>> want to mention "language" we should also take the opportunity to
>> mention other related categorizations like "content type".
> Are "content type" and "language" going to be different things?
> They seem the same to me.

I think there's the same kind of difference between "language" and
"content type" as between "language" and "major mode" :-)

> OTOH, the major mode can only run the language hook, I think, if any major
> mode can correspond only to one language.

Not so.  A major mode can easily do

    (run-mode-hooks (compute-the-hook))

> Though I suppose if set-auto-mode-0 saves the currently "detected"
> language somewhere, the major mode definitions could pick it up and
> call the corresponding hook.

Major modes are not activated solely via `set-auto-mode-0`, so relying
on that is a crutch/hack, not something on which to base a design.

>> I'm not comfortable enshrining the "-ts-mode" convention here.
> We can still go the "strict" approach, where when no language is assigned,
> we don't try to guess it.

I think the `<LANG>-mode` heuristic is acceptable, because it's been
*the* convention used in Emacs.

>> Also I think if we want a `buffer-language` function, it should not rely
>> on how the mode was installed (e.g. `set-auto-mode--last`) but only on
>> the major mode itself, i.e. something like
>>      (defun buffer-language ()
>>        (or buffer-language
> Where would the buffer-language variable be set, if not inside
> set-auto-mode-*?

In the major mode?

>>            (some heuristic based on major-mode and/or derived-modes)))
> If we're sure we don't want several languages to be able to refer to the
> same major mode...

A major mode can

    (setq major-mode ...)

If/when such "generic" major modes become a thing, and the `(setq major-mode ...)`
hack becomes too inconvenient, we can devise a better solution
(e.g. extending/tweaking the way `derived-mode-*` work).

>> [ Of course, I already mentioned that I also suspect that there can/will
>>    be sometimes several languages (or none).  ]
> I'm not clear on this. You mentioned complex cases - like an xml inside an
> archive? But depending on the usage, only one of the languages might be
> "active" at a given time.

But depending on what "the language/type/mode" is used for, we may not
care really about which language/type/mode is "active" but about which
languages/types/modes are applicable (e.g. for `.dir-locals.el`).

>>> +(defun set-buffer-language (language)
>>> +  "Set the language of the current buffer.
>>> +And switch the major mode appropriately."
>>> +  (interactive
>>> +   (list (let* ((ct (mapcan
>>> +                     (lambda (pair) (and (keywordp (car pair))
>>> +                                    (list (symbol-name (car pair)))))
>>> +                     major-mode-remap-alist))
>>> +                (lang (completing-read "Language: " ct)))
>>> +           (and lang (intern lang)))))
>>> +  (set-auto-mode-0 language))
>> I see several issues with this function (name and implementation), but
>> I wonder when we'd ever need such a thing.
>
> It seemed like a missed opportunity not to provide a more high-level command
> to switch to a specific language for the buffer. E.g. how we sometimes use
> 'M-x foo-major-mode' when a file type's been misdetected, or the buffer is
> non-file-visiting (perhaps very temporary).
>
> A command which does this with exhaustive completion across the configured
> languages seems handy. At least that's my impression from briefly testing
> it out.

We can do the same with major modes, of course (just `mapatom` and filter out
the non-major modes), so feel free to add such a command, but it doesn't
seem like offering it for "languages" is particularly more useful than
offering it for "major modes".

> Also, get-current-mode-for-language can be implemented in terms of
> set-buffer-language (see my earlier email to Joao).

That seems to be a roundabout way to go about it.
`get-current-mode-for-language/type/mode` should be used by
`set-auto-mode` rather than other way around, no?

> <LANG>-mode is lexically indistinguishable from <NONLANG>-mode. If we used
> the names like <LANG>-lang, at least one could tell whether one of the
> parents of a given <foo>-mode is a language.

Other than for Eglot, where does this distinction matter?

>> Another issue I see if we don't use something like
>> `derived-mode-add-parents` is that all the various places where we use
>> mode-indexing, such as `.dir-locals.el`, `ffap`, YASnippet, etc... will
>> need to be extended with a way to use "languages" as well, and then we
>> also need to define a sane precedence between settings that apply to
>> a given mode and settings that apply to a given language (setting for
>> `js-ts-mode` should presumably take precedence over settings for
>> `:js` which should take precedence over settings for `prog-mode`).
> That's a good point: if "languages" as a separate notion gets added, it
> would make sense to use them in more places (not 100% necessary, but good
> for consistency). With the associated complexity that you mention.

And if it's not merged into the same hierarchy as major modes, how do
you get `:js` (i.e. "language") to be sometimes higher-precedence and
sometimes lower precedence than a mode?

        Stefan