* doc/lispref/parsing.texi (Retrieving Node): Update manual.
* lisp/treesit.el (treesit-node-at): Change semantic. It tries to
return the node that a user would expect in various circumstances.
* test/src/treesit-tests.el (treesit-node-at): New test.
@cindex syntax tree, retrieving nodes
@defun treesit-node-at pos &optional parser-or-lang named
-This function returns the @emph{smallest} node that starts at or after
-the buffer position @var{pos}. In other words, the start of the node
-is greater or equal to @var{pos}.
+This function returns a @emph{leaf} node at buffer position @var{pos}.
+A leaf node is a node that doesn't have any child nodes.
+
+This function tries to return a node whose span covers @var{pos}: the
+node's beginning is less or equal to @var{pos}, and the node's end is
+greater or equal to @var{pos}.
+
+But if no leaf node's span covers @var{pos} (e.g., @var{pos} is on the
+whitespace between two leaf nodes), this function returns the first
+leaf node after @var{pos}.
+
+Finally, if there is no leaf node after @var{pos}, return the first
+leaf node before @var{pos}.
+
+If @var{pos} is in between two adjacent nodes, this function returns
+the one after @var{pos}.
When @var{parser-or-lang} is @code{nil} or omitted, this function uses
the first parser in @code{(treesit-parser-list)} of the current
parser using that language in @code{(treesit-parser-list)}, and uses
that.
+If this function cannot find a suitable node to return, it returns
+nil.
+
If @var{named} is non-@code{nil}, this function looks for a named node
only (@pxref{tree-sitter named node, named node}).
-When @var{pos} is after all the text in the buffer, technically there
-is no node after @var{pos}. But for convenience, this function will
-return the last leaf node in the parse tree. If @var{strict} is
-non-@code{nil}, this function will strictly comply to the semantics and
-return @var{nil}.
-
Example:
@example
(treesit-parser-language
(treesit-node-parser node)))
-(defun treesit-node-at (pos &optional parser-or-lang named strict)
- "Return the smallest node that starts at or after buffer position POS.
+(defun treesit-node-at (pos &optional parser-or-lang named)
+ "Return the leaf node at position POS.
-\"Starts at or after POS\" means the start of the node is greater
-than or equal to POS.
+A leaf node is a node that doesn't have any child nodes.
-Return nil if none was found. If NAMED is non-nil, only look for
-named node.
+The returned node's span covers POS: the node's beginning is less
+or equal to POS, and the node's end is greater or equal to POS.
+
+If no leaf node's span covers POS (e.g., POS is on whitespace
+between two leaf nodes), return the first leaf node after POS.
+
+If there is no leaf node after POS, return the first leaf node
+before POS.
+
+If POS is in between two adjacent nodes, return the one after
+POS.
+
+Return nil if no leaf node can be returned. If NAMED is non-nil,
+only look for named nodes.
If PARSER-OR-LANG is nil, use the first parser in
`treesit-parser-list'; if PARSER-OR-LANG is a parser, use
that parser; if PARSER-OR-LANG is a language, find a parser using
-that language in the current buffer, and use that.
-
-If POS is after all the text in the buffer, i.e., there is no
-node after POS, return the last leaf node in the parse tree, even
-though that node is before POS. If STRICT is non-nil, return nil
-in this case."
+that language in the current buffer, and use that."
(let* ((root (if (treesit-parser-p parser-or-lang)
(treesit-parser-root-node parser-or-lang)
(treesit-buffer-root-node parser-or-lang)))
(node root)
+ (node-before root)
+ (pos-1 (max (1- pos) (point-min)))
next)
(when node
;; This is very fast so no need for C implementation.
(while (setq next (treesit-node-first-child-for-pos
node pos named))
(setq node next))
- ;; If we are at the end of buffer or after all the text, we will
- ;; end up with NODE = root node. For convenience, return the last
- ;; leaf node in the tree.
+ ;; If POS is at the end of buffer, after all the text, we will
+ ;; end up with NODE = root node. Instead of returning nil,
+ ;; return the last leaf node in the tree for convenience.
(if (treesit-node-eq node root)
- (if strict
- nil
+ (progn
(while (setq next (treesit-node-child node -1 named))
(setq node next))
node)
- node))))
+ ;; Normal case, where we found a node.
+ (if (<= (treesit-node-start node) pos)
+ node
+ ;; So the node we found is completely after POS, try to find
+ ;; a node whose end equals to POS.
+ (while (setq next (treesit-node-first-child-for-pos
+ node-before pos-1 named))
+ (setq node-before next))
+ (if (eq (treesit-node-end node-before) pos)
+ node-before
+ node))))))
(defun treesit-node-on (beg end &optional parser-or-lang named)
"Return the smallest node covering BEG to END.
;; `treesit-search-forward-goto'
))
+(ert-deftest treesit-node-at ()
+ "Test `treesit-node-at'."
+ (skip-unless (treesit-language-available-p 'json))
+ (let (parser root-node)
+ (progn
+ (insert "[1, 2, 3,4] ")
+ (setq parser (treesit-parser-create 'json))
+ (setq root-node (treesit-parser-root-node
+ parser)))
+ ;; Point at ",", should return ",".
+ (goto-char (point-min))
+ (search-forward "1")
+ (should (equal (treesit-node-text
+ (treesit-node-at (point)))
+ ","))
+ ;; Point behind ",", should still return the ",".
+ (search-forward ",")
+ (should (equal (treesit-node-text
+ (treesit-node-at (point)))
+ ","))
+ ;; Point between "," and "2", should return 2.
+ (forward-char)
+ (should (equal (treesit-node-text
+ (treesit-node-at (point)))
+ "2"))
+ ;; EOF, should return the last leaf node "]".
+ (goto-char (point-max))
+ (should (equal (treesit-node-text
+ (treesit-node-at (point)))
+ "]"))))
+
(ert-deftest treesit-misc ()
"Misc helper functions."
(let ((settings '((t 0 t)