From d19ce8e2ff61d16a0194b97c5f9395128f94948e Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Mon, 2 Dec 2024 17:23:08 -0800 Subject: [PATCH] Make tree-sitter-explorer support selecting local parser Now treesit-explore-mode will prompt the user to select a parser to explore, rather than a language. It'll also include the local parsers at point for selection. * lisp/treesit.el (treesit--explorer-language): Change to treesit--explorer-parser. (treesit--explorer--nodes-to-highlight): (treesit--explorer-refresh): Change to use parser. (treesit--explorer-generate-parser-alist): New function. (treesit-explorer-switch-parser): New command. (treesit-explore-mode): Use switch-parser to setup. * doc/lispref/parsing.texi (Language Grammar): Mention treesit-explorer-switch-parser. (cherry picked from commit 10b4d3045e1b856009c5ac1e1a1ca257f2d4493f) --- doc/lispref/parsing.texi | 3 ++ lisp/treesit.el | 113 +++++++++++++++++++++++++-------------- 2 files changed, 76 insertions(+), 40 deletions(-) diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index 5ef29f558ef..7fbb8e61ce1 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi @@ -240,12 +240,15 @@ which displays the syntax tree of the source in the current buffer in real time. Emacs also comes with an ``inspect mode'', which displays information of the nodes at point in the mode-line. +@findex treesit-explorer-switch-parser @deffn Command treesit-explore This command pops up a window displaying the syntax tree of the source in the current buffer. Selecting text in the source buffer highlights the corresponding nodes in the syntax tree display. Clicking on nodes in the syntax tree highlights the corresponding text in the source buffer. + +To switch to another parser, use @code{treesit-explorer-switch-parser}. @end deffn @deffn Command treesit-inspect-mode diff --git a/lisp/treesit.el b/lisp/treesit.el index e4da8b19739..be6c8820638 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -3339,7 +3339,7 @@ to the offending pattern and highlight the pattern." (defvar-local treesit--explorer-source-buffer nil "Source buffer corresponding to the playground buffer.") -(defvar-local treesit--explorer-language nil +(defvar-local treesit--explorer-parser nil "The language used in the playground.") (defvar-local treesit--explorer-refresh-timer nil @@ -3353,8 +3353,8 @@ to the offending pattern and highlight the pattern." (defvar treesit-explore-mode) -(defun treesit--explorer--nodes-to-highlight (language) - "Return nodes for LANGUAGE covered in region. +(defun treesit--explorer--nodes-to-highlight (parser) + "Return nodes for PARSER covered in region. This function tries to return the largest node possible. If the region covers exactly one node, that node is returned (in a list). If the region covers more than one node, two nodes are @@ -3362,7 +3362,7 @@ returned: the very first one in the region and the very last one in the region." (let* ((beg (region-beginning)) (end (region-end)) - (node (treesit-node-on beg end language)) + (node (treesit-node-on beg end parser)) (node (or (treesit-parent-while node (lambda (n) @@ -3386,7 +3386,7 @@ in the region." (when (and treesit-explore-mode (buffer-live-p treesit--explorer-buffer)) (let* ((root (treesit-node-on - (window-start) (window-end) treesit--explorer-language)) + (window-start) (window-end) treesit--explorer-parser)) ;; Only highlight the current top-level construct. ;; Highlighting the whole buffer is slow and unnecessary. ;; But if the buffer is small (ie, used in playground @@ -3403,7 +3403,7 @@ in the region." (nodes-hl (when (region-active-p) (treesit--explorer--nodes-to-highlight - treesit--explorer-language))) + treesit--explorer-parser))) ;; If we didn't edit the buffer nor change the top-level ;; node, don't redraw the whole syntax tree. (highlight-only (treesit-node-eq @@ -3581,11 +3581,56 @@ leaves point at the end of the last line of NODE." (when (buffer-live-p treesit--explorer-buffer) (kill-buffer treesit--explorer-buffer))) +(defun treesit--explorer-generate-parser-alist () + "Return an alist of (PARSER-NAME . PARSER) for relevant parsers. +Relevant parsers include all global parsers and local parsers that +covers point. PARSER-NAME are unique." + (let* ((local-parsers (treesit-parser-list nil nil 'embedded)) + (local-parsers-at-point + (treesit-local-parsers-at (point))) + res) + (dolist (parser (treesit-parser-list nil nil t)) + ;; Exclude local parsers that doesn't cover point. + (when (or (memq parser local-parsers-at-point) + (not (memq parser local-parsers))) + (push (cons (concat (format "%s" parser) + (if (treesit-parser-tag parser) + (format " tag=%s" + (treesit-parser-tag + parser)) + "") + (if (memq parser + local-parsers-at-point) + " (local)" + "") + (propertize (format " %s" (gensym)) + 'invisible t)) + parser) + res))) + (nreverse res))) + (define-derived-mode treesit--explorer-tree-mode special-mode "TS Explorer" "Mode for displaying syntax trees for `treesit-explore-mode'." nil) +(defun treesit-explorer-switch-parser (parser) + "Switch explorer to use PARSER." + (interactive + (list (let* ((parser-alist + (treesit--explorer-generate-parser-alist)) + (parser-name (completing-read + "Parser: " (mapcar #'car parser-alist)))) + (alist-get parser-name parser-alist + nil nil #'equal)))) + (unless treesit-explore-mode + (user-error "Not in `treesit-explore-mode'")) + (setq-local treesit--explorer-parser parser) + (display-buffer treesit--explorer-buffer + (cons nil '((inhibit-same-window . t)))) + (setq-local treesit--explorer-last-node nil) + (treesit--explorer-refresh)) + (define-minor-mode treesit-explore-mode "Enable exploring the current buffer's syntax tree. Pops up a window showing the syntax tree of the source in the @@ -3594,40 +3639,28 @@ the text in the active region is highlighted in the explorer window." :lighter " TSexplore" (if treesit-explore-mode - (let ((language - (intern (completing-read - "Language: " - (cl-remove-duplicates - (mapcar #'treesit-parser-language - (treesit-parser-list nil nil t))))))) - (if (not (treesit-language-available-p language)) - (user-error "Cannot find tree-sitter grammar for %s: %s" - language (cdr (treesit-language-available-p - language t))) - ;; Create explorer buffer. - (unless (buffer-live-p treesit--explorer-buffer) - (setq-local treesit--explorer-buffer - (get-buffer-create - (format "*tree-sitter explorer for %s*" - (buffer-name)))) - (setq-local treesit--explorer-language language) - (with-current-buffer treesit--explorer-buffer - (treesit--explorer-tree-mode))) - (display-buffer treesit--explorer-buffer - (cons nil '((inhibit-same-window . t)))) - (setq-local treesit--explorer-last-node nil) - (treesit--explorer-refresh) - ;; Set up variables and hooks. - (add-hook 'post-command-hook - #'treesit--explorer-post-command 0 t) - (add-hook 'kill-buffer-hook - #'treesit--explorer-kill-explorer-buffer 0 t) - ;; Tell `desktop-save' to not save explorer buffers. - (when (boundp 'desktop-modes-not-to-save) - (unless (memq 'treesit--explorer-tree-mode - desktop-modes-not-to-save) - (push 'treesit--explorer-tree-mode - desktop-modes-not-to-save))))) + (progn + ;; Create explorer buffer. + (unless (buffer-live-p treesit--explorer-buffer) + (setq-local treesit--explorer-buffer + (get-buffer-create + (format "*tree-sitter explorer for %s*" + (buffer-name)))) + (with-current-buffer treesit--explorer-buffer + (treesit--explorer-tree-mode))) + ;; Select parser. + (call-interactively #'treesit-explorer-switch-parser) + ;; Set up variables and hooks. + (add-hook 'post-command-hook + #'treesit--explorer-post-command 0 t) + (add-hook 'kill-buffer-hook + #'treesit--explorer-kill-explorer-buffer 0 t) + ;; Tell `desktop-save' to not save explorer buffers. + (when (boundp 'desktop-modes-not-to-save) + (unless (memq 'treesit--explorer-tree-mode + desktop-modes-not-to-save) + (push 'treesit--explorer-tree-mode + desktop-modes-not-to-save)))) ;; Turn off explore mode. (remove-hook 'post-command-hook #'treesit--explorer-post-command t) -- 2.39.5