New treesit thing 'sexp-list' (bug#73404)

* lisp/treesit.el (treesit-forward-list): New command.
(treesit-forward-sexp): Check the thing 'text' only when
'treesit-sexp-type-regexp' is nil.
(treesit-forward-sexp-list): New function.
(treesit-navigate-thing): Don't try moving to parent
when thing is 'sexp-list'.
(treesit-major-mode-setup): Set 'forward-sexp-function' to
'treesit-forward-sexp-list' when the 'sexp-list' thing is defined.
This commit is contained in:
Juri Linkov 2024-12-19 09:27:28 +02:00
parent 3281f8615a
commit d930d7f865
2 changed files with 94 additions and 3 deletions

View file

@ -954,6 +954,13 @@ override flag by 'treesit-font-lock-setting-query',
'treesit-font-lock-setting-feature', 'treesit-font-lock-setting-enable',
and 'treesit-font-lock-setting-override'.
*** New treesit thing 'sexp-list'.
Unlike the existing thing 'sexp' that defines both lists and atoms,
'sexp-list' defines only lists to be navigated by 'forward-sexp'.
The new function 'treesit-forward-sexp-list' uses 'sexp-list'
to move across lists. But to move across atoms inside the list
it uses `forward-sexp-default-function'.
+++
** New optional BUFFER argument for 'string-pixel-width'.
If supplied, 'string-pixel-width' will use any face remappings from

View file

@ -2366,6 +2366,25 @@ delimits medium sized statements in the source code. It is,
however, smaller in scope than sentences. This is used by
`treesit-forward-sexp' and friends.")
(defun treesit-forward-list (&optional arg)
"Move forward across a list.
What constitutes a list is determined by `sexp-list' in
`treesit-thing-settings' that usually defines
parentheses-like expressions.
Unlike `forward-sexp', this command moves only across a list,
but not across atoms (such as symbols or words) inside the list.
This command is the tree-sitter variant of `forward-list'. But since
`forward-list' has no \"forward-list-function\" like there is
`forward-sexp-function' for `forward-sexp', this command
can be used on its own.
ARG is described in the docstring of `forward-list'."
(interactive "^p")
(let ((treesit-sexp-type-regexp 'sexp-list))
(treesit-forward-sexp arg)))
(defun treesit-forward-sexp (&optional arg)
"Tree-sitter implementation for `forward-sexp-function'.
@ -2379,12 +2398,18 @@ signal `scan-error' like `forward-sexp' does. If point is already
at top-level, return nil without moving point.
What constitutes as text and source code sexp is determined
by `text' and `sexp' in `treesit-thing-settings'."
by `text' and `sexp' in `treesit-thing-settings'.
There is an alternative implementation in `treesit-forward-sexp-list'
that uses `sexp-list' in `treesit-thing-settings' to move only
across lists, whereas uses `forward-sexp-default-function' to move
across atoms (such as symbols or words) inside the list."
(interactive "^p")
(let ((arg (or arg 1))
(pred (or treesit-sexp-type-regexp 'sexp))
(node-at-point
(treesit-node-at (point) (treesit-language-at (point)))))
(when (null treesit-sexp-type-regexp)
(treesit-node-at (point) (treesit-language-at (point))))))
(or (when (and node-at-point
;; Make sure point is strictly inside node.
(< (treesit-node-start node-at-point)
@ -2408,6 +2433,60 @@ by `text' and `sexp' in `treesit-thing-settings'."
(treesit-node-start boundary)
(treesit-node-end boundary)))))))
(defun treesit-forward-sexp-list (&optional arg)
"Alternative tree-sitter implementation for `forward-sexp-function'.
Whereas `treesit-forward-sexp' moves across both lists and atoms
using `sexp' in `treesit-thing-settings', this function uses
`sexp-list' in `treesit-thing-settings' to move only across lists.
But to move across atoms (such as symbols or words) inside the list
it uses `forward-sexp-default-function' as long as it doesn't go
outside of the boundaries of the current list.
ARG is described in the docstring of `forward-sexp-function'."
(interactive "^p")
(let* ((arg (or arg 1))
(pred 'sexp-list)
(default-pos
(condition-case _
(save-excursion
(forward-sexp-default-function arg)
(point))
(scan-error nil)))
(default-pos (unless (eq (point) default-pos) default-pos))
(sibling-pos
(when default-pos
(save-excursion
(and (if (> arg 0)
(treesit-end-of-thing pred (abs arg) 'restricted)
(treesit-beginning-of-thing pred (abs arg) 'restricted))
(point)))))
(sibling (when sibling-pos
(if (> arg 0)
(treesit-thing-prev sibling-pos pred)
(treesit-thing-next sibling-pos pred))))
(sibling (when (and sibling
(if (> arg 0)
(<= (point) (treesit-node-start sibling))
(>= (point) (treesit-node-end sibling))))
sibling))
(current-thing (when default-pos
(treesit-thing-at (point) pred t))))
;; 'forward-sexp-default-function' should not go out of the current thing,
;; neither go inside the next thing or go over the next thing
(or (when (and default-pos
(or (null current-thing)
(if (> arg 0)
(< default-pos (treesit-node-end current-thing))
(> default-pos (treesit-node-start current-thing))))
(or (null sibling)
(if (> arg 0)
(<= default-pos (treesit-node-start sibling))
(>= default-pos (treesit-node-end sibling)))))
(goto-char default-pos))
(treesit-forward-list arg))))
(defun treesit-transpose-sexps (&optional arg)
"Tree-sitter `transpose-sexps' function.
ARG is the same as in `transpose-sexps'.
@ -2857,7 +2936,9 @@ function is called recursively."
(if (eq tactic 'restricted)
(setq pos (funcall
advance
(cond ((and (null next) (null prev)) parent)
(cond ((and (null next) (null prev)
(not (eq thing 'sexp-list)))
parent)
((> arg 0) next)
(t prev))))
;; For `nested', it's a bit more work:
@ -3254,6 +3335,9 @@ before calling this function."
(setq-local forward-sexp-function #'treesit-forward-sexp)
(setq-local transpose-sexps-function #'treesit-transpose-sexps))
(when (treesit-thing-defined-p 'sexp-list nil)
(setq-local forward-sexp-function #'treesit-forward-sexp-list))
(when (treesit-thing-defined-p 'sentence nil)
(setq-local forward-sentence-function #'treesit-forward-sentence))