aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYuan Fu2024-12-24 13:17:51 -0800
committerYuan Fu2024-12-24 13:56:44 -0800
commite2a9af431191d5c71e2ca7a4347ce9e435e8cca0 (patch)
tree67a6f8c6dfa92f4905f584eb887508e8b92b99ef
parent833494d4b00a837be8ceaa09b37f54ce17d0a062 (diff)
downloademacs-e2a9af431191d5c71e2ca7a4347ce9e435e8cca0.tar.gz
emacs-e2a9af431191d5c71e2ca7a4347ce9e435e8cca0.zip
Add treesit-aggregated-simple-imenu-settings
Now we support setting up Imenu for multiple languages * doc/lispref/modes.texi: Update manual. * lisp/treesit.el: (treesit-aggregated-simple-imenu-settings): New variable. (treesit--imenu-merge-entries): New function. (treesit--generate-simple-imenu): This was previously treesit-simple-imenu. (treesit-simple-imenu): Support treesit-aggregated-simple-imenu-settings. (treesit-major-mode-setup): Recognize treesit-aggregated-simple-imenu-settings. * test/src/treesit-tests.el (treesit-imenu): New test.
-rw-r--r--doc/lispref/modes.texi9
-rw-r--r--lisp/treesit.el104
-rw-r--r--test/src/treesit-tests.el14
3 files changed, 113 insertions, 14 deletions
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index 73edb688c85..f227bdc635f 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -3109,6 +3109,15 @@ instead.
3109automatically sets up Imenu if this variable is non-@code{nil}. 3109automatically sets up Imenu if this variable is non-@code{nil}.
3110@end defvar 3110@end defvar
3111 3111
3112@defvar treesit-aggregated-simple-imenu-settings
3113This variable allows major modes to configure Imenu for multiple
3114languages. Its value is an alist mapping language symbols to Imenu
3115settings described in @var{treesit-simple-imenu-settings}.
3116
3117If both this variable and @var{treesit-simple-imenu-settings} is
3118non-@code{nil}, Emacs uses this variable for setting up Imenu.
3119@end defvar
3120
3112@node Outline Minor Mode 3121@node Outline Minor Mode
3113@section Outline Minor Mode 3122@section Outline Minor Mode
3114 3123
diff --git a/lisp/treesit.el b/lisp/treesit.el
index 2cf7bccdeed..464b7e688be 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -3123,6 +3123,31 @@ node and returns the name of that defun node. If NAME-FN is nil,
3123`treesit-major-mode-setup' automatically sets up Imenu if this 3123`treesit-major-mode-setup' automatically sets up Imenu if this
3124variable is non-nil.") 3124variable is non-nil.")
3125 3125
3126;; `treesit-simple-imenu-settings' doesn't support multiple languages,
3127;; and we need to add multi-lang support for Imenu. One option is to
3128;; extend treesit-simple-imenu-settings to specify language, either by
3129;; making it optionally an alist (just like
3130;; `treesit-aggregated-simple-imenu-settings'), or add a fifth element
3131;; to each setting. But either way makes borrowing Imenu settings from
3132;; other modes difficult: with the alist approach, you'd need to check
3133;; whether other mode uses a plain list or an alist; with the fifth
3134;; element approach, again, you need to check if each setting has the
3135;; fifth element, and add it if not.
3136;;
3137;; OTOH, with `treesit-aggregated-simple-imenu-settings', borrowing
3138;; Imenu settings is easy: if `treesit-aggregated-simple-imenu-settings'
3139;; is non-nil, copy everything over; if `treesit-simple-imenu-settings'
3140;; is non-nil, copy the settings and put them under a language symbol.
3141(defvar treesit-aggregated-simple-imenu-settings nil
3142 "Settings that configure `treesit-simple-imenu' for multi-language modes.
3143
3144The value should be an alist of (LANG . SETTINGS), where LANG is a
3145language symbol, and SETTINGS has the same form as
3146`treesit-simple-imenu-settings'.
3147
3148When both this variable and `treesit-simple-imenu-settings' are non-nil,
3149this variable takes priority.")
3150
3126(defun treesit--simple-imenu-1 (node pred name-fn) 3151(defun treesit--simple-imenu-1 (node pred name-fn)
3127 "Given a sparse tree, create an Imenu index. 3152 "Given a sparse tree, create an Imenu index.
3128 3153
@@ -3170,20 +3195,69 @@ ENTRY. MARKER marks the start of each tree-sitter node."
3170 ;; Leaf node, return a (list of) plain index entry. 3195 ;; Leaf node, return a (list of) plain index entry.
3171 (t (list (cons name marker)))))) 3196 (t (list (cons name marker))))))
3172 3197
3198(defun treesit--imenu-merge-entries (entries)
3199 "Merge ENTRIES by category.
3200
3201ENTRIES is a list of (CATEGORY . SUB-ENTRIES...). Merge them so there's
3202no duplicate CATEGORY. CATEGORY's are strings. The merge is stable,
3203meaning the order of elements are kept."
3204 (let ((return-entries nil))
3205 (dolist (entry entries)
3206 (let* ((category (car entry))
3207 (sub-entries (cdr entry))
3208 (existing-entries
3209 (alist-get category return-entries nil nil #'equal)))
3210 (if (not existing-entries)
3211 (push entry return-entries)
3212 (setf (alist-get category return-entries nil nil #'equal)
3213 (append existing-entries sub-entries)))))
3214 (nreverse return-entries)))
3215
3216(defun treesit--generate-simple-imenu (node settings)
3217 "Return an Imenu index for NODE with SETTINGS.
3218
3219NODE usually should be a root node of a parser. SETTINGS is described
3220by `treesit-simple-imenu-settings'."
3221 (mapcan (lambda (setting)
3222 (pcase-let ((`(,category ,regexp ,pred ,name-fn)
3223 setting))
3224 (when-let* ((tree (treesit-induce-sparse-tree
3225 node regexp))
3226 (index (treesit--simple-imenu-1
3227 tree pred name-fn)))
3228 (if category
3229 (list (cons category index))
3230 index))))
3231 settings))
3232
3173(defun treesit-simple-imenu () 3233(defun treesit-simple-imenu ()
3174 "Return an Imenu index for the current buffer." 3234 "Return an Imenu index for the current buffer."
3175 (let ((root (treesit-buffer-root-node))) 3235 (if (not treesit-aggregated-simple-imenu-settings)
3176 (mapcan (lambda (setting) 3236 (treesit--generate-simple-imenu
3177 (pcase-let ((`(,category ,regexp ,pred ,name-fn) 3237 (treesit-parser-root-node treesit-primary-parser)
3178 setting)) 3238 treesit-simple-imenu-settings)
3179 (when-let* ((tree (treesit-induce-sparse-tree 3239 ;; Use `treesit-aggregated-simple-imenu-settings'. Remove languages
3180 root regexp)) 3240 ;; that doesn't have any Imenu entries.
3181 (index (treesit--simple-imenu-1 3241 (seq-filter
3182 tree pred name-fn))) 3242 #'cdr
3183 (if category 3243 (mapcar
3184 (list (cons category index)) 3244 (lambda (entry)
3185 index)))) 3245 (let* ((lang (car entry))
3186 treesit-simple-imenu-settings))) 3246 (settings (cdr entry))
3247 (global-parser (car (treesit-parser-list nil lang)))
3248 (local-parsers
3249 (treesit-parser-list nil lang 'embedded)))
3250 (cons (treesit-language-display-name lang)
3251 ;; No one says you can't have both global and local
3252 ;; parsers for the same language. E.g., Rust uses
3253 ;; local parsers for the same language to handle
3254 ;; macros.
3255 (treesit--imenu-merge-entries
3256 (mapcan (lambda (parser)
3257 (treesit--generate-simple-imenu
3258 (treesit-parser-root-node parser) settings))
3259 (cons global-parser local-parsers))))))
3260 treesit-aggregated-simple-imenu-settings))))
3187 3261
3188;;; Outline minor mode 3262;;; Outline minor mode
3189 3263
@@ -3321,7 +3395,8 @@ and `end-of-defun-function'.
3321If `treesit-defun-name-function' is non-nil, set up 3395If `treesit-defun-name-function' is non-nil, set up
3322`add-log-current-defun'. 3396`add-log-current-defun'.
3323 3397
3324If `treesit-simple-imenu-settings' is non-nil, set up Imenu. 3398If `treesit-simple-imenu-settings' or
3399`treesit-aggregated-simple-imenu-settings' is non-nil, set up Imenu.
3325 3400
3326If either `treesit-outline-predicate' or `treesit-simple-imenu-settings' 3401If either `treesit-outline-predicate' or `treesit-simple-imenu-settings'
3327are non-nil, and Outline minor mode settings don't already exist, setup 3402are non-nil, and Outline minor mode settings don't already exist, setup
@@ -3395,7 +3470,8 @@ before calling this function."
3395 (setq-local forward-sentence-function #'treesit-forward-sentence)) 3470 (setq-local forward-sentence-function #'treesit-forward-sentence))
3396 3471
3397 ;; Imenu. 3472 ;; Imenu.
3398 (when treesit-simple-imenu-settings 3473 (when (or treesit-aggregated-simple-imenu-settings
3474 treesit-simple-imenu-settings)
3399 (setq-local imenu-create-index-function 3475 (setq-local imenu-create-index-function
3400 #'treesit-simple-imenu)) 3476 #'treesit-simple-imenu))
3401 3477
diff --git a/test/src/treesit-tests.el b/test/src/treesit-tests.el
index 50f205421d7..43102fc97e0 100644
--- a/test/src/treesit-tests.el
+++ b/test/src/treesit-tests.el
@@ -1270,6 +1270,20 @@ This tests bug#60355."
1270 (should node) 1270 (should node)
1271 (should (equal (treesit-node-text node) "2")))) 1271 (should (equal (treesit-node-text node) "2"))))
1272 1272
1273;;; Imenu
1274
1275(ert-deftest treesit-imenu ()
1276 "Test imenu functions."
1277 (should (equal (treesit--imenu-merge-entries
1278 '(("Function" . (f1 f2))
1279 ("Function" . (f3 f4 f5))
1280 ("Class" . (c1 c2 c3))
1281 ("Variables" . (v1 v2))
1282 ("Class" . (c4))))
1283 '(("Function" . (f1 f2 f3 f4 f5))
1284 ("Class" . (c1 c2 c3 c4))
1285 ("Variables" . (v1 v2))))))
1286
1273 1287
1274;; TODO 1288;; TODO
1275;; - Functions in treesit.el 1289;; - Functions in treesit.el