Function: treesit-navigate-thing

treesit-navigate-thing is a byte-compiled function defined in treesit.el.gz.

Signature

(treesit-navigate-thing POS ARG SIDE THING &optional TACTIC RECURSING)

Documentation

Navigate thing ARG steps from POS.

If ARG is positive, move forward that many steps, if negative, move backward. If SIDE is beg, stop at the beginning of a thing, if SIDE is end, stop at the end.

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.

THING can be a regexp, a predicate function, and more. See treesit-thing-settings for details.

TACTIC determines how does this function move between things. It can be nested, top-level(var)/top-level(fun), restricted, or nil. nested means normal nested navigation: try to move to siblings first, and if there aren't enough siblings, move to the parent and its siblings. top-level(var)/top-level(fun) means only consider top-level things, and nested things are ignored. restricted means movement is restricted inside the thing that encloses POS (i.e., parent), should there be one. If omitted, TACTIC is considered to be nested.

RECURSING is an internal parameter, if non-nil, it means this function is called recursively.

View in manual

Probably introduced at or before Emacs version 30.1.

Source Code

;; Defined in /usr/src/emacs/lisp/treesit.el.gz
;; 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
;; move into a defun, only outwards.
;;
;; Let me describe roughly what does this function do: there are four
;; possible operations: prev-beg, next-end, prev-end, next-beg, and
;; each of (prev-sibling next-sibling and parent) could exist or not
;; exist.  So there are 4 times 8 = 32 situations.
;;
;; I'll only describe the situation when we go backward (prev-beg &
;; prev-end), and consider only prev-sibling & parent. Deriving the
;; reverse situations is left as an exercise for the reader.
;;
;; prev-beg (easy case):
;; 1. prev-sibling or parent exists
;;    -> go the prev-sibling/parent's beg
;;
;; prev-end (tricky):
;; 1. prev-sibling exists
;;    -> If we are already at prev-sibling's end, we need to go one
;;       step further, either to prev-prev-sibling's end, or parent's
;;       prev-sibling's end, etc.
;; 2. prev-sibling is nil but parent exists
;;    -> 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 thing &optional tactic recursing)
  "Navigate thing ARG steps from POS.

If ARG is positive, move forward that many steps, if negative,
move backward.  If SIDE is `beg', stop at the beginning of a
thing, if SIDE is `end', stop at the end.

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.

THING can be a regexp, a predicate function, and more.  See
`treesit-thing-settings' for details.

TACTIC determines how does this function move between things.  It
can be `nested', `top-level', `restricted', or nil.  `nested'
means normal nested navigation: try to move to siblings first,
and if there aren't enough siblings, move to the parent and its
siblings.  `top-level' means only consider top-level things, and
nested things are ignored.  `restricted' means movement is
restricted inside the thing that encloses POS (i.e., parent),
should there be one.  If omitted, TACTIC is considered to be
`nested'.

RECURSING is an internal parameter, if non-nil, it means this
function is called recursively."
  (pcase-let*
      ((counter (abs arg))
       ;; Move POS to the beg/end of NODE.  If NODE is nil, terminate.
       ;; Return the position we moved to.
       (advance (lambda (node)
                  (let ((dest (pcase side
                                ('beg (treesit-node-start node))
                                ('end (treesit-node-end node)))))
                    (if (null dest)
                        (throw 'term nil)
                      dest)))))
    (catch 'term
      (while (> counter 0)
        (let ((prev (treesit-thing-prev pos thing))
              (next (treesit-thing-next pos thing))
              (parent (treesit-thing-at pos thing t)))
          (when (and parent prev
                     (not (treesit-node-enclosed-p prev parent)))
            (setq prev nil))
          (when (and parent next
                     (not (treesit-node-enclosed-p next parent)))
            (setq next nil))
          ;; 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-node-top-level parent thing t)
                  prev nil
                  next nil))
          ;; If TACTIC is `restricted', the implementation is simple.
          ;; In principle we don't go to parent's beg/end for
          ;; `restricted' tactic, but if the parent is a "leaf thing"
          ;; (doesn't have any child "thing" inside it), then we can
          ;; move to the beg/end of it (bug#68899).
          (if (eq tactic 'restricted)
              (setq pos (funcall
                         advance
                         (cond ((and (null next) (null prev)) parent)
                               ((> arg 0) next)
                               (t prev))))
            ;; For `nested', it's a bit more work:
            ;; Move...
            (if (> arg 0)
                ;; ...forward.
                (if (and (eq side 'beg)
                         ;; Should we skip the defun (recurse)?
                         (cond (next (and (not recursing) ; [1] (see below)
                                          (eq pos (funcall advance next))))
                               (parent t))) ; [2]
                    ;; Special case: go to next beg-of-defun, but point
                    ;; is already on beg-of-defun.  Set POS to the end
                    ;; of next-sib/parent defun, and run one more step.
                    ;; If there is a next-sib defun, we only need to
                    ;; recurse once, so we don't need to recurse if we
                    ;; are already recursing [1]. If there is no
                    ;; next-sib but a parent, keep stepping out
                    ;; (recursing) until we got out of the parents until
                    ;; (1) there is a next sibling defun, or (2) no more
                    ;; parents [2].
                    ;;
                    ;; If point on beg-of-defun but we are already
                    ;; recurring, that doesn't count as special case,
                    ;; because we have already made progress (by moving
                    ;; the end of next before recurring.)
                    (setq pos (or (treesit-navigate-thing
                                   (treesit-node-end (or next parent))
                                   1 'beg thing tactic t)
                                  (throw 'term nil)))
                  ;; Normal case.
                  (setq pos (funcall advance (or next parent))))
              ;; ...backward.
              (if (and (eq side 'end)
                       (cond (prev (and (not recursing)
                                        (eq pos (funcall advance prev))))
                             (parent t)))
                  ;; Special case: go to prev end-of-defun.
                  (setq pos (or (treesit-navigate-thing
                                 (treesit-node-start (or prev parent))
                                 -1 'end thing tactic t)
                                (throw 'term nil)))
                ;; Normal case.
                (setq pos (funcall advance (or prev parent))))))
          ;; A successful step! Decrement counter.
          (cl-decf counter))))
    ;; Counter equal to 0 means we successfully stepped ARG steps.
    (if (eq counter 0) pos nil)))