Subject: bug#77256: Treesit language-at-point

From: Yuan Fu <[email-masked]>
Date: Thu, 10 Apr 2025 01:06:47 -0700

> On Apr 8, 2025, at 11:18 PM, Juri Linkov <[email-masked]> wrote:
> 
>>>> This would make sure the `treesit-language-at-point-function` is good
>>>> enough to implement the default behavior and it makes it possible to use
>>>> `add-function` on `treesit-language-at-point-function`.
>>> 
>>> Unfortunately, it's too late to add a non-nil default value.
>> 
>> Is it?
>> I can't find any code out there that seems to care what is its default
>> value (all users set it but doesn't look at it, AFAICT).

The default value shouldn't matter. What’s the difference between a major-mode-set non-nil value and a default non-nil value?

> 
> This case looks problematic:
> 
>                 ;; LANG can be nil.  We don't want to use the fallback
>                 ;; in `treesit-language-at', so here we call
>                 ;; `treesit-language-at-point-function' directly.
>                 (let* ((lang (and treesit-language-at-point-function
>                                   (funcall treesit-language-at-point-function
>                                            pos)))
> 
> But I believe this whole 'let*' can be replaced by just
> the 'treesit-parsers-at' call.  I hope Yuan could confirm this.

Why is this problematic? And yes, treesit-parsers-at is a good fit.

> 
>>> I already tried to do the same by adding 'forward-sexp-default-function'
>>> to 'forward-sexp-function', but failed spectacularly (bug#70426),
>> 
>> Yes, that one is much harder (it's one of the first I tried to change),
> 
> If no more code outside relies on the default value,
> the default function could be added like this:
> diff --git a/lisp/treesit.el b/lisp/treesit.el
> index 8e57a6dae14..a010ff24afb 100644
> --- a/lisp/treesit.el
> +++ b/lisp/treesit.el
> @@ -165,7 +165,8 @@ treesit-primary-parser
> The primary parser should be a parser that parses the entire buffer, as
> opposed to embedded parsers which parses only part of the buffer.")
> 
> -(defvar-local treesit-language-at-point-function nil
> +(defvar-local treesit-language-at-point-function
> +    #'treesit-language-at-point-default
>   "A function that returns the language at point.
> This is used by `treesit-language-at', which is used by various
> functions to determine which parser to use at point.
> @@ -183,10 +184,13 @@ treesit-language-at
> the most relevant parser (hence language) by their embed level.
> If `treesit-language-at-point-function' is non-nil, return
> the return value of that function instead."
> -  (if treesit-language-at-point-function
> -      (funcall treesit-language-at-point-function position)
> -    (treesit-parser-language
> -     (car (treesit-parsers-at position)))))
> +  (funcall treesit-language-at-point-function position))
> +
> +(defun treesit-language-at-point-default (position)
> +  "Default function for `treesit-language-at-point-function'.
> +Return the deepest parser by embed level."
> +  (treesit-parser-language
> +   (car (treesit-parsers-at position))))
> 
> ;;; Node API supplement
> 

LGTM.

> BTW, I noticed now that indentation of the default value
> in defvar-local is incorrect and differs from defvar and defcustom.
> Here is the fix:
> 
> diff --git a/lisp/subr.el b/lisp/subr.el
> index be847aab28a..164ed2a6f63 100644
> --- a/lisp/subr.el
> +++ b/lisp/subr.el
> @@ -197,7 +197,7 @@ defvar-local
> Like `defvar' but additionally marks the variable as being automatically
> buffer-local wherever it is set.
> \n(fn SYMBOL &optional VALUE DOCSTRING)"
> -  (declare (debug defvar) (doc-string 3) (indent 2))
> +  (declare (debug defvar) (doc-string 3) (indent defun))
>   ;; Can't use backquote here, it's too early in the bootstrap.
>   (let ((value (car-safe args))
>         (docstring (car-safe (cdr-safe args))))