diff options
| author | Yuan Fu | 2024-12-24 13:17:51 -0800 |
|---|---|---|
| committer | Yuan Fu | 2024-12-24 13:56:44 -0800 |
| commit | e2a9af431191d5c71e2ca7a4347ce9e435e8cca0 (patch) | |
| tree | 67a6f8c6dfa92f4905f584eb887508e8b92b99ef | |
| parent | 833494d4b00a837be8ceaa09b37f54ce17d0a062 (diff) | |
| download | emacs-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.texi | 9 | ||||
| -rw-r--r-- | lisp/treesit.el | 104 | ||||
| -rw-r--r-- | test/src/treesit-tests.el | 14 |
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. | |||
| 3109 | automatically sets up Imenu if this variable is non-@code{nil}. | 3109 | automatically 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 | ||
| 3113 | This variable allows major modes to configure Imenu for multiple | ||
| 3114 | languages. Its value is an alist mapping language symbols to Imenu | ||
| 3115 | settings described in @var{treesit-simple-imenu-settings}. | ||
| 3116 | |||
| 3117 | If both this variable and @var{treesit-simple-imenu-settings} is | ||
| 3118 | non-@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 |
| 3124 | variable is non-nil.") | 3124 | variable 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 | |||
| 3144 | The value should be an alist of (LANG . SETTINGS), where LANG is a | ||
| 3145 | language symbol, and SETTINGS has the same form as | ||
| 3146 | `treesit-simple-imenu-settings'. | ||
| 3147 | |||
| 3148 | When both this variable and `treesit-simple-imenu-settings' are non-nil, | ||
| 3149 | this 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 | |||
| 3201 | ENTRIES is a list of (CATEGORY . SUB-ENTRIES...). Merge them so there's | ||
| 3202 | no duplicate CATEGORY. CATEGORY's are strings. The merge is stable, | ||
| 3203 | meaning 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 | |||
| 3219 | NODE usually should be a root node of a parser. SETTINGS is described | ||
| 3220 | by `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'. | |||
| 3321 | If `treesit-defun-name-function' is non-nil, set up | 3395 | If `treesit-defun-name-function' is non-nil, set up |
| 3322 | `add-log-current-defun'. | 3396 | `add-log-current-defun'. |
| 3323 | 3397 | ||
| 3324 | If `treesit-simple-imenu-settings' is non-nil, set up Imenu. | 3398 | If `treesit-simple-imenu-settings' or |
| 3399 | `treesit-aggregated-simple-imenu-settings' is non-nil, set up Imenu. | ||
| 3325 | 3400 | ||
| 3326 | If either `treesit-outline-predicate' or `treesit-simple-imenu-settings' | 3401 | If either `treesit-outline-predicate' or `treesit-simple-imenu-settings' |
| 3327 | are non-nil, and Outline minor mode settings don't already exist, setup | 3402 | are 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 |