diff options
| author | Yuan Fu | 2022-11-11 20:04:38 -0800 |
|---|---|---|
| committer | Yuan Fu | 2022-11-11 20:04:38 -0800 |
| commit | 5cd3db73bed06e394ea8e7b0e332b1b1e5bd9571 (patch) | |
| tree | 959a84f4a86cc39784ab5e0964bb21b80c5a9770 | |
| parent | 4489450f37deafb013b1f0fc00c89f0973fda14a (diff) | |
| download | emacs-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.texi | 28 | ||||
| -rw-r--r-- | lisp/treesit.el | 53 | ||||
| -rw-r--r-- | test/src/treesit-tests.el | 31 |
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 |
| 495 | This function returns the @emph{smallest} node that starts at or after | 495 | This function returns a @emph{leaf} node at buffer position @var{pos}. |
| 496 | the buffer position @var{pos}. In other words, the start of the node | 496 | A leaf node is a node that doesn't have any child nodes. |
| 497 | is greater or equal to @var{pos}. | 497 | |
| 498 | This function tries to return a node whose span covers @var{pos}: the | ||
| 499 | node's beginning is less or equal to @var{pos}, and the node's end is | ||
| 500 | greater or equal to @var{pos}. | ||
| 501 | |||
| 502 | But if no leaf node's span covers @var{pos} (e.g., @var{pos} is on the | ||
| 503 | whitespace between two leaf nodes), this function returns the first | ||
| 504 | leaf node after @var{pos}. | ||
| 505 | |||
| 506 | Finally, if there is no leaf node after @var{pos}, return the first | ||
| 507 | leaf node before @var{pos}. | ||
| 508 | |||
| 509 | If @var{pos} is in between two adjacent nodes, this function returns | ||
| 510 | the one after @var{pos}. | ||
| 498 | 511 | ||
| 499 | When @var{parser-or-lang} is @code{nil} or omitted, this function uses | 512 | When @var{parser-or-lang} is @code{nil} or omitted, this function uses |
| 500 | the first parser in @code{(treesit-parser-list)} of the current | 513 | the 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 | |||
| 503 | parser using that language in @code{(treesit-parser-list)}, and uses | 516 | parser using that language in @code{(treesit-parser-list)}, and uses |
| 504 | that. | 517 | that. |
| 505 | 518 | ||
| 519 | If this function cannot find a suitable node to return, it returns | ||
| 520 | nil. | ||
| 521 | |||
| 506 | If @var{named} is non-@code{nil}, this function looks for a named node | 522 | If @var{named} is non-@code{nil}, this function looks for a named node |
| 507 | only (@pxref{tree-sitter named node, named node}). | 523 | only (@pxref{tree-sitter named node, named node}). |
| 508 | 524 | ||
| 509 | When @var{pos} is after all the text in the buffer, technically there | ||
| 510 | is no node after @var{pos}. But for convenience, this function will | ||
| 511 | return the last leaf node in the parse tree. If @var{strict} is | ||
| 512 | non-@code{nil}, this function will strictly comply to the semantics and | ||
| 513 | return @var{nil}. | ||
| 514 | |||
| 515 | Example: | 525 | Example: |
| 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 | 174 | A leaf node is a node that doesn't have any child nodes. |
| 175 | than or equal to POS. | ||
| 176 | 175 | ||
| 177 | Return nil if none was found. If NAMED is non-nil, only look for | 176 | The returned node's span covers POS: the node's beginning is less |
| 178 | named node. | 177 | or equal to POS, and the node's end is greater or equal to POS. |
| 178 | |||
| 179 | If no leaf node's span covers POS (e.g., POS is on whitespace | ||
| 180 | between two leaf nodes), return the first leaf node after POS. | ||
| 181 | |||
| 182 | If there is no leaf node after POS, return the first leaf node | ||
| 183 | before POS. | ||
| 184 | |||
| 185 | If POS is in between two adjacent nodes, return the one after | ||
| 186 | POS. | ||
| 187 | |||
| 188 | Return nil if no leaf node can be returned. If NAMED is non-nil, | ||
| 189 | only look for named nodes. | ||
| 179 | 190 | ||
| 180 | If PARSER-OR-LANG is nil, use the first parser in | 191 | If 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 |
| 182 | that parser; if PARSER-OR-LANG is a language, find a parser using | 193 | that parser; if PARSER-OR-LANG is a language, find a parser using |
| 183 | that language in the current buffer, and use that. | 194 | that language in the current buffer, and use that." |
| 184 | |||
| 185 | If POS is after all the text in the buffer, i.e., there is no | ||
| 186 | node after POS, return the last leaf node in the parse tree, even | ||
| 187 | though that node is before POS. If STRICT is non-nil, return nil | ||
| 188 | in 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) |