From e5821b3c3e8aac9c7398adf0bd9d72e7fb4aca3e Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Mon, 9 Jun 2025 21:41:17 -0700 Subject: [PATCH] Add new tactic to treesit-navigate-thing (bug#78703) * doc/emacs/programs.texi (Moving by Defuns): * doc/lispref/parsing.texi (User-defined Things): Describe the new tactic. * lisp/treesit.el (treesit-navigate-thing): Add new tactic. (cherry picked from commit 0a629abfbbdb34efcefe7b7d6f933bc7d90b5501) --- doc/emacs/programs.texi | 18 +++++++++++------- doc/lispref/parsing.texi | 20 +++++++++++--------- lisp/treesit.el | 23 ++++++++++++++--------- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/doc/emacs/programs.texi b/doc/emacs/programs.texi index f8ad7596b4e..609fc3ee4a2 100644 --- a/doc/emacs/programs.texi +++ b/doc/emacs/programs.texi @@ -241,13 +241,17 @@ bindings for that purpose. @cindex nested defuns @vindex treesit-defun-tactic Some programming languages supported @dfn{nested defuns}, whereby a -defun (such as a function or a method or a class) can be defined -inside (i.e., as part of the body) of another defun. The commands -described above by default find the beginning and the end of the -@emph{innermost} defun around point. Major modes based on the -tree-sitter library provide control of this behavior: if the variable -@code{treesit-defun-tactic} is set to the value @code{top-level}, the -defun commands will find the @emph{outermost} defuns instead. +defun (such as a function or a method or a class) can be defined inside +(i.e., as part of the body) of another defun. The commands described +above by default find the beginning and the end of the @emph{innermost} +defun around point. Major modes based on the tree-sitter library +provide control of this behavior: by default, the value of +@code{treesit-defun-tactic} is set to @code{nested}; if it's set +to@code{top-level}, the defun commands will find the @emph{outermost} +defuns instead; if the value is set to @code{parent-first}, the defun +command always tries to move out of the current enclosing defun rather +than moving to the previous or next defun around point; if there's no +enclosing defun, it moves to the previous or next defun. @node Moving by Sentences @subsection Moving by Sentences diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index f155bdbd1dd..922ba217920 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi @@ -1787,15 +1787,17 @@ Like in @code{treesit-thing-prev}, @var{thing} can be a thing symbol defined in @code{treesit-thing-settings}, or a predicate. @var{tactic} determines how this function moves between things. It can -be @code{nested}, @code{top-level}, @code{restricted}, or @code{nil}. -@code{nested} or @code{nil} means normal nested navigation: first try to -move across siblings; if there aren't any siblings left in the current -level, move to the parent, then its siblings, and so on. -@code{top-level} means only navigate across top-level things and ignore -nested things. @code{restricted} means movement is restricted within -the thing that encloses @var{position}, if there is such a thing. This -tactic is useful for commands that want to stop at the current nesting -level and not move up. +be @code{nested}, @code{top-level}, @code{restricted}, +@code{parent-first}, or @code{nil}. @code{nested} or @code{nil} means +normal nested navigation: first try to move across siblings; if there +aren't any siblings left in the current level, move to the parent, then +its siblings, and so on. @code{top-level} means only navigate across +top-level things and ignore nested things. @code{restricted} means +movement is restricted within the thing that encloses @var{position}, if +there is such a thing. This tactic is useful for commands that want to +stop at the current nesting level and not move up. @var{parent-first} +means move to the parent if there is one; and move to siblings if +there's no parent. @end defun @defun treesit-thing-at position thing &optional strict diff --git a/lisp/treesit.el b/lisp/treesit.el index 9526a075296..5d16433c0e5 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -3687,15 +3687,15 @@ 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'. +TACTIC determines how does this function move between things. It can be +`nested', `top-level', `restricted', `parent-first' 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. `parent' means +move to the parent if there is one; and move to siblings if there's no +parent. If omitted, TACTIC is considered to be `nested'. RECURSING is an internal parameter, if non-nil, it means this function is called recursively." @@ -3729,6 +3729,11 @@ function is called recursively." (setq parent (treesit-node-top-level parent thing t) prev nil next nil)) + ;; When PARENT is nil, `nested' and `parent-first' are the + ;; same, if there is a PARENT, pretend there is no nested PREV + ;; and NEXT so the following code moves to the parent. + (when (and (eq tactic 'parent-first) parent) + (setq 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" -- 2.39.5