Make use of the new pred shapes in treesit.el

treesit-search-forward and friends now accept more shapes for PRED,
make use of it in navigation functions.

* lisp/treesit.el (treesit-node-top-level): Use treesit-node-match-p.
(treesit--thing-unpack-pattern): Remove function.
(treesit-beginning-of-thing)
(treesit-end-of-thing): Remove PRED argument.
(treesit--things-around): Remove PRED argument, use
treesit-node-match-p.
(treesit--top-level-thing): Remove function.
(treesit--navigate-thing): Remove PRED argument.
(treesit-thing-at-point): Update docstring, don't unpack PATTERN.

* test/src/treesit-tests.el:
(treesit--ert-test-defun-navigation): Don't unpack pattern.
This commit is contained in:
Yuan Fu 2023-04-13 19:48:57 -07:00
parent a4de6d8dd3
commit 293029458c
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
2 changed files with 28 additions and 62 deletions

View file

@ -88,6 +88,7 @@
(declare-function treesit-search-forward "treesit.c")
(declare-function treesit-induce-sparse-tree "treesit.c")
(declare-function treesit-subtree-stat "treesit.c")
(declare-function treesit-node-match-p "treesit.c")
(declare-function treesit-available-p "treesit.c")
@ -245,21 +246,19 @@ is nil, try to guess the language at BEG using `treesit-language-at'."
Specifically, return the highest parent of NODE that has the same
type as it. If no such parent exists, return nil.
If PRED is non-nil, match each parent's type with PRED as a
regexp, rather than using NODE's type. PRED can also be a
function that takes the node as an argument, and return
non-nil/nil for match/no match.
If PRED is non-nil, match each parent's type with PRED rather
than using NODE's type. PRED can also be a predicate function,
and more. See `treesit-things-definition' for detail.
If INCLUDE-NODE is non-nil, return NODE if it satisfies PRED."
(let ((pred (or pred (treesit-node-type node)))
(let ((pred (or pred (rx-to-string
`(bos ,(treesit-node-type node) eos))))
(result nil))
(cl-loop for cursor = (if include-node node
(treesit-node-parent node))
then (treesit-node-parent cursor)
while cursor
if (if (stringp pred)
(string-match-p pred (treesit-node-type cursor))
(funcall pred cursor))
if (treesit-node-match-p cursor pred)
do (setq result cursor))
result))
@ -1887,17 +1886,6 @@ nil.")
"The delimiter used to connect several defun names.
This is used in `treesit-add-log-current-defun'.")
(defsubst treesit--thing-unpack-pattern (pattern)
"Unpack PATTERN in the shape of `treesit-defun-type-regexp'.
Basically,
(unpack REGEXP) = (REGEXP . nil)
(unpack (REGEXP . PRED)) = (REGEXP . PRED)"
(if (consp pattern)
pattern
(cons pattern nil)))
(defun treesit-beginning-of-thing (pattern &optional arg tactic)
"Like `beginning-of-defun', but generalized into things.
@ -1916,10 +1904,8 @@ should there be one. If omitted, TACTIC is considered to be
Return non-nil if successfully moved, nil otherwise."
(pcase-let* ((arg (or arg 1))
(`(,regexp . ,pred) (treesit--thing-unpack-pattern
pattern))
(dest (treesit--navigate-thing
(point) (- arg) 'beg regexp pred tactic)))
(point) (- arg) 'beg pattern tactic)))
(when dest
(goto-char dest))))
@ -1941,10 +1927,8 @@ should there be one. If omitted, TACTIC is considered to be
Return non-nil if successfully moved, nil otherwise."
(pcase-let* ((arg (or arg 1))
(`(,regexp . ,pred) (treesit--thing-unpack-pattern
pattern))
(dest (treesit--navigate-thing
(point) arg 'end regexp pred tactic)))
(point) arg 'end pattern tactic)))
(when dest
(goto-char dest))))
@ -2069,7 +2053,7 @@ the current line if the beginning of the defun is indented."
;; parent:
;; 1. node covers pos
;; 2. smallest such node
(defun treesit--things-around (pos regexp &optional pred)
(defun treesit--things-around (pos regexp)
"Return the previous, next, and parent thing around POS.
Return a list of (PREV NEXT PARENT), where PREV and NEXT are
@ -2077,7 +2061,8 @@ previous and next sibling things around POS, and PARENT is the
parent thing surrounding POS. All of three could be nil if no
sound things exists.
REGEXP and PRED are the same as in `treesit-thing-at-point'."
REGEXP can be a regexp, a predicate function, and more. See
`treesit-things-definition' for details."
(let* ((node (treesit-node-at pos))
(result (list nil nil nil)))
;; 1. Find previous and next sibling defuns.
@ -2100,9 +2085,7 @@ REGEXP and PRED are the same as in `treesit-thing-at-point'."
when node
do (let ((cursor node)
(iter-pred (lambda (node)
(and (string-match-p
regexp (treesit-node-type node))
(or (null pred) (funcall pred node))
(and (treesit-node-match-p node regexp)
(funcall pos-pred node)))))
;; Find the node just before/after POS to start searching.
(save-excursion
@ -2120,9 +2103,7 @@ REGEXP and PRED are the same as in `treesit-thing-at-point'."
;; 2. Find the parent defun.
(let ((cursor (or (nth 0 result) (nth 1 result) node))
(iter-pred (lambda (node)
(and (string-match-p
regexp (treesit-node-type node))
(or (null pred) (funcall pred node))
(and (treesit-node-match-p node regexp)
(not (treesit-node-eq node (nth 0 result)))
(not (treesit-node-eq node (nth 1 result)))
(< (treesit-node-start node)
@ -2132,15 +2113,6 @@ REGEXP and PRED are the same as in `treesit-thing-at-point'."
(treesit-parent-until cursor iter-pred)))
result))
(defun treesit--top-level-thing (node regexp &optional pred)
"Return the top-level parent thing of NODE.
REGEXP and PRED are the same as in `treesit-thing-at-point'."
(treesit-node-top-level
node (lambda (node)
(and (string-match-p regexp (treesit-node-type node))
(or (null pred) (funcall pred node))))
t))
;; The basic idea for nested defun navigation is that we first try to
;; move across sibling defuns in the same level, if no more siblings
;; exist, we move to parents's beg/end, rinse and repeat. We never
@ -2168,7 +2140,7 @@ REGEXP and PRED are the same as in `treesit-thing-at-point'."
;; -> Obviously we don't want to go to parent's end, instead, we
;; want to go to parent's prev-sibling's end. Again, we recurse
;; in the function to do that.
(defun treesit--navigate-thing (pos arg side regexp &optional pred tactic recursing)
(defun treesit--navigate-thing (pos arg side regexp &optional tactic recursing)
"Navigate thing ARG steps from POS.
If ARG is positive, move forward that many steps, if negative,
@ -2179,7 +2151,8 @@ This function doesn't actually move point, it just returns the
position it would move to. If there aren't enough things to move
across, return nil.
REGEXP and PRED are the same as in `treesit-thing-at-point'.
REGEXP can be a regexp, a predicate function, and more. See
`treesit-things-definition' for detail.
TACTIC determines how does this function move between things. It
can be `nested', `top-level', `restricted', or nil. `nested'
@ -2208,14 +2181,13 @@ function is called recursively."
(while (> counter 0)
(pcase-let
((`(,prev ,next ,parent)
(treesit--things-around pos regexp pred)))
(treesit--things-around pos regexp)))
;; When PARENT is nil, nested and top-level are the same, if
;; there is a PARENT, make PARENT to be the top-level parent
;; and pretend there is no nested PREV and NEXT.
(when (and (eq tactic 'top-level)
parent)
(setq parent (treesit--top-level-thing
parent regexp pred)
(setq parent (treesit-node-top-level parent regexp t)
prev nil
next nil))
;; If TACTIC is `restricted', the implementation is very simple.
@ -2247,7 +2219,7 @@ function is called recursively."
;; the end of next before recurring.)
(setq pos (or (treesit--navigate-thing
(treesit-node-end (or next parent))
1 'beg regexp pred tactic t)
1 'beg regexp tactic t)
(throw 'term nil)))
;; Normal case.
(setq pos (funcall advance (or next parent))))
@ -2259,7 +2231,7 @@ function is called recursively."
;; Special case: go to prev end-of-defun.
(setq pos (or (treesit--navigate-thing
(treesit-node-start (or prev parent))
-1 'end regexp pred tactic t)
-1 'end regexp tactic t)
(throw 'term nil)))
;; Normal case.
(setq pos (funcall advance (or prev parent))))))
@ -2272,18 +2244,14 @@ function is called recursively."
(defun treesit-thing-at-point (pattern tactic)
"Return the thing node at point or nil if none is found.
\"Thing\" is defined by PATTERN, which can be either a string
REGEXP or a cons cell (REGEXP . PRED): if a node's type matches
REGEXP, it is a thing. The \"thing\" could be further restricted
by PRED: if non-nil, PRED should be a function that takes a node
and returns t if the node is a \"thing\", and nil if not.
\"Thing\" is defined by PATTERN, which can be a regexp, a
predication function, and more, see `treesit-things-definition'
for detail.
Return the top-level defun if TACTIC is `top-level', return the
immediate parent thing if TACTIC is `nested'."
(pcase-let* ((`(,regexp . ,pred)
(treesit--thing-unpack-pattern pattern))
(`(,_ ,next ,parent)
(treesit--things-around (point) regexp pred))
(pcase-let* ((`(,_ ,next ,parent)
(treesit--things-around (point) pattern))
;; If point is at the beginning of a thing, we
;; prioritize that thing over the parent in nested
;; mode.
@ -2291,7 +2259,7 @@ immediate parent thing if TACTIC is `nested'."
next)
parent)))
(if (eq tactic 'top-level)
(treesit--top-level-thing node regexp pred)
(treesit-node-top-level node pattern t)
node)))
(defun treesit-defun-at-point ()

View file

@ -916,8 +916,6 @@ and \"]\"."
collect
(cl-loop for pos in record
collect (alist-get pos marker-alist))))
(`(,regexp . ,pred) (treesit--thing-unpack-pattern
treesit-defun-type-regexp))
;; Collect positions each function returns.
(positions
(treesit--ert-collect-positions
@ -929,7 +927,7 @@ and \"]\"."
(if-let ((pos (funcall
#'treesit--navigate-thing
(point) (car conf) (cdr conf)
regexp pred tactic)))
treesit-defun-type-regexp tactic)))
(save-excursion
(goto-char pos)
(funcall treesit-defun-skipper)