branch: scratch/javaimp-wip commit 4ac5cb48db9fa6d57b937d33a336dcda8cc8c3a1 Author: Filipp Gunbin <fgun...@fastmail.fm> Commit: Filipp Gunbin <fgun...@fastmail.fm>
wip --- javaimp-parse.el | 13 ++- javaimp-tests.el | 177 +++++++++++++++++++++++++++++++++++---- javaimp-util.el | 60 ++++++------- javaimp.el | 65 ++++++++------ testdata/test1-misc-classes.java | 5 ++ 5 files changed, 246 insertions(+), 74 deletions(-) diff --git a/javaimp-parse.el b/javaimp-parse.el index 1a46b65..061b18d 100644 --- a/javaimp-parse.el +++ b/javaimp-parse.el @@ -55,9 +55,14 @@ present." )) (defsubst javaimp--parse-is-classlike (scope) - (when scope - (member (symbol-name (javaimp-scope-type scope)) - javaimp--parse-classlike-keywords))) + (and scope + (member (symbol-name (javaimp-scope-type scope)) + javaimp--parse-classlike-keywords))) + +(defsubst javaimp--parse-is-named (scope) + (and scope + (memq (javaimp-scope-type scope) + javaimp--parse-named-scope-types))) (defvar javaimp--arglist-syntax-table (let ((st (make-syntax-table java-mode-syntax-table))) ;TODO don't depend @@ -477,7 +482,7 @@ variable after parsing is done." (and (eq (javaimp-scope-type s) 'method) (javaimp--parse-is-classlike (javaimp-scope-parent s)))))) (classes (javaimp--parse-get-all-scopes - nil ;no need to reparse + nil ; no need to reparse #'javaimp--parse-is-classlike)) (top-classes (mapcar (lambda (s) (null (javaimp-scope-parent s))) diff --git a/javaimp-tests.el b/javaimp-tests.el index 7bb63b5..b4788e2 100644 --- a/javaimp-tests.el +++ b/javaimp-tests.el @@ -189,34 +189,179 @@ throws E1 {" "Top.IInner1.IInner1_CInner1" "Top.IInner1.IInner1_IInner1" "Top.EInner1" - "Top.EInner1.EInner1_EInner1"))))) - -(ert-deftest javaimp-test--parse-get-forest-for-imenu () - ;; TODO - ) - + "Top.EInner1.EInner1_EInner1" + "ColocatedTop"))))) (ert-deftest javaimp-test--parse-get-all-scopes () (with-temp-buffer (insert-file-contents (concat javaimp--basedir "testdata/test1-misc-classes.java")) + ;; ;; parse full buffer - (javaimp-test--check-scopes (javaimp--parse-get-all-scopes t)) - ;; reparse half buffer + (javaimp-test--check-named-scopes + (javaimp--parse-get-all-scopes t #'javaimp--parse-is-named)) + ;; + ;; reparse half of buffer (setq javaimp--parse-dirty-pos (/ (- (point-max) (point-min)) 2)) - (javaimp-test--check-scopes (javaimp--parse-get-all-scopes)) + (javaimp-test--check-named-scopes + (javaimp--parse-get-all-scopes nil #'javaimp--parse-is-named)) + ;; ;; use cache - (javaimp-test--check-scopes (javaimp--parse-get-all-scopes)))) + (javaimp-test--check-named-scopes + (javaimp--parse-get-all-scopes nil #'javaimp--parse-is-named)))) + +(defun javaimp-test--check-named-scopes (scopes) + (let ((actual + (mapcar (lambda (s) + (let (res) + (while s + (push (list (javaimp-scope-type s) + (javaimp-scope-name s)) + res) + (setq s (javaimp-scope-parent s))) + (nreverse res))) + scopes)) + (expected + '(((class "Top")) + ((class "CInner1") (class "Top")) + ((method "foo") (class "CInner1") (class "Top")) + ((local-class "CInner1_CLocal1") + (method "foo") (class "CInner1") (class "Top")) + ((method "foo") + (local-class "CInner1_CLocal1") + (method "foo") (class "CInner1") (class "Top")) + ((local-class "CInner1_CLocal1_CLocal1") + (method "foo") + (local-class "CInner1_CLocal1") + (method "foo") (class "CInner1") (class "Top")) + ((method "foo") + (local-class "CInner1_CLocal1_CLocal1") + (method "foo") + (local-class "CInner1_CLocal1") + (method "foo") (class "CInner1") (class "Top")) + + ((local-class "CInner1_CLocal2") + (method "foo") (class "CInner1") (class "Top")) + ((method "foo") + (local-class "CInner1_CLocal2") + (method "foo") (class "CInner1") (class "Top")) + + ((class "CInner1_CInner") (class "CInner1") (class "Top")) + ((method "foo") + (class "CInner1_CInner") (class "CInner1") (class "Top")) + ((method "bar") + (class "CInner1_CInner") (class "CInner1") (class "Top")) + + ((interface "IInner1") (class "Top")) + ((method "foo") (interface "IInner1") (class "Top")) + ((class "IInner1_CInner1") (interface "IInner1") (class "Top")) + ((method "foo") + (class "IInner1_CInner1") (interface "IInner1") (class "Top")) + ((method "defaultMethod") (interface "IInner1") (class "Top")) + + ((interface "IInner1_IInner1") (interface "IInner1") (class "Top")) + ((method "defaultMethod") + (interface "IInner1_IInner1") (interface "IInner1") (class "Top")) + + ((enum "EnumInner1") (class "Top")) + ((method "EnumInner1") (enum "EnumInner1") (class "Top")) + ((method "foo") (enum "EnumInner1") (class "Top")) + ((enum "EnumInner1_EInner1") (enum "EnumInner1") (class "Top")) + + ((class "ColocatedTop")) + ((method "foo") (class "ColocatedTop"))))) + (should (= (length expected) (length actual))) + (dotimes (i (length expected)) + (should (equal (nth i expected) (nth i actual))))) + ;; + (let ((data + `((,(nth 0 scopes) "Top" 26 36) + (,(nth 15 scopes) "Top.IInner1.IInner1_CInner1.foo" 1810 1816) + (,(nth 22 scopes) "Top.EnumInner1_EInner1" 2452 2476) + (,(nth 24 scopes) "ColocatedTop.foo" 2544 2550)))) + (dolist (elt data) + (let ((scope (nth 0 elt))) + (should (equal (nth 1 elt) (javaimp-scope-name scope))) + (should (equal (nth 2 elt) (javaimp-scope-start scope))) + (should (equal (nth 3 elt) (javaimp-scope-open-brace scope))))))) -(defun javaimp--test-check-scopes (scopes) - ;; TODO check positions of first / most nested / last - '((class "Top")) - '((class "CInner1") (class "Top")) - '((method "foo") (class "CInner1") (class "Top")) + +;; Tests for imenu function + +(ert-deftest javaimp-test--imenu-group () + (let* ((javaimp-imenu-group-methods t) + (actual (javaimp-test--imenu-get-index))) + (should + (equal + actual + '(("Top" + ("CInner1" + ("foo" . 98) + ("CInner1_CInner1" + ("foo" . 1099) + ("bar" . 1192))) + ("IInner1" + ("foo" . 1603) + ("IInner1_CInner1" + ("foo" . 1810)) + ("defaultMethod" . 1975) + ("IInner1_IInner1" + ("defaultMethod" . 2158))) + ("EnumInner1" + ("EnumInner1" . 2343) + ("foo" . 2389) + ;; "EnumInner1_EInner1" empty - omitted + )) + ("ColocatedTop" + ("foo" . 2544))))))) + +(ert-deftest javaimp-test--imenu-simple () + (let* ((javaimp-imenu-group-methods nil) + (actual (javaimp-test--imenu-get-index))) + (should + (equal + actual + '(("foo (Top.CInner1)" . 98) + ("foo (Top.CInner1.CInner1_CInner1)" . 1099) + ("bar" . 1192) + ("foo (Top.IInner1)" . 1603) + ("foo (Top.IInner1.IInner1_CInner1)" . 1810) + ("defaultMethod (Top.IInner1)" . 1975) + ("defaultMethod (Top.IInner1.IInner1_IInner1)" . 2158) + ("EnumInner1" . 2343) + ("foo (Top.EnumInner1)" . 2389) + ("foo (ColocatedTop)" . 2544)))))) + +(ert-deftest javaimp-test--imenu-qualified () + (let* ((javaimp-imenu-group-methods 'qualified) + (actual + (mapcar (lambda (entry) + + (javaimp-test--imenu-get-index))) + (should + (equal - ) + (lambda (s) + (cons (javaimp-scope-name entry) (javaimp-scope-start entry))) + actual + '(("Top.CInner1.foo" . 98) + ("Top.CInner1.CInner1_CInner1.foo" . 1099) + ("Top.CInner1.CInner1_CInner1.bar" . 1192) + ("Top.IInner1.foo" . 1603) + ("Top.IInner1.IInner1_CInner1.foo" . 1810) + ("Top.IInner1.defaultMethod" . 1975) + ("Top.IInner1.IInner1_IInner1.defaultMethod" . 2158) + ("Top.EnumInner1.EnumInner1" . 2343) + ("Top.EnumInner1.foo" . 2389) + ("ColocatedTop.foo" . 2544)))))) + +(defun javaimp-test--imenu-get-index () + (with-temp-buffer + (insert-file-contents + (concat javaimp--basedir "testdata/test1-misc-classes.java")) + (javaimp-imenu-create-index))) (provide 'javaimp-tests) diff --git a/javaimp-util.el b/javaimp-util.el index f2b4dda..1bf2a79 100644 --- a/javaimp-util.el +++ b/javaimp-util.el @@ -157,57 +157,59 @@ PARENT-NODE is indented for recursive calls." (setf (javaimp-node-children this-node) child-nodes) this-node))) -(defun javaimp--find-node (predicate forest &optional unwrap) +(defun javaimp--find-node (pred forest &optional unwrap) (catch 'found (dolist (tree forest) - (javaimp--find-node-in-tree-1 tree predicate unwrap)))) + (javaimp--find-node-in-tree-1 tree pred unwrap)))) -(defun javaimp--find-node-in-tree-1 (tree predicate unwrap) +(defun javaimp--find-node-in-tree-1 (tree pred unwrap) (when tree - (if (funcall predicate (javaimp-node-contents tree)) + (if (funcall pred (javaimp-node-contents tree)) (throw 'found (if unwrap (javaimp-node-contents tree) tree))) (dolist (child (javaimp-node-children tree)) - (javaimp--find-node-in-tree-1 child predicate unwrap)))) + (javaimp--find-node-in-tree-1 child pred unwrap)))) -(defun javaimp--collect-nodes (predicate forest &optional unwrap) +(defun javaimp--collect-nodes (pred forest) (apply #'seq-concatenate 'list (mapcar (lambda (tree) - (javaimp--collect-nodes-from-tree tree predicate unwrap)) + (javaimp--collect-nodes-from-tree tree pred)) forest))) -(defun javaimp--collect-nodes-from-tree (tree predicate unwrap) +(defun javaimp--collect-nodes-from-tree (tree pred) (when tree - (append (when (funcall predicate (javaimp-node-contents tree)) - (list (if unwrap - (javaimp-node-contents tree) - tree))) + (append (when (funcall pred (javaimp-node-contents tree)) + (list (javaimp-node-contents tree))) (apply #'seq-concatenate 'list (mapcar (lambda (child) - (javaimp--collect-nodes-from-tree - child predicate unwrap)) + (javaimp--collect-nodes-from-tree child pred)) (javaimp-node-children tree)))))) -(defun javaimp--map-nodes (mapper forest &optional unwrap) - (mapcar (lambda (tree) - (javaimp--map-nodes-from-tree tree mapper unwrap)) - forest)) -(defun javaimp--map-nodes-from-tree (tree mapper unwrap) +(defun javaimp--map-nodes (mapper pred forest) + "Recursively applies MAPPER to each node's contents in FOREST. +Returns new tree where each node is of the form (MAPPED-NODE +. CHILDREN). A node is included only if PRED applied to it +returns non-nil." + (delq nil + (mapcar (lambda (tree) + (javaimp--map-nodes-from-tree tree mapper pred)) + forest))) + +(defun javaimp--map-nodes-from-tree (tree mapper pred) (when tree - (cons (let ((contents (funcall mapper (javaimp-node-contents tree)))) - (if unwrap - contents - (make-javaimp-node - :parent (javaimp-node-parent tree) - :children (javaimp-node-children tree) - :contents contents))) - (mapcar (lambda (child) - (javaimp--map-nodes-from-tree child mapper unwrap)) - (javaimp-node-children tree))))) + (let* ((contents (funcall mapper (javaimp-node-contents tree))) + (children + (delq nil + (mapcar (lambda (child) + (javaimp--map-nodes-from-tree child mapper pred)) + (javaimp-node-children tree)))) + (res (cons contents children))) + (and (funcall pred res) + res)))) (defun javaimp--get-root (node) (while (javaimp-node-parent node) diff --git a/javaimp.el b/javaimp.el index 5d9ef9d..86b16cd 100644 --- a/javaimp.el +++ b/javaimp.el @@ -310,14 +310,10 @@ PREDICATE returns non-nil." (defun javaimp-collect-modules (predicate) "Returns all modules in `javaimp-project-forest' for which PREDICATE returns non-nil." - (javaimp--collect-nodes predicate javaimp-project-forest t)) + (javaimp--collect-nodes predicate javaimp-project-forest)) (defun javaimp-map-modules (mapper) - "Applies MAPPER to all modules in `javaimp-project-forest' and -returns the result as a tree-like structure: a list of -conses (CONTENTS . CHILDREN) where CHILDREN have the same -structure, and so on." - (javaimp--map-nodes mapper javaimp-project-forest t)) + (javaimp--map-nodes mapper #'always javaimp-project-forest)) ;;; Adding imports @@ -577,24 +573,31 @@ is `ordinary' or `static'. Interactively, NEW-IMPORTS is nil." (let ((forest (save-excursion (javaimp--parse-get-forest-for-imenu)))) (cond ((not javaimp-imenu-group-methods) - ;; just plain list of methods - (javaimp--collect-nodes - (lambda (scope) - (when (eq (javaimp-scope-type scope) 'method) - (javaimp-imenu--make-entry scope))) - forest t)) + ;; plain list of methods + (let ((entries (mapcar #'javaimp-imenu--make-entry + (javaimp--collect-nodes + #'javaimp-imenu--included-method forest)))) + (mapcar (lambda (entry) + ;; disambiguate similar method names + (when (assoc (car entry) entries) + (setcar entry + (format "%s (%s)" + (car entry) + (javaimp-imenu--concat-parent-names + (nth 3 entry)))))) + entries))) ((eq javaimp-imenu-group-methods 'qualified) ;; list of qualified methods - (javaimp--collect-nodes - (lambda (scope) - (when (eq (javaimp-scope-type scope) 'method) - (let ((entry (javaimp-imenu--make-entry scope))) - ;; prepend parents to name - (while (setq scope (javaimp-scope-parent scope)) - (setcar entry (concat (javaimp-scope-name scope) - "." - (car entry))))))) - forest t)) + (mapcar (lambda (entry) + ;; prepend parents to name + (setcar entry (concat (javaimp-imenu--concat-parent-names + (nth 3 entry)) + "." + (car entry)))) + (mapcar #'javaimp-imenu-make-entry + (javaimp--collect-nodes + #'javaimp-imenu--included-method forest)))) + (t ;; group methods inside their enclosing class (javaimp--map-nodes @@ -602,16 +605,28 @@ is `ordinary' or `static'. Interactively, NEW-IMPORTS is nil." (cond ((javaimp--parse-is-classlike scope) ;; this will be a sub-alist's car (javaimp-scope-name scope)) - ((eq (javaimp-scope-type scope) 'method) + ((javaimp-imenu--included-method scope) (javaimp-imenu--make-entry scope)))) - forest t))))) + #'cdr ;don't include empty container scopes + forest))))) -(defun javaimp-imenu--make-entry (scope) +(defsubst javaimp-imenu--included-method (scope) + (and (eq (javaimp-scope-type scope) 'method) + (javaimp--parse-is-classlike (javaimp-scope-parent scope)))) + +(defsubst javaimp-imenu--make-entry (scope) (list (javaimp-scope-name scope) (javaimp-scope-start scope) #'javaimp-imenu--go scope)) +(defun javaimp-imenu--concat-parent-names (scope) + (let (parents) + (while (setq scope (javaimp-scope-parent scope)) + (push scope parents)) + (mapconcat #'javaimp-scope-name parents "."))) + + (defun javaimp-imenu--go (scope) (goto-char (javaimp-scope-start scope)) (back-to-indentation)) diff --git a/testdata/test1-misc-classes.java b/testdata/test1-misc-classes.java index 436d057..d916221 100644 --- a/testdata/test1-misc-classes.java +++ b/testdata/test1-misc-classes.java @@ -115,3 +115,8 @@ public class Top { } } } + +class ColocatedTop { + void foo() { + } +}