From cbef1422fe3ba5d3327835f3952a0f42f7881716 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Mon, 27 Mar 2023 03:49:13 +0300 Subject: [PATCH] ruby-ts-mode: Fix/simplify Imenu index generation * lisp/progmodes/ruby-ts-mode.el (ruby-ts--full-name): Drop '#' from the end of resulting string when the node is not a method. Support 'singleton_method' nodes. (ruby-ts--imenu-helper): Simplify, to create a "flat" list of entries, rather than a nested one. The previous implementation had problems (like producing a nested structure of full-qualified names, thus creating a lot of textual repetition), seems easier to just follow ruby-mode's example here, at least for Emacs 29's release. * test/lisp/progmodes/ruby-ts-mode-tests.el (ruby-ts-imenu-index): New test. --- lisp/progmodes/ruby-ts-mode.el | 46 +++++++++++------------ test/lisp/progmodes/ruby-ts-mode-tests.el | 25 ++++++++++++ 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/lisp/progmodes/ruby-ts-mode.el b/lisp/progmodes/ruby-ts-mode.el index 0915c29881d..91d65a2777b 100644 --- a/lisp/progmodes/ruby-ts-mode.el +++ b/lisp/progmodes/ruby-ts-mode.el @@ -883,32 +883,24 @@ a statement container is a node that matches "Return the fully qualified name of NODE." (let* ((name (ruby-ts--get-name node)) (delimiter "#")) + (when (equal (treesit-node-type node) "singleton_method") + (setq delimiter "." + name (treesit-node-text (treesit-node-child-by-field-name node "name")))) (while (setq node (treesit-parent-until node #'ruby-ts--class-or-module-p)) - (setq name (concat (ruby-ts--get-name node) delimiter name)) + (if name + (setq name (concat (ruby-ts--get-name node) delimiter name)) + (setq name (ruby-ts--get-name node))) (setq delimiter "::")) name)) -(defun ruby-ts--imenu-helper (node) - "Convert a treesit sparse tree NODE in an imenu list. -Helper for `ruby-ts--imenu' which converts a treesit sparse -NODE into a list of imenu ( name . pos ) nodes" - (let* ((ts-node (car node)) - (subtrees (mapcan #'ruby-ts--imenu-helper (cdr node))) - (name (when ts-node - (ruby-ts--full-name ts-node))) - (marker (when ts-node - (set-marker (make-marker) - (treesit-node-start ts-node))))) - (cond - ((or (null ts-node) (null name)) subtrees) - ;; Don't include the anonymous "class" and "module" nodes - ((string-match-p "(\"\\(class\\|module\\)\")" - (treesit-node-string ts-node)) - nil) - (subtrees - `((,name ,(cons name marker) ,@subtrees))) - (t - `((,name . ,marker)))))) +(defun ruby-ts--imenu-helper (tree) + "Convert a treesit sparse tree NODE in a flat imenu list." + (if (cdr tree) + ;; We only use the "leaf" values in the tree. It does include a + ;; leaf node for every class or module body. + (cl-mapcan #'ruby-ts--imenu-helper (cdr tree)) + (list (cons (ruby-ts--full-name (car tree)) + (treesit-node-start (car tree)))))) ;; For now, this is going to work like ruby-mode and return a list of ;; class, modules, def (methods), and alias. It is likely that this @@ -916,8 +908,14 @@ NODE into a list of imenu ( name . pos ) nodes" (defun ruby-ts--imenu () "Return Imenu alist for the current buffer." (let* ((root (treesit-buffer-root-node)) - (nodes (treesit-induce-sparse-tree root "^\\(method\\|alias\\|class\\|module\\)$"))) - (ruby-ts--imenu-helper nodes))) + (tree (treesit-induce-sparse-tree root + (rx bol (or "singleton_method" + "method" + "alias" + "class" + "module") + eol)))) + (ruby-ts--imenu-helper tree))) (defun ruby-ts--arrow-up-start (arg) "Move to the start ARG levels up or out." diff --git a/test/lisp/progmodes/ruby-ts-mode-tests.el b/test/lisp/progmodes/ruby-ts-mode-tests.el index e0d9f1b5c50..11125dc5cd3 100644 --- a/test/lisp/progmodes/ruby-ts-mode-tests.el +++ b/test/lisp/progmodes/ruby-ts-mode-tests.el @@ -281,6 +281,31 @@ The whitespace before and including \"|\" on each line is removed." (file-truename (expand-file-name (format "ruby-mode-resources/%s" ,file)))))) +(ert-deftest ruby-ts-imenu-index () + (ruby-ts-with-temp-buffer + (ruby-ts-test-string + "module Foo + | class Blub + | def hi + | 'Hi!' + | end + | + | def bye + | 'Bye!' + | end + | + | private def self.hiding + | 'You can't see me' + | end + | end + |end") + (should (equal (mapcar #'car (ruby-ts--imenu)) + '("Foo" + "Foo::Blub" + "Foo::Blub#hi" + "Foo::Blub#bye" + "Foo::Blub.hiding"))))) + (defmacro ruby-ts-deftest-indent (file) `(ert-deftest ,(intern (format "ruby-ts-indent-test/%s" file)) () ;; :tags '(:expensive-test) -- 2.39.2