aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYuan Fu2022-11-11 20:04:38 -0800
committerYuan Fu2022-11-11 20:04:38 -0800
commit5cd3db73bed06e394ea8e7b0e332b1b1e5bd9571 (patch)
tree959a84f4a86cc39784ab5e0964bb21b80c5a9770
parent4489450f37deafb013b1f0fc00c89f0973fda14a (diff)
downloademacs-5cd3db73bed06e394ea8e7b0e332b1b1e5bd9571.tar.gz
emacs-5cd3db73bed06e394ea8e7b0e332b1b1e5bd9571.zip
Improve treesit-node-at
* 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.
-rw-r--r--doc/lispref/parsing.texi28
-rw-r--r--lisp/treesit.el53
-rw-r--r--test/src/treesit-tests.el31
3 files changed, 85 insertions, 27 deletions
diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi
index bc2f0dda91c..f690e6a6047 100644
--- a/doc/lispref/parsing.texi
+++ b/doc/lispref/parsing.texi
@@ -492,9 +492,22 @@ Using an outdated node signals the @code{treesit-node-outdated} error.
492@cindex syntax tree, retrieving nodes 492@cindex syntax tree, retrieving nodes
493 493
494@defun treesit-node-at pos &optional parser-or-lang named 494@defun treesit-node-at pos &optional parser-or-lang named
495This function returns the @emph{smallest} node that starts at or after 495This function returns a @emph{leaf} node at buffer position @var{pos}.
496the buffer position @var{pos}. In other words, the start of the node 496A leaf node is a node that doesn't have any child nodes.
497is greater or equal to @var{pos}. 497
498This function tries to return a node whose span covers @var{pos}: the
499node's beginning is less or equal to @var{pos}, and the node's end is
500greater or equal to @var{pos}.
501
502But if no leaf node's span covers @var{pos} (e.g., @var{pos} is on the
503whitespace between two leaf nodes), this function returns the first
504leaf node after @var{pos}.
505
506Finally, if there is no leaf node after @var{pos}, return the first
507leaf node before @var{pos}.
508
509If @var{pos} is in between two adjacent nodes, this function returns
510the one after @var{pos}.
498 511
499When @var{parser-or-lang} is @code{nil} or omitted, this function uses 512When @var{parser-or-lang} is @code{nil} or omitted, this function uses
500the first parser in @code{(treesit-parser-list)} of the current 513the first parser in @code{(treesit-parser-list)} of the current
@@ -503,15 +516,12 @@ parser; if @var{parser-or-lang} is a language, it finds the first
503parser using that language in @code{(treesit-parser-list)}, and uses 516parser using that language in @code{(treesit-parser-list)}, and uses
504that. 517that.
505 518
519If this function cannot find a suitable node to return, it returns
520nil.
521
506If @var{named} is non-@code{nil}, this function looks for a named node 522If @var{named} is non-@code{nil}, this function looks for a named node
507only (@pxref{tree-sitter named node, named node}). 523only (@pxref{tree-sitter named node, named node}).
508 524
509When @var{pos} is after all the text in the buffer, technically there
510is no node after @var{pos}. But for convenience, this function will
511return the last leaf node in the parse tree. If @var{strict} is
512non-@code{nil}, this function will strictly comply to the semantics and
513return @var{nil}.
514
515Example: 525Example:
516 526
517@example 527@example
diff --git a/lisp/treesit.el b/lisp/treesit.el
index 1c61b1efebf..796b85ef74b 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -168,44 +168,61 @@ parser in `treesit-parser-list', or nil if there is no parser."
168 (treesit-parser-language 168 (treesit-parser-language
169 (treesit-node-parser node))) 169 (treesit-node-parser node)))
170 170
171(defun treesit-node-at (pos &optional parser-or-lang named strict) 171(defun treesit-node-at (pos &optional parser-or-lang named)
172 "Return the smallest node that starts at or after buffer position POS. 172 "Return the leaf node at position POS.
173 173
174\"Starts at or after POS\" means the start of the node is greater 174A leaf node is a node that doesn't have any child nodes.
175than or equal to POS.
176 175
177Return nil if none was found. If NAMED is non-nil, only look for 176The returned node's span covers POS: the node's beginning is less
178named node. 177or equal to POS, and the node's end is greater or equal to POS.
178
179If no leaf node's span covers POS (e.g., POS is on whitespace
180between two leaf nodes), return the first leaf node after POS.
181
182If there is no leaf node after POS, return the first leaf node
183before POS.
184
185If POS is in between two adjacent nodes, return the one after
186POS.
187
188Return nil if no leaf node can be returned. If NAMED is non-nil,
189only look for named nodes.
179 190
180If PARSER-OR-LANG is nil, use the first parser in 191If PARSER-OR-LANG is nil, use the first parser in
181`treesit-parser-list'; if PARSER-OR-LANG is a parser, use 192`treesit-parser-list'; if PARSER-OR-LANG is a parser, use
182that parser; if PARSER-OR-LANG is a language, find a parser using 193that parser; if PARSER-OR-LANG is a language, find a parser using
183that language in the current buffer, and use that. 194that language in the current buffer, and use that."
184
185If POS is after all the text in the buffer, i.e., there is no
186node after POS, return the last leaf node in the parse tree, even
187though that node is before POS. If STRICT is non-nil, return nil
188in this case."
189 (let* ((root (if (treesit-parser-p parser-or-lang) 195 (let* ((root (if (treesit-parser-p parser-or-lang)
190 (treesit-parser-root-node parser-or-lang) 196 (treesit-parser-root-node parser-or-lang)
191 (treesit-buffer-root-node parser-or-lang))) 197 (treesit-buffer-root-node parser-or-lang)))
192 (node root) 198 (node root)
199 (node-before root)
200 (pos-1 (max (1- pos) (point-min)))
193 next) 201 next)
194 (when node 202 (when node
195 ;; This is very fast so no need for C implementation. 203 ;; This is very fast so no need for C implementation.
196 (while (setq next (treesit-node-first-child-for-pos 204 (while (setq next (treesit-node-first-child-for-pos
197 node pos named)) 205 node pos named))
198 (setq node next)) 206 (setq node next))
199 ;; If we are at the end of buffer or after all the text, we will 207 ;; If POS is at the end of buffer, after all the text, we will
200 ;; end up with NODE = root node. For convenience, return the last 208 ;; end up with NODE = root node. Instead of returning nil,
201 ;; leaf node in the tree. 209 ;; return the last leaf node in the tree for convenience.
202 (if (treesit-node-eq node root) 210 (if (treesit-node-eq node root)
203 (if strict 211 (progn
204 nil
205 (while (setq next (treesit-node-child node -1 named)) 212 (while (setq next (treesit-node-child node -1 named))
206 (setq node next)) 213 (setq node next))
207 node) 214 node)
208 node)))) 215 ;; Normal case, where we found a node.
216 (if (<= (treesit-node-start node) pos)
217 node
218 ;; So the node we found is completely after POS, try to find
219 ;; a node whose end equals to POS.
220 (while (setq next (treesit-node-first-child-for-pos
221 node-before pos-1 named))
222 (setq node-before next))
223 (if (eq (treesit-node-end node-before) pos)
224 node-before
225 node))))))
209 226
210(defun treesit-node-on (beg end &optional parser-or-lang named) 227(defun treesit-node-on (beg end &optional parser-or-lang named)
211 "Return the smallest node covering BEG to END. 228 "Return the smallest node covering BEG to END.
diff --git a/test/src/treesit-tests.el b/test/src/treesit-tests.el
index 5e4aea3ad41..7fc810492bc 100644
--- a/test/src/treesit-tests.el
+++ b/test/src/treesit-tests.el
@@ -474,6 +474,37 @@ visible_end.)"
474 ;; `treesit-search-forward-goto' 474 ;; `treesit-search-forward-goto'
475 )) 475 ))
476 476
477(ert-deftest treesit-node-at ()
478 "Test `treesit-node-at'."
479 (skip-unless (treesit-language-available-p 'json))
480 (let (parser root-node)
481 (progn
482 (insert "[1, 2, 3,4] ")
483 (setq parser (treesit-parser-create 'json))
484 (setq root-node (treesit-parser-root-node
485 parser)))
486 ;; Point at ",", should return ",".
487 (goto-char (point-min))
488 (search-forward "1")
489 (should (equal (treesit-node-text
490 (treesit-node-at (point)))
491 ","))
492 ;; Point behind ",", should still return the ",".
493 (search-forward ",")
494 (should (equal (treesit-node-text
495 (treesit-node-at (point)))
496 ","))
497 ;; Point between "," and "2", should return 2.
498 (forward-char)
499 (should (equal (treesit-node-text
500 (treesit-node-at (point)))
501 "2"))
502 ;; EOF, should return the last leaf node "]".
503 (goto-char (point-max))
504 (should (equal (treesit-node-text
505 (treesit-node-at (point)))
506 "]"))))
507
477(ert-deftest treesit-misc () 508(ert-deftest treesit-misc ()
478 "Misc helper functions." 509 "Misc helper functions."
479 (let ((settings '((t 0 t) 510 (let ((settings '((t 0 t)