Function: treesit--navigate-thing

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

Signature

(treesit--navigate-thing POS ARG SIDE REGEXP &optional PRED 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.

REGEXP and PRED are the same as in treesit-thing-at-point.

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

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 regexp &optional pred 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.

REGEXP and PRED are the same as in `treesit-thing-at-point'.

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)
        (pcase-let
            ((`(,prev ,next ,parent)
              (treesit--things-around pos regexp pred)))
          ;; 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 treesit-defun-tactic 'top-level)
                     parent)
            (setq parent (treesit--top-level-thing
                          parent regexp pred)
                  prev nil
                  next nil))
          ;; 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 regexp pred 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 regexp pred 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)))