[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
emacs-29 b39dc7ab27 1/2: Add tree-sitter helper functions for Imenu
From: |
Yuan Fu |
Subject: |
emacs-29 b39dc7ab27 1/2: Add tree-sitter helper functions for Imenu |
Date: |
Wed, 28 Dec 2022 00:04:27 -0500 (EST) |
branch: emacs-29
commit b39dc7ab27a696a8607ab859aeff3c71509231f5
Author: Yuan Fu <casouri@gmail.com>
Commit: Yuan Fu <casouri@gmail.com>
Add tree-sitter helper functions for Imenu
We didn't add an integration for Imenu because we aren't sure what
should it look like. Now we have a pretty good idea. All the major
modes copy-paste the two Imenu functions and tweaks them in a standard
way. With the addition of treesit-defun-type-regexp and
treesit-defun-name-function, now is a good time to standardize Imenu
integration.
In the next commit we update all the major modes to use this
integration.
* doc/lispref/modes.texi (Imenu): Add manual.
* doc/lispref/parsing.texi (Tree-sitter major modes): Update manual.
* lisp/treesit.el (treesit-simple-imenu-settings): New varaible.
(treesit--simple-imenu-1)
(treesit-simple-imenu): New functions.
(treesit-major-mode-setup): Setup Imenu.
---
doc/lispref/modes.texi | 29 +++++++++++++++
doc/lispref/parsing.texi | 5 +++
lisp/treesit.el | 96 +++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 129 insertions(+), 1 deletion(-)
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index 449529a430..de17969566 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -2841,6 +2841,35 @@ function uses @code{imenu-generic-expression} instead.
Setting this variable makes it buffer-local in the current buffer.
@end defvar
+If built with tree-sitter, Emacs can automatically generate an Imenu
+index if the major mode sets relevant variables.
+
+@defvar treesit-simple-imenu-settings
+This variable instructs Emacs how to generate Imenu indexes. It
+should be a list of @w{(@var{category} @var{regexp} @var{pred}
+@var{name-fn})}.
+
+@var{category} should be the name of a category, like "Function",
+"Class", etc. @var{regexp} should be a regexp matching the type of
+nodes that belong to @var{category}. @var{pred} should be either
+@code{nil} or a function that takes a node as the argument. It should
+return non-@code{nil} if the node is a valid node for @var{category},
+or @code{nil} if not.
+
+@var{category} could also be @code{nil}. In which case the entries
+matched by @var{regexp} and @var{pred} are not grouped under
+@var{category}.
+
+@var{name-fn} should be either @var{nil} or a function that takes a
+defun node and returns the name of that defun, e.g., the function name
+for a function definition. If @var{name-fn} is @var{nil},
+@code{treesit-defun-name} (@pxref{Tree-sitter major modes}) is used
+instead.
+
+@code{treesit-major-mode-setup} (@pxref{Tree-sitter major modes})
+automatically sets up Imenu if this variable is non-@code{nil}.
+@end defvar
+
@node Font Lock Mode
@section Font Lock Mode
@cindex Font Lock mode
diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi
index 63741b69c2..c5500b0b37 100644
--- a/doc/lispref/parsing.texi
+++ b/doc/lispref/parsing.texi
@@ -1738,6 +1738,11 @@ navigation functions for @code{beginning-of-defun} and
If @code{treesit-defun-name-function} is non-@code{nil}, it sets up
add-log functions used by @code{add-log-current-defun}.
@end itemize
+
+@item
+If @code{treesit-simple-imenu-settings} (@pxref{Imenu}) is
+non-@code{nil}, it sets up Imenu.
+@end itemize
@end defun
For more information of these built-in tree-sitter features,
diff --git a/lisp/treesit.el b/lisp/treesit.el
index f3fdcfb652..0aab0a1261 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -2009,6 +2009,91 @@ The delimiter between nested defun names is controlled by
(setq node (treesit-node-parent node)))
name))
+;;; Imenu
+
+(defvar treesit-simple-imenu-settings nil
+ "Settings that configure `treesit-simple-imenu'.
+
+It should be a list of (CATEGORY REGEXP PRED NAME-FN).
+
+CATEGORY is the name of a category, like \"Function\", \"Class\",
+etc. REGEXP should be a regexp matching the type of nodes that
+belong to CATEGORY. PRED should be either nil or a function
+that takes a node an the argument. It should return non-nil if
+the node is a valid node for CATEGORY, or nil if not.
+
+CATEGORY could also be nil. In that case the entries matched by
+REGEXP and PRED are not grouped under CATEGORY.
+
+NAME-FN should be either nil or a function that takes a defun
+node and returns the name of that defun node. If NAME-FN is nil,
+`treesit-defun-name' is used.
+
+`treesit-major-mode-setup' automatically sets up Imenu if this
+variable is non-nil.")
+
+(defun treesit--simple-imenu-1 (node pred name-fn)
+ "Given a sparse tree, create an Imenu index.
+
+NODE is a node in the tree returned by
+`treesit-induce-sparse-tree' (not a tree-sitter node, its car is
+a tree-sitter node). Walk that tree and return an Imenu index.
+
+Return a list of ENTRYs where
+
+ENTRY := (NAME . MARKER)
+ | (NAME . ((\" \" . MARKER)
+ ENTRY
+ ...)
+
+PRED and NAME-FN are the same as described in
+`treesit-simple-imenu-settings'. NAME-FN computes NAME in an
+ENTRY. MARKER marks the start of each tree-sitter node."
+ (let* ((ts-node (car node))
+ (children (cdr node))
+ (subtrees (mapcan (lambda (node)
+ (treesit--simple-imenu-1 node pred name-fn))
+ children))
+ ;; The root of the tree could have a nil ts-node.
+ (name (when ts-node
+ (or (if name-fn
+ (funcall name-fn ts-node)
+ (treesit-defun-name ts-node))
+ "Anonymous")))
+ (marker (when ts-node
+ (set-marker (make-marker)
+ (treesit-node-start ts-node)))))
+ (cond
+ ;; The tree-sitter node in the root node of the tree returned by
+ ;; `treesit-induce-sparse-tree' is often nil.
+ ((null ts-node)
+ subtrees)
+ ;; This tree-sitter node is not a valid entry, skip it.
+ ((and pred (not (funcall pred ts-node)))
+ subtrees)
+ ;; Non-leaf node, return a (list of) subgroup.
+ (subtrees
+ `((,name
+ ,(cons " " marker)
+ ,@subtrees)))
+ ;; Leaf node, return a (list of) plain index entry.
+ (t (list (cons name marker))))))
+
+(defun treesit-simple-imenu ()
+ "Return an Imenu index for the current buffer."
+ (let ((root (treesit-buffer-root-node)))
+ (mapcan (lambda (setting)
+ (pcase-let ((`(,category ,regexp ,pred ,name-fn)
+ setting))
+ (when-let* ((tree (treesit-induce-sparse-tree
+ root regexp))
+ (index (treesit--simple-imenu-1
+ tree pred name-fn)))
+ (if category
+ (list (cons category index))
+ index))))
+ treesit-simple-imenu-settings)))
+
;;; Activating tree-sitter
(defun treesit-ready-p (language &optional quiet)
@@ -2066,6 +2151,11 @@ If `treesit-simple-indent-rules' is non-nil, setup
indentation.
If `treesit-defun-type-regexp' is non-nil, setup
`beginning/end-of-defun' functions.
+If `treesit-defun-name-function' is non-nil, setup
+`add-log-current-defun'.
+
+If `treesit-simple-imenu-settings' is non-nil, setup Imenu.
+
Make sure necessary parsers are created for the current buffer
before calling this function."
;; Font-lock.
@@ -2106,7 +2196,11 @@ before calling this function."
;; Defun name.
(when treesit-defun-name-function
(setq-local add-log-current-defun-function
- #'treesit-add-log-current-defun)))
+ #'treesit-add-log-current-defun))
+ ;; Imenu.
+ (when treesit-simple-imenu-settings
+ (setq-local imenu-create-index-function
+ #'treesit-simple-imenu)))
;;; Debugging