branch: externals/javaimp commit 691f0ec7a0aee8f598bae969508c0b7b3966c7d6 Author: Filipp Gunbin <fgun...@fastmail.fm> Commit: Filipp Gunbin <fgun...@fastmail.fm>
Imenu & scopes improvements --- javaimp-parse.el | 83 ++++++++-------------- javaimp-tests.el | 204 +++++++++++++++++++++++++++---------------------------- javaimp-util.el | 61 ++++++++--------- javaimp.el | 97 +++++++++++++++++++------- 4 files changed, 230 insertions(+), 215 deletions(-) diff --git a/javaimp-parse.el b/javaimp-parse.el index 460e615..130edff 100644 --- a/javaimp-parse.el +++ b/javaimp-parse.el @@ -208,8 +208,7 @@ is unchanged." the position of opening brace.") (defun javaimp--parse-scope-class (brace-pos) - "Attempts to parse 'class' / 'interface' / 'enum' scope. Some of -those may later become 'local-class' (see `javaimp--parse-scopes')." + "Attempts to parse 'class' / 'interface' / 'enum' scope." (save-excursion (if (javaimp--parse-preceding (regexp-opt javaimp--parse-classlike-keywords 'symbols) brace-pos) @@ -334,8 +333,9 @@ those may later become 'local-class' (see `javaimp--parse-scopes')." :open-brace brace-pos)) (defun javaimp--parse-scopes (count) - "Attempts to parse COUNT enclosing scopes at point. If COUNT is -nil then goes all the way up. Examines and sets property + "Attempts to parse COUNT enclosing scopes at point. Returns most +nested one, with its parents sets accordingly. If COUNT is nil +then goes all the way up. Examines and sets property 'javaimp-parse-scope' at each scope's open brace." (let ((state (syntax-ppss)) res) @@ -356,19 +356,12 @@ nil then goes all the way up. Examines and sets property (if (javaimp-scope-start scope) (goto-char (javaimp-scope-start scope))))) (setq state (syntax-ppss)))) - ;; if a class is enclosed in anything other than a class, then it - ;; should be local - (let ((tmp res) - in-local parent) - (while tmp - (if (javaimp--is-classlike (car tmp)) - (when in-local - (setf (javaimp-scope-type (car tmp)) 'local-class)) - (setq in-local t)) - (setf (javaimp-scope-parent (car tmp)) parent) - (setq parent (car tmp)) - (setq tmp (cdr tmp)))) - res)) + (let (parent) + (while res + (setf (javaimp-scope-parent (car res)) parent) + (setq parent (car res)) + (setq res (cdr res))) + parent))) (defun javaimp--parse-all-scopes () "Entry point to the scope parsing. Parses scopes in this @@ -403,20 +396,26 @@ non-nil. Resets this variable after parsing is done." (javaimp--parse-rsb-keyword ";" nil t -1) ;; we're in the same nest (= (nth 1 (syntax-ppss)) enclosing)) - (backward-char) ;skip semicolon + (backward-char) ;skip semicolon ;; now parse as normal method scope (when-let ((scope (javaimp--parse-scope-method-or-stmt (point))) - ;; note that an abstract method with no parents - ;; will be ignored - (parents (javaimp--parse-scopes nil))) - (setf (javaimp-scope-parent scope) (car (last parents))) + ;; note that an abstract method with no + ;; parents will be ignored + (parent (javaimp--parse-scopes nil))) + (setf (javaimp-scope-parent scope) (javaimp--copy-scope parent)) (push scope res))))))) res)) +(defun javaimp--parse-abstract-interface-methods () + ;; TODO + ) + + ;; Functions intended to be called from other parts of javaimp. (defun javaimp--parse-get-package () + "Return the package declared in the current file." (save-excursion (save-restriction (widen) @@ -425,39 +424,6 @@ non-nil. Resets this variable after parsing is done." "^[ \t]*package[ \t]+\\([^ \t;\n]+\\)[ \t]*;" nil t 1) (match-string 1))))) -(defun javaimp--parse-get-all-classlikes () - (mapcar (lambda (scope) - (let ((name (javaimp-scope-name scope)) - (parent-names (javaimp--concat-scope-parents scope))) - (if (string-empty-p parent-names) - name - (concat parent-names "." name)))) - (javaimp--parse-get-all-scopes #'javaimp--is-classlike))) - -(defun javaimp--parse-get-imenu-forest () - (let* ((methods (javaimp--parse-get-all-scopes - #'javaimp--is-imenu-included-method #'javaimp--is-classlike)) - (classes (javaimp--parse-get-all-scopes #'javaimp--is-classlike)) - (top-classes (seq-filter (lambda (s) - (null (javaimp-scope-parent s))) - classes)) - (ac-methods (javaimp--parse-abstract-class-methods))) - (mapcar - (lambda (top-class) - (message "Building tree for top-level class-like scope: %s" - (javaimp-scope-name top-class)) - (javaimp--build-tree top-class - (append methods - classes - ac-methods) - (lambda (el tested) - (equal el (javaimp-scope-parent tested))) - nil - (lambda (s1 s2) - (< (javaimp-scope-start s1) - (javaimp-scope-start s2))))) - top-classes))) - (defun javaimp--parse-get-all-scopes (&optional pred parent-pred) "Return all scopes in the current buffer, optionally filtering them with PRED, and their parents with PARENT-PRED. Neither of @@ -479,6 +445,13 @@ them should move point." (push scope res))) res)))) +(defun javaimp--parse-abstract-methods () + (save-excursion + (save-restriction + (widen) + (append (javaimp--parse-abstract-class-methods) + (javaimp--parse-abstract-interface-methods))))) + (defun javaimp--parse-update-dirty-pos (beg _end _old-len) "Function to add to `after-change-functions' hook." (when (or (not javaimp--parse-dirty-pos) diff --git a/javaimp-tests.el b/javaimp-tests.el index 06162fa..93dfcc4 100644 --- a/javaimp-tests.el +++ b/javaimp-tests.el @@ -173,7 +173,7 @@ throws E1 {" (should (equal (javaimp-scope-name (car scopes)) (nth 2 item))))))) -;; Tests for javaimp-parse.el "package-private" API. +;; Tests for parsing (ert-deftest javaimp-test--parse-get-package () (with-temp-buffer @@ -185,126 +185,107 @@ package commented.block; (setq syntax-ppss-table javaimp-syntax-table) (should (equal (javaimp--parse-get-package) "foo.bar.baz")))) -(ert-deftest javaimp-test--parse-get-all-classlikes () - (with-temp-buffer - (insert-file-contents - (concat javaimp--basedir "testdata/test1-misc-classes.java")) - (setq syntax-ppss-table javaimp-syntax-table) - (setq javaimp--parse-dirty-pos (point-min)) - (should (equal (javaimp--parse-get-all-classlikes) - '("Top" - "Top.CInner1" - "Top.CInner1.CInner1_CInner1" - "Top.IInner1" - "Top.IInner1.IInner1_CInner1" - "Top.IInner1.IInner1_IInner1" - "Top.EnumInner1" - "Top.EnumInner1.EnumInner1_EInner1" - "ColocatedTop"))))) - (ert-deftest javaimp-test--parse-get-all-scopes () (with-temp-buffer (insert-file-contents (concat javaimp--basedir "testdata/test1-misc-classes.java")) (setq syntax-ppss-table javaimp-syntax-table) (let ((javaimp-format-method-name #'javaimp-format-method-name-types)) - ;; ;; parse full buffer (setq javaimp--parse-dirty-pos (point-min)) - (javaimp-test--check-named-scopes - (javaimp--parse-get-all-scopes - #'javaimp--is-named #'javaimp--is-named)) + (javaimp-test--check-named-scopes) ;; ;; reparse half of buffer (setq javaimp--parse-dirty-pos (/ (- (point-max) (point-min)) 2)) - (javaimp-test--check-named-scopes - (javaimp--parse-get-all-scopes - #'javaimp--is-named #'javaimp--is-named)) + (javaimp-test--check-named-scopes) ;; ;; don't reparse - (javaimp-test--check-named-scopes - (javaimp--parse-get-all-scopes - #'javaimp--is-named #'javaimp--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")) - - ((method "toString()") - (class "CInner1") (class "Top")) - - ((class "CInner1_CInner1") (class "CInner1") (class "Top")) - ((method "foo()") - (class "CInner1_CInner1") (class "CInner1") (class "Top")) - ((method "bar()") - (class "CInner1_CInner1") (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(String)") - (interface "IInner1") (class "Top")) - - ((interface "IInner1_IInner1") (interface "IInner1") (class "Top")) - ((method "defaultMethod(String)") - (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")) - ((method "bar(String, String)") (class "ColocatedTop"))))) + (javaimp-test--check-named-scopes)))) + +(defun javaimp-test--check-named-scopes () + (let* ((scopes (javaimp--parse-get-all-scopes + (lambda (scope) + (memq (javaimp-scope-type scope) '(class interface enum method))) + (lambda (scope) + (memq (javaimp-scope-type scope) '(class interface enum method))))) + (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")) + ((class "CInner1_CLocal1") + (method "foo()") (class "CInner1") (class "Top")) + ((method "foo()") + (class "CInner1_CLocal1") + (method "foo()") (class "CInner1") (class "Top")) + ((class "CInner1_CLocal1_CLocal1") + (method "foo()") + (class "CInner1_CLocal1") + (method "foo()") (class "CInner1") (class "Top")) + ((method "foo()") + (class "CInner1_CLocal1_CLocal1") + (method "foo()") + (class "CInner1_CLocal1") + (method "foo()") (class "CInner1") (class "Top")) + + ((class "CInner1_CLocal2") + (method "foo()") (class "CInner1") (class "Top")) + ((method "foo()") + (class "CInner1_CLocal2") + (method "foo()") (class "CInner1") (class "Top")) + + ((method "toString()") + (class "CInner1") (class "Top")) + + ((class "CInner1_CInner1") (class "CInner1") (class "Top")) + ((method "foo()") + (class "CInner1_CInner1") (class "CInner1") (class "Top")) + ((method "bar()") + (class "CInner1_CInner1") (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(String)") + (interface "IInner1") (class "Top")) + + ((interface "IInner1_IInner1") (interface "IInner1") (class "Top")) + ((method "defaultMethod(String)") + (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")) + ((method "bar(String, String)") (class "ColocatedTop"))))) (should (= (length expected) (length actual))) (dotimes (i (length expected)) - (should (equal (nth i expected) (nth i actual))))) - ;; - (let ((data + (should (equal (nth i expected) (nth i actual)))) + ;; + (let ((data `((,(nth 0 scopes) "Top" 26 36) (,(nth 16 scopes) "foo()" 1798 1804) (,(nth 23 scopes) "EnumInner1_EInner1" 2462 2486) (,(nth 25 scopes) "foo()" 2554 2560)))) - (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))))))) + (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)))))))) @@ -404,4 +385,23 @@ package commented.block; (dotimes (i (length expected-names)) (should (equal (nth i expected-names) (car (nth i actual))))))) + +(ert-deftest javaimp-test--get-file-classes () + (with-temp-buffer + (insert-file-contents + (concat javaimp--basedir "testdata/test1-misc-classes.java")) + (setq syntax-ppss-table javaimp-syntax-table) + (setq javaimp--parse-dirty-pos (point-min)) + (should (equal (javaimp--get-file-classes-1) + '("org.foo.Top" + "org.foo.Top.CInner1" + "org.foo.Top.CInner1.CInner1_CInner1" + "org.foo.Top.IInner1" + "org.foo.Top.IInner1.IInner1_CInner1" + "org.foo.Top.IInner1.IInner1_IInner1" + "org.foo.Top.EnumInner1" + "org.foo.Top.EnumInner1.EnumInner1_EInner1" + "org.foo.ColocatedTop"))))) + + (provide 'javaimp-tests) diff --git a/javaimp-util.el b/javaimp-util.el index 588e8bf..15052d2 100644 --- a/javaimp-util.el +++ b/javaimp-util.el @@ -32,15 +32,16 @@ (defconst javaimp--classlike-scope-types '(class interface enum)) -(defconst javaimp--named-scope-types - (append - '(local-class method) - javaimp--classlike-scope-types)) - (defconst javaimp--all-scope-types (append - '(anonymous-class statement simple-statement array unknown) - javaimp--named-scope-types)) + '(anonymous-class + array + method + simple-statement + statement + array + unknown) + javaimp--classlike-scope-types)) (defconst javaimp--help-scope-type-abbrevs '((anonymous-class . "ac") @@ -48,7 +49,6 @@ (simple-statement . "ss") (array . "ar") (unknown . "un") - (local-class . "lc") (method . "me") (class . "cl") (interface . "in") @@ -109,20 +109,6 @@ ;; Scopes -(defsubst javaimp--is-classlike (scope) - (and scope - (memq (javaimp-scope-type scope) - javaimp--classlike-scope-types))) - -(defsubst javaimp--is-named (scope) - (and scope - (memq (javaimp-scope-type scope) - javaimp--named-scope-types))) - -(defsubst javaimp--is-imenu-included-method (scope) - (and (eq (javaimp-scope-type scope) 'method) - (javaimp--is-classlike (javaimp-scope-parent scope)))) - (defun javaimp--copy-scope (scope) "Recursively copies SCOPE and its parents." (let* ((res (copy-javaimp-scope scope)) @@ -150,6 +136,13 @@ left." (push scope parents)) (mapconcat #'javaimp-scope-name parents "."))) +(defsubst javaimp-test-scope-type (scope leaf-types parent-types) + (declare (indent 1)) + (let ((res (memq (javaimp-scope-type scope) leaf-types))) + (while (and res + (setq scope (javaimp-scope-parent scope))) + (setq res (memq (javaimp-scope-type scope) parent-types))) + res)) ;;; Formatting @@ -202,37 +195,41 @@ recursive calls." (setf (javaimp-node-children this-node) child-nodes) this-node))) -(defun javaimp--find-node (pred forest &optional unwrap) +(defun javaimp--find-node (contents-pred forest &optional unwrap) + "Return first node for which CONTENTS-PRED returns non-nil. If +UNWRAP is non-nil, then node contents is returned." (catch 'found (dolist (tree forest) - (javaimp--find-node-in-tree tree pred unwrap)))) + (javaimp--find-node-in-tree tree contents-pred unwrap)))) -(defun javaimp--find-node-in-tree (tree pred unwrap) +(defun javaimp--find-node-in-tree (tree contents-pred unwrap) (when tree - (if (funcall pred (javaimp-node-contents tree)) + (if (funcall contents-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 child pred unwrap)))) + (javaimp--find-node-in-tree child contents-pred unwrap)))) -(defun javaimp--collect-nodes (pred forest) +(defun javaimp--collect-nodes (contents-pred forest) + "Return all nodes' contents for which CONTENTS-PRED returns +non-nil." (apply #'seq-concatenate 'list (mapcar (lambda (tree) (delq nil - (javaimp--collect-nodes-from-tree tree pred))) + (javaimp--collect-nodes-from-tree tree contents-pred))) forest))) -(defun javaimp--collect-nodes-from-tree (tree pred) +(defun javaimp--collect-nodes-from-tree (tree contents-pred) (when tree - (cons (and (funcall pred (javaimp-node-contents tree)) + (cons (and (funcall contents-pred (javaimp-node-contents tree)) (javaimp-node-contents tree)) (apply #'seq-concatenate 'list (mapcar (lambda (child) (delq nil - (javaimp--collect-nodes-from-tree child pred))) + (javaimp--collect-nodes-from-tree child contents-pred))) (javaimp-node-children tree)))))) diff --git a/javaimp.el b/javaimp.el index c8c7bc8..cf77216 100644 --- a/javaimp.el +++ b/javaimp.el @@ -465,11 +465,11 @@ prefix arg is given, don't do this filtering." default-directory)))) (defun javaimp--get-directory-classes (dir) - (if (file-accessible-directory-p dir) - (seq-mapcat #'javaimp--get-file-classes - (seq-filter (lambda (file) - (not (file-symlink-p file))) - (directory-files-recursively dir "\\.java\\'"))))) + (when (file-accessible-directory-p dir) + (seq-mapcat #'javaimp--get-file-classes + (seq-filter (lambda (file) + (not (file-symlink-p file))) + (directory-files-recursively dir "\\.java\\'"))))) (defun javaimp--get-file-classes (file) (let ((buf (seq-find (lambda (b) (equal (buffer-file-name b) file)) @@ -483,12 +483,25 @@ prefix arg is given, don't do this filtering." (javaimp--get-file-classes-1))))) (defun javaimp--get-file-classes-1 () - (let ((package (javaimp--parse-get-package))) + "Return fully-qualified names of all class-like scopes." + (let ((package (javaimp--parse-get-package)) + (scopes (javaimp--parse-get-all-scopes + (lambda (scope) + (javaimp-test-scope-type scope + javaimp--classlike-scope-types + javaimp--classlike-scope-types))))) (mapcar (lambda (class) (if package (concat package "." class) class)) - (javaimp--parse-get-all-classlikes)))) + (mapcar (lambda (scope) + (let ((name (javaimp-scope-name scope)) + (parent-names (javaimp--concat-scope-parents scope))) + (if (string-empty-p parent-names) + name + (concat parent-names "." name)))) + scopes)))) + ;; Organizing imports @@ -624,15 +637,10 @@ the `java-mode-hook': In future, when we implement a minor / major mode, it will be done in mode functions automatically." - (let ((forest (javaimp--parse-get-imenu-forest))) + (let ((forest (javaimp-imenu--get-forest))) (cond ((not javaimp-imenu-group-methods) ;; plain list of methods - (let ((entries - (mapcar #'javaimp-imenu--make-entry - (seq-sort-by - #'javaimp-scope-start #'< - (javaimp--collect-nodes - #'javaimp--is-imenu-included-method forest)))) + (let ((entries (javaimp-imenu--make-entries-simple forest)) name-alist) (mapc (lambda (entry) (setf (alist-get (car entry) name-alist 0 nil #'equal) @@ -655,27 +663,64 @@ done in mode functions automatically." (nth 3 entry)) "." (car entry)))) - (mapcar #'javaimp-imenu--make-entry - (seq-sort-by - #'javaimp-scope-start #'< - (javaimp--collect-nodes - #'javaimp--is-imenu-included-method forest))))) - + (javaimp-imenu--make-entries-simple forest))) (t ;; group methods inside their enclosing class (javaimp--map-nodes (lambda (scope) - (cond ((javaimp--is-classlike scope) - ;; sub-alist - (cons t (javaimp-scope-name scope))) - ((javaimp--is-imenu-included-method scope) - ;; entry - (cons nil (javaimp-imenu--make-entry scope))))) + (if (eq (javaimp-scope-type scope) 'method) + ;; entry + (cons nil (javaimp-imenu--make-entry scope)) + ;; sub-alist + (cons t (javaimp-scope-name scope)))) (lambda (res) (or (functionp (nth 2 res)) ;entry (cdr res))) ;non-empty sub-alist forest))))) +(defun javaimp-imenu--get-forest () + (let* ((scopes + (javaimp--parse-get-all-scopes + (lambda (scope) + (javaimp-test-scope-type scope + '(class interface enum method) + javaimp--classlike-scope-types)))) + (methods (seq-filter + (lambda (scope) + (eq (javaimp-scope-type scope) 'method)) + scopes)) + (classes (seq-filter + (lambda (scope) + (not (eq (javaimp-scope-type scope) 'method))) + scopes)) + (top-classes (seq-filter (lambda (s) + (null (javaimp-scope-parent s))) + classes)) + (abstract-methods (javaimp--parse-abstract-methods))) + (mapcar + (lambda (top-class) + (message "Building tree for top-level class-like scope: %s" + (javaimp-scope-name top-class)) + (javaimp--build-tree top-class + (append methods + classes + abstract-methods) + (lambda (el tested) + (equal el (javaimp-scope-parent tested))) + nil + (lambda (s1 s2) + (< (javaimp-scope-start s1) + (javaimp-scope-start s2))))) + top-classes))) + +(defun javaimp-imenu--make-entries-simple (forest) + (mapcar #'javaimp-imenu--make-entry + (seq-sort-by #'javaimp-scope-start #'< + (javaimp--collect-nodes + (lambda (scope) + (eq (javaimp-scope-type scope) 'method)) + forest)))) + (defsubst javaimp-imenu--make-entry (scope) (list (javaimp-scope-name scope) (if imenu-use-markers