[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/javaimp 894f428: Support imenu, first working version
From: |
Filipp Gunbin |
Subject: |
[elpa] externals/javaimp 894f428: Support imenu, first working version |
Date: |
Thu, 5 Aug 2021 16:30:03 -0400 (EDT) |
branch: externals/javaimp
commit 894f4284b6d9fbf728fd7e9767925b757a56d94f
Author: Filipp Gunbin <fgunbin@fastmail.fm>
Commit: Filipp Gunbin <fgunbin@fastmail.fm>
Support imenu, first working version
---
javaimp-gradle.el | 17 +-
javaimp-maven.el | 18 +-
javaimp-parse.el | 226 ++++++++--------
javaimp-tests.el | 468 ++++++++++++++++++++++++----------
javaimp-util.el | 262 ++++++++++++++-----
javaimp.el | 195 +++++++++++---
testdata/test-get-file-classes-1.java | 60 -----
testdata/test1-misc-classes.java | 125 +++++++++
8 files changed, 955 insertions(+), 416 deletions(-)
diff --git a/javaimp-gradle.el b/javaimp-gradle.el
index 85e3f2e..af3fd5a 100644
--- a/javaimp-gradle.el
+++ b/javaimp-gradle.el
@@ -20,13 +20,6 @@
(require 'javaimp-util)
-(defcustom javaimp-gradle-program "gradle"
- "Path to the `gradle' program. Customize it if the program is
-not on `exec-path'. If the visited project's directory contains
-gradlew program, it is used in preference."
- :group 'javaimp
- :type 'string)
-
(defconst javaimp--gradle-task-body
(with-temp-buffer
(insert-file-contents (expand-file-name "gradleTaskBody.inc.kts"
javaimp--basedir))
@@ -48,7 +41,15 @@ information."
(javaimp--gradle-module-from-alist alist file))
alists)))
;; first module is always root
- (javaimp--build-tree (car modules) nil modules)))
+ (message "Building tree for root: %s"
+ (javaimp-print-id (javaimp-module-id (car modules))))
+ (javaimp--build-tree (car modules) modules
+ ;; more or less reliable way to find children
+ ;; is to look for modules with "this" as the
+ ;; parent
+ (lambda (el tested)
+ (equal (javaimp-module-parent-id tested)
+ (javaimp-module-id el))))))
(defun javaimp--gradle-handler ()
(goto-char (point-min))
diff --git a/javaimp-maven.el b/javaimp-maven.el
index eee886f..73d914d 100644
--- a/javaimp-maven.el
+++ b/javaimp-maven.el
@@ -23,13 +23,6 @@
(require 'javaimp-util)
-(defcustom javaimp-mvn-program "mvn"
- "Path to the `mvn' program. Customize it if the program is not
-on `exec-path'."
- :group 'javaimp
- :type 'string)
-
-
(defun javaimp--maven-visit (file)
"Calls \"mvn help:effective-pom\" on FILE,
reads project structure from the output and records which files
@@ -87,7 +80,16 @@ resulting module trees."
modules)))
(cdr modules)))))
(mapcar (lambda (root)
- (javaimp--build-tree root nil modules))
+ (message "Building tree for root: %s"
+ (javaimp-print-id (javaimp-module-id root)))
+ (javaimp--build-tree
+ root modules
+ ;; more or less reliable way to find
+ ;; children is to look for modules with
+ ;; "this" as the parent
+ (lambda (el tested)
+ (equal (javaimp-module-parent-id tested)
+ (javaimp-module-id el)))))
roots))))
(defun javaimp--maven-effective-pom-handler ()
diff --git a/javaimp-parse.el b/javaimp-parse.el
index fca0369..0c31506 100644
--- a/javaimp-parse.el
+++ b/javaimp-parse.el
@@ -18,52 +18,31 @@
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-(require 'cl-lib)
-(require 'seq)
-(require 'cc-mode) ;for java-mode-syntax-table
(require 'javaimp-util)
-(defcustom javaimp-parse-format-method-name
- #'javaimp--parse-format-method-name-full
- "Function to format method name, invoked with 3 arguments:
-NAME, ARGS and THROWS-ARGS. The last two are lists with elements
-of the form (TYPE . NAME). For THROWS-ARGS, only TYPE is
-present."
- :group 'javaimp
- :type 'function)
-
-(cl-defstruct javaimp-scope
- type ; one of anonymous-class, class, interface, enum, local-class,
- ; method, statement, simple-statement, array, unknown
- name
- start
- open-brace)
-
-(defconst javaimp--parse-class-keywords
- '("class" "interface" "enum"))
+(defconst javaimp--parse-classlike-keywords
+ (mapcar #'symbol-name
+ javaimp--classlike-scope-types))
+
(defconst javaimp--parse-stmt-keywords
'("if" "for" "while" "switch" "try" "catch" "finally"
- "static" ;static initializer block
+ "static" ; static initializer block
))
-(defsubst javaimp--parse-is-class (scope)
- (member (symbol-name (javaimp-scope-type scope))
javaimp--parse-class-keywords))
+(defvar-local javaimp--parse-dirty-pos nil
+ "Buffer position after which all parsed information should be
+considered as stale. Usually set by modification change hooks.
+Should be set to (point-min) in major mode hook.")
-(defvar javaimp--arglist-syntax-table
- (let ((st (make-syntax-table java-mode-syntax-table))) ;TODO don't depend
- (modify-syntax-entry ?< "(>" st)
- (modify-syntax-entry ?> ")<" st)
- (modify-syntax-entry ?. "_" st) ; separates parts of fully-qualified type
- st)
- "Enables parsing angle brackets as lists")
-(defmacro javaimp--parse-with-arglist-syntax (beg &rest body)
- (declare (debug t))
+(defmacro javaimp--parse-with-syntax-table (syntax-table beg &rest body)
+ (declare (debug t)
+ (indent 2))
(let ((begin (make-symbol "begin")))
`(let ((,begin ,beg))
(syntax-ppss-flush-cache ,begin)
(unwind-protect
- (with-syntax-table javaimp--arglist-syntax-table
+ (with-syntax-table ,syntax-table
,@body)
(syntax-ppss-flush-cache ,begin)))))
@@ -90,7 +69,7 @@ point is outside of any context initially."
"Parse arg list between BEG and END, of the form 'TYPE NAME,
...'. Return list of conses (TYPE . NAME). If ONLY-TYPE is
non-nil, then name parsing is skipped."
- (javaimp--parse-with-arglist-syntax beg
+ (javaimp--parse-with-syntax-table javaimp--arglist-syntax-table beg
(save-excursion
(save-restriction
(narrow-to-region beg end)
@@ -207,7 +186,7 @@ is unchanged."
(catch 'found
(while (javaimp--parse-rsb-keyword regexp bound t)
(let ((scan-pos (match-end 0)))
- (javaimp--parse-with-arglist-syntax scan-pos
+ (javaimp--parse-with-syntax-table javaimp--arglist-syntax-table
scan-pos
(while (and scan-pos (<= scan-pos (nth 1 state)))
(if (ignore-errors
(= (scan-lists scan-pos 1 -1) ;As in
javaimp--parse-preceding
@@ -221,32 +200,6 @@ is unchanged."
(goto-char pos)
nil)))
-
-
-;;; Formatting
-
-(defsubst javaimp--parse-format-method-name-full (name args throws-args)
- "Outputs NAME, ARGS (name and type) and THROWS-ARGS (only type)."
- (concat name
- "("
- (mapconcat (lambda (arg)
- (concat (car arg) " " (cdr arg)))
- args
- ", ")
- ")"
- (if throws-args
- (concat " throws "
- (mapconcat #'car throws-args ", ")))
- ))
-
-(defsubst javaimp--parse-format-method-name-types (name args _throws-args)
- "Outputs NAME and ARGS (only type)."
- (concat name
- "("
- (mapconcat #'car args ", ")
- ")"
- ))
-
;;; Scopes
@@ -265,7 +218,7 @@ is unchanged."
"Attempts to parse 'class' / 'interface' / 'enum' scope. Some of
those may later become 'local-class' (see `javaimp--parse-scopes')."
(save-excursion
- (if (javaimp--parse-preceding (regexp-opt javaimp--parse-class-keywords
'words)
+ (if (javaimp--parse-preceding (regexp-opt
javaimp--parse-classlike-keywords 'words)
(nth 1 state))
(let* ((keyword-start (match-beginning 1))
(keyword-end (match-end 1))
@@ -335,6 +288,7 @@ those may later become 'local-class' (see
`javaimp--parse-scopes')."
(javaimp--parse-skip-back-until)
(= (char-before) ?\)))
(ignore-errors
+ ;; for method this is arglist
(goto-char
(scan-lists (point) -1 0))))
(let* (;; leave open/close parens out
@@ -355,7 +309,7 @@ those may later become 'local-class' (see
`javaimp--parse-scopes')."
:type type
:name (if (eq type 'statement)
name
- (funcall javaimp-parse-format-method-name
+ (funcall javaimp-format-method-name
name
(javaimp--parse-arglist (car arglist-region)
(cdr arglist-region))
@@ -367,14 +321,14 @@ those may later become 'local-class' (see
`javaimp--parse-scopes')."
"Attempts to parse 'array' scope."
(save-excursion
(and (javaimp--parse-skip-back-until)
- (member (char-before) '(?, ?{ ?\]))
+ (member (char-before) '(?, ?\]))
(make-javaimp-scope :type 'array
:name ""
:start nil
:open-brace (nth 1 state)))))
(defun javaimp--parse-scope-unknown (state)
- "Catch-all parser which produces `unknown' scope."
+ "Catch-all parser which produces 'unknown' scope."
(make-javaimp-scope :type 'unknown
:name "unknown"
:start nil
@@ -382,58 +336,122 @@ those may later become 'local-class' (see
`javaimp--parse-scopes')."
(defun javaimp--parse-scopes (count)
"Attempts to parse COUNT enclosing scopes at point. If COUNT is
-nil then goes all the way up."
- (let ((state (syntax-ppss)) res)
+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)
(unless (syntax-ppss-context state)
- (save-excursion
- (while (and (nth 1 state)
- (or (not count)
- (>= (setq count (1- count)) 0)))
- ;; find innermost enclosing open-bracket
- (goto-char (nth 1 state))
- (when (= (char-after) ?{)
- (let ((scope (run-hook-with-args-until-success
- 'javaimp--parse-scope-hook state)))
- (push scope res)
- (if (javaimp-scope-start scope)
- (goto-char (javaimp-scope-start scope)))))
- (setq state (syntax-ppss)))))
+ (while (and (nth 1 state)
+ (or (not count)
+ (>= (setq count (1- count)) 0)))
+ ;; find innermost enclosing open-bracket
+ (goto-char (nth 1 state))
+ (when (= (char-after) ?{)
+ (let ((scope (get-text-property (point) 'javaimp-parse-scope)))
+ (unless scope
+ (setq scope (run-hook-with-args-until-success
+ 'javaimp--parse-scope-hook state))
+ (put-text-property (point) (1+ (point))
+ 'javaimp-parse-scope scope))
+ (push scope res)
+ (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)
+ in-local parent)
(while tmp
- (if (javaimp--parse-is-class (car tmp))
- (if in-local
- (setf (javaimp-scope-type (car tmp)) 'local-class))
+ (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))
+(defun javaimp--parse-all-scopes ()
+ "Entry point to the scope parsing. Parses scopes in this buffer
+which are after `javaimp--parse-dirty-pos', if it is non-nil.
+Resets this variable after parsing is done."
+ (when javaimp--parse-dirty-pos
+ (remove-text-properties javaimp--parse-dirty-pos (point-max)
+ '(javaimp-parse-scope nil))
+ (goto-char (point-max))
+ ;; FIXME With major mode we could set these, as well as syntax
+ ;; table, in mode function.
+ (let ((parse-sexp-ignore-comments t)
+ (parse-sexp-lookup-properties nil))
+ (javaimp--parse-with-syntax-table javaimp-syntax-table (point-min)
+ (while (javaimp--parse-rsb-keyword "{" javaimp--parse-dirty-pos t)
+ (save-excursion
+ (forward-char)
+ ;; Set props at this brace and all the way up
+ (javaimp--parse-scopes nil)))))
+ (setq javaimp--parse-dirty-pos nil)))
+
-;; Main
+;; Functions intended to be called from other parts of javaimp.
(defun javaimp--parse-get-package ()
(goto-char (point-max))
- (when (javaimp--parse-rsb-keyword
- "^\\s-*package\\s-+\\([^;\n]+\\)\\s-*;" nil t 1)
- (match-string 1)))
-
-(defun javaimp--parse-get-file-classes ()
- (goto-char (point-max))
- (let (res)
- (while (javaimp--parse-rsb-keyword
- (regexp-opt javaimp--parse-class-keywords 'words) nil t)
- (save-excursion
- (let ((parse-sexp-ignore-comments t) ; FIXME remove with major mode
- (parse-sexp-lookup-properties nil))
- (when (and (ignore-errors
- (goto-char (scan-lists (point) 1 -1)))
- (= (char-before) ?{))
- (let ((scopes (javaimp--parse-scopes nil)))
- (when (seq-every-p #'javaimp--parse-is-class scopes)
- (push (mapconcat #'javaimp-scope-name scopes ".") res)))))))
+ (javaimp--parse-with-syntax-table javaimp-syntax-table (point-min)
+ (when (javaimp--parse-rsb-keyword
+ "^[ \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)))
+ (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)
+ (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."
+ (javaimp--parse-all-scopes)
+ (let ((pos (point-max))
+ scope res)
+ (while (setq pos (previous-single-property-change pos
'javaimp-parse-scope))
+ (setq scope (get-text-property pos 'javaimp-parse-scope))
+ (when (and scope
+ (or (null pred)
+ (funcall pred scope)))
+ (setq scope (javaimp--copy-scope scope))
+ (when parent-pred
+ (javaimp--filter-scope-parents scope parent-pred))
+ (push scope res)))
res))
+(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)
+ (< beg javaimp--parse-dirty-pos))
+ (setq javaimp--parse-dirty-pos beg)))
+
(provide 'javaimp-parse)
diff --git a/javaimp-tests.el b/javaimp-tests.el
index 0ca4593..755e644 100644
--- a/javaimp-tests.el
+++ b/javaimp-tests.el
@@ -8,119 +8,7 @@
(require 'ert)
(require 'javaimp)
-;; (ert-deftest javaimp-test--maven-projects-from-xml--project ()
-;; (with-temp-buffer
-;; (insert "<project/>")
-;; (let ((projects (javaimp--maven-projects-from-xml
-;; (xml-parse-region (point-min) (point-max)))))
-;; (should (eql (length projects) 1)))))
-
-;; (ert-deftest javaimp-test--maven-projects-from-xml--projects ()
-;; (with-temp-buffer
-;; (insert "<projects><project/><project/></projects>")
-;; (let ((projects (javaimp--maven-projects-from-xml
-;; (xml-parse-region (point-min) (point-max)))))
-;; (should (eql (length projects) 2)))))
-
-
-(defun javaimp-test--check-scope (parse-hook &rest test-items)
- (declare (indent 1))
- (dolist (item test-items)
- (with-temp-buffer
- (insert (nth 0 item))
- (java-mode)
- (let* ((parse-sexp-ignore-comments t) ;FIXME remove with major mode
- (parse-sexp-lookup-properties nil)
- (javaimp--parse-scope-hook parse-hook)
- (scope (car (javaimp--parse-scopes 1))))
- (should-not (null scope))
- (should (eq (javaimp-scope-type scope) (nth 1 item)))
- (should (equal (javaimp-scope-name scope) (nth 2 item)))))))
-
-(ert-deftest javaimp-test--parse-scope-class ()
- (javaimp-test--check-scope #'javaimp--parse-scope-class
- '("class Foo {"
- class "Foo")
- '("class Foo extends Bar {"
- class "Foo")
- '("class Foo implements Bar {"
- class "Foo")
- '("class Foo implements Bar, Baz {"
- class "Foo")
- '("public class Foo extends Bar implements Baz1 , Baz2 {"
- class "Foo")
- `(,(subst-char-in-string
- ? ?\n
- "public class Foo extends Bar implements Baz1 , Baz2 {")
- class "Foo")
- '("class Foo<Bar, Baz> extends FooSuper<Bar, Baz> \
-implements Interface1<Bar, Baz>, Interface2 {"
- class "Foo")
- '("class Foo<E extends Bar> {"
- class "Foo")
- '("class Foo<Enum<?>> {"
- class "Foo")
- '("class Foo<T extends Baz<? extends Baz2>> \
-extends Bar<? extends Baz<? extends Baz2>> {"
- class "Foo")
- '("interface Foo<Bar, Baz> {"
- interface "Foo")
- '("private enum Foo {"
- enum "Foo")))
-
-(ert-deftest javaimp-test--parse-scope-anonymous-class ()
- (javaimp-test--check-scope #'javaimp--parse-scope-anonymous-class
- '(" = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {"
- anonymous-class "Object")
- `(,(subst-char-in-string
- ? ?\n
- " = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {")
- anonymous-class "Object")
- '(" = (obj.getField()).new Object<Class1, Class2>(1, baz) {"
- anonymous-class "Object")
- '(" = obj.new Object<>(1, baz) {"
- anonymous-class "Object")))
-
-(ert-deftest javaimp-test--parse-scope-method-or-stmt ()
- (javaimp-test--check-scope #'javaimp--parse-scope-method-or-stmt
- '("static void foo_bar ( String a , int b ) {"
- method "foo_bar(String a, int b)")
- `(,(subst-char-in-string
- ? ?\n
- "static void foo_bar ( String a , int b ) {")
- method "foo_bar(String a, int b)")
- '("void foo_bar(String a, int b) throws E1, E2 {"
- method "foo_bar(String a, int b) throws E1, E2")
- '("void foo_bar()
-throws E1 {"
- method "foo_bar() throws E1")
- '("if (foo_bar(a, b) < 2) {"
- statement "if")))
-
-(ert-deftest javaimp-test--parse-scope-simple-stmt ()
- (javaimp-test--check-scope #'javaimp--parse-scope-simple-stmt
- '(" try {"
- simple-statement "try")
- `(,(subst-char-in-string ? ?\n " try {")
- simple-statement "try")
- ;; static initializer
- '("static {"
- simple-statement "static")
- ;; lambda
- '("it -> {"
- simple-statement "lambda")
- '("(x, y) -> {"
- simple-statement "lambda")
- ))
-
-(ert-deftest javaimp-test--parse-scope-array ()
- (javaimp-test--check-scope #'javaimp--parse-scope-array
- '("new String[] {"
- array "")
- '("new Object[][] { {"
- array "")
- '("new int[] {{1, 2}, {"
- array "")))
+;; Tests for low-level helpers of scope parsers.
(ert-deftest javaimp-test--parse-arglist ()
(dolist (data '(("")
@@ -146,7 +34,6 @@ throws E1 {"
("String[][]" . "arr"))
))
(with-temp-buffer
- (java-mode)
(insert (car data))
(should (equal (javaimp--parse-arglist (point-min) (point-max))
(cdr data))))))
@@ -169,30 +56,347 @@ Exception4<? super Exception5>>")
("Exception6")
("Exception7<Exception8>"))))
(with-temp-buffer
- (java-mode)
(insert (car data))
(should (equal (javaimp--parse-arglist (point-min) (point-max) t)
(cdr data))))))
+
+;; Tests for scope parsers, which should be in
+;; `javaimp--parse-scope-hook'.
+
+(ert-deftest javaimp-test--parse-scope-class ()
+ (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-class))
+ (javaimp-test--check-single-scope
+ '("class Foo {"
+ class "Foo")
+ '("class Foo extends Bar {"
+ class "Foo")
+ '("class Foo implements Bar {"
+ class "Foo")
+ '("class Foo implements Bar, Baz {"
+ class "Foo")
+ '("public class Foo extends Bar implements Baz1 , Baz2 {"
+ class "Foo")
+ `(,(subst-char-in-string
+ ? ?\n
+ "public class Foo extends Bar implements Baz1 , Baz2 {")
+ class "Foo")
+ '("class Foo<Bar, Baz> extends FooSuper<Bar, Baz> \
+implements Interface1<Bar, Baz>, Interface2 {"
+ class "Foo")
+ '("class Foo<E extends Bar> {"
+ class "Foo")
+ '("class Foo<Enum<?>> {"
+ class "Foo")
+ '("class Foo<T extends Baz<? extends Baz2>> \
+extends Bar<? extends Baz<? extends Baz2>> {"
+ class "Foo")
+ '("interface Foo<Bar, Baz> {"
+ interface "Foo")
+ '("private enum Foo {"
+ enum "Foo")
+ )))
+
+(ert-deftest javaimp-test--parse-scope-anonymous-class ()
+ (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-anonymous-class))
+ (javaimp-test--check-single-scope
+ '(" = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {"
+ anonymous-class "Object")
+ `(,(subst-char-in-string
+ ? ?\n
+ " = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {")
+ anonymous-class "Object")
+ '(" = (obj.getField()).new Object<Class1, Class2>(1, baz) {"
+ anonymous-class "Object")
+ '(" = obj.new Object<>(1, baz) {"
+ anonymous-class "Object")
+ )))
+
+(ert-deftest javaimp-test--parse-scope-method-or-stmt ()
+ (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-method-or-stmt)
+ (javaimp-format-method-name #'javaimp-format-method-name-full))
+ (javaimp-test--check-single-scope
+ '("static void foo_bar ( String a , int b ) {"
+ method "foo_bar(String a, int b)")
+ `(,(subst-char-in-string
+ ? ?\n
+ "static void foo_bar ( String a , int b ) {")
+ method "foo_bar(String a, int b)")
+ '("void foo_bar(String a, int b) throws E1, E2 {"
+ method "foo_bar(String a, int b) throws E1, E2")
+ '("void foo_bar()
+throws E1 {"
+ method "foo_bar() throws E1")
+ '("if (foo_bar(a, b) < 2) {"
+ statement "if")
+ )))
+
+(ert-deftest javaimp-test--parse-scope-simple-stmt ()
+ (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-simple-stmt))
+ (javaimp-test--check-single-scope
+ '(" try {"
+ simple-statement "try")
+ `(,(subst-char-in-string ? ?\n " try {")
+ simple-statement "try")
+ ;; static initializer
+ '("static {"
+ simple-statement "static")
+ ;; lambda
+ '("it -> {"
+ simple-statement "lambda")
+ '("(x, y) -> {"
+ simple-statement "lambda")
+ )))
+
+(ert-deftest javaimp-test--parse-scope-array ()
+ (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-array))
+ (javaimp-test--check-single-scope
+ '("new String[] {"
+ array "")
+ ;; TODO fix
+ ;; '("new Object[][] { {"
+ ;; array "")
+ ;; '("new int[] {{1, 2}, {"
+ ;; array "")
+ )))
+
+(defun javaimp-test--check-single-scope (&rest test-items)
+ (dolist (item test-items)
+ (with-temp-buffer
+ (insert (nth 0 item))
+ (setq javaimp--parse-dirty-pos (point-min))
+ (let ((scopes (javaimp--parse-get-all-scopes)))
+ (should (= 1 (length scopes)))
+ (should (eq (javaimp-scope-type (car scopes)) (nth 1 item)))
+ (should (equal (javaimp-scope-name (car scopes)) (nth 2 item)))))))
+
+
+;; Tests for javaimp-parse.el "package-private" API.
+
(ert-deftest javaimp-test--parse-get-package ()
(with-temp-buffer
- (insert "//package org.commented1;
-/*package org.commented2;*/
- package org.foo;")
- (should (equal (javaimp--parse-get-package) "org.foo"))))
-
-
-(ert-deftest javaimp-test--get-file-classes ()
- (should (equal (javaimp--get-file-classes
- (concat javaimp--basedir
"testdata/test-get-file-classes-1.java"))
- '("org.foo.Top"
- "org.foo.Top.CInner1"
- "org.foo.Top.CInner1.CInner1_CInner1"
- "org.foo.Top.IInner1"
- "org.foo.Top.IInner1.IInner1_IInner1"
- "org.foo.Top.IInner1.IInner1_CInner1"
- "org.foo.Top.EInner1"
- "org.foo.Top.EInner1.EInner1_EInner1"))))
+ (insert " package foo.bar.baz ;
+//package commented.line;
+/*
+package commented.block;
+*/")
+ (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 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"))
+ (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))
+ ;;
+ ;; 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))
+ ;;
+ ;; 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")))))
+ (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 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)))))))
+
+
+
+;; Tests for imenu function
+
+(ert-deftest javaimp-test--imenu-group ()
+ (let* ((javaimp-imenu-group-methods t)
+ (javaimp-format-method-name #'javaimp-format-method-name-types)
+ (actual (with-temp-buffer
+ (insert-file-contents
+ (concat javaimp--basedir
"testdata/test1-misc-classes.java"))
+ (setq javaimp--parse-dirty-pos (point-min))
+ (javaimp-imenu-create-index))))
+ (javaimp-test--imenu-simplify-entries actual)
+ (should
+ (equal
+ '(("Top"
+ ("CInner1"
+ ("foo()" . 98)
+ ("CInner1_CInner1"
+ ("foo()" . 1099)
+ ("bar()" . 1192)))
+ ("IInner1"
+ ("foo()" . 1603)
+ ("IInner1_CInner1"
+ ("foo()" . 1798))
+ ("defaultMethod(String)" . 1963)
+ ("IInner1_IInner1"
+ ("defaultMethod(String)" . 2157)))
+ ("EnumInner1"
+ ("EnumInner1()" . 2353)
+ ("foo()" . 2399)
+ ;; "EnumInner1_EInner1" omitted because no methods inside
+ ))
+ ("ColocatedTop"
+ ("foo()" . 2554)
+ ("bar(String, String)" . 2578)))
+ actual))))
+
+(defun javaimp-test--imenu-simplify-entries (alist)
+ (dolist (elt alist)
+ (if (and (= (length elt) 4)
+ (functionp (nth 2 elt)))
+ (setcdr elt (nth 1 elt))
+ (javaimp-test--imenu-simplify-entries (cdr elt)))))
+
+
+(ert-deftest javaimp-test--imenu-simple ()
+ (let ((javaimp-format-method-name #'javaimp-format-method-name-types)
+ (javaimp-imenu-group-methods nil))
+ (javaimp-test--imenu-method-list 0)))
+
+(ert-deftest javaimp-test--imenu-qualified ()
+ (let ((javaimp-format-method-name #'javaimp-format-method-name-types)
+ (javaimp-imenu-group-methods 'qualified))
+ (javaimp-test--imenu-method-list 1)))
+
+(defconst javaimp-test--imenu-method-list-expected
+ '(("foo() [Top.CInner1]"
+ "Top.CInner1.foo()" 98)
+ ("foo() [Top.CInner1.CInner1_CInner1]"
+ "Top.CInner1.CInner1_CInner1.foo()" 1099)
+ ("bar()"
+ "Top.CInner1.CInner1_CInner1.bar()" 1192)
+ ("foo() [Top.IInner1]"
+ "Top.IInner1.foo()" 1603)
+ ("foo() [Top.IInner1.IInner1_CInner1]"
+ "Top.IInner1.IInner1_CInner1.foo()" 1798)
+ ("defaultMethod(String) [Top.IInner1]"
+ "Top.IInner1.defaultMethod(String)" 1963)
+ ("defaultMethod(String) [Top.IInner1.IInner1_IInner1]"
+ "Top.IInner1.IInner1_IInner1.defaultMethod(String)" 2157)
+ ("EnumInner1()"
+ "Top.EnumInner1.EnumInner1()" 2353)
+ ("foo() [Top.EnumInner1]"
+ "Top.EnumInner1.foo()" 2399)
+ ("foo() [ColocatedTop]"
+ "ColocatedTop.foo()" 2554)
+ ("bar(String, String)"
+ "ColocatedTop.bar(String, String)" 2578)))
+
+(defun javaimp-test--imenu-method-list (exp-name-idx)
+ (let ((actual
+ (with-temp-buffer
+ (insert-file-contents
+ (concat javaimp--basedir "testdata/test1-misc-classes.java"))
+ (setq javaimp--parse-dirty-pos (point-min))
+ (javaimp-imenu-create-index)))
+ (expected javaimp-test--imenu-method-list-expected))
+ (should (= (length expected) (length actual)))
+ (dotimes (i (length expected))
+ (let ((exp (nth i expected))
+ (act (nth i actual)))
+ ;; name
+ (should (equal (nth exp-name-idx exp) (nth 0 act)))
+ ;; pos
+ (should (= (nth 2 exp) (nth 1 act)))))))
(provide 'javaimp-tests)
diff --git a/javaimp-util.el b/javaimp-util.el
index 1544cc4..d058e82 100644
--- a/javaimp-util.el
+++ b/javaimp-util.el
@@ -25,17 +25,25 @@
(require 'cl-lib)
(require 'seq)
-(defcustom javaimp-cygpath-program
- (if (eq system-type 'cygwin) "cygpath")
- "Path to the `cygpath' program (Cygwin only). Customize it if
-the program is not on `exec-path'."
- :group 'javaimp
- :type 'string)
-
(defconst javaimp-debug-buf-name "*javaimp-debug*")
(defconst javaimp--basedir (file-name-directory load-file-name))
+(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))
+
+
+
;; Structs
(cl-defstruct javaimp-node
@@ -59,6 +67,16 @@ the program is not on `exec-path'."
(cl-defstruct javaimp-cached-jar ;jar or jmod
file read-ts classes)
+(cl-defstruct javaimp-scope
+ type ; see javaimp--all-scope-types
+ name
+ start
+ open-brace
+ parent)
+
+
+
+;; Xml
(defun javaimp--xml-children (xml-tree child-name)
"Returns list of children of XML-TREE filtered by CHILD-NAME"
@@ -76,15 +94,181 @@ the program is not on `exec-path'."
(car (cddr el)))
-(defun javaimp--get-file-ts (file)
- (nth 5 (file-attributes file)))
+
+;; 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))
+ (tmp res)
+ orig-parent)
+ (while (setq orig-parent (javaimp-scope-parent tmp))
+ (setf (javaimp-scope-parent tmp) (copy-javaimp-scope orig-parent))
+ (setq tmp (javaimp-scope-parent tmp)))
+ res))
+
+(defun javaimp--filter-scope-parents (scope pred)
+ "Rewrite SCOPE's parents so that only those matching PRED are
+left."
+ (while scope
+ (let ((parent (javaimp-scope-parent scope)))
+ (if (and parent
+ (not (funcall pred parent)))
+ ;; leave out this parent
+ (setf (javaimp-scope-parent scope) (javaimp-scope-parent parent))
+ (setq scope (javaimp-scope-parent scope))))))
+
+(defun javaimp--concat-scope-parents (scope)
+ (let (parents)
+ (while (setq scope (javaimp-scope-parent scope))
+ (push scope parents))
+ (mapconcat #'javaimp-scope-name parents ".")))
+
+
+
+;;; Formatting
+
+(defsubst javaimp-format-method-name-full (name args throws-args)
+ "Outputs NAME, ARGS (name and type) and THROWS-ARGS (only type)."
+ (concat name
+ "("
+ (mapconcat (lambda (arg)
+ (concat (car arg) " " (cdr arg)))
+ args
+ ", ")
+ ")"
+ (if throws-args
+ (concat " throws "
+ (mapconcat #'car throws-args ", ")))
+ ))
+
+(defsubst javaimp-format-method-name-types (name args _throws-args)
+ "Outputs NAME and ARGS (only type)."
+ (concat name
+ "("
+ (mapconcat #'car args ", ")
+ ")"
+ ))
+
+
+
+;; Tree
+
+(defun javaimp--build-tree (this all child-p &optional parent-node sort-pred)
+ "Recursively builds tree for element THIS and its children.
+Children are those elements from ALL for which CHILD-P invoked
+with this element and tested element returns non-nil. Children
+are sorted by SORT-PRED, if given. PARENT-NODE is indented for
+recursive calls."
+ (let ((children (seq-filter (apply-partially child-p this)
+ all)))
+ (if sort-pred
+ (setq children (sort children sort-pred)))
+ (let* ((this-node (make-javaimp-node
+ :parent parent-node
+ :children nil
+ :contents this))
+ (child-nodes
+ (mapcar (lambda (child)
+ (javaimp--build-tree
+ child all child-p this-node sort-pred))
+ children)))
+ (setf (javaimp-node-children this-node) child-nodes)
+ this-node)))
+
+(defun javaimp--find-node (pred forest &optional unwrap)
+ (catch 'found
+ (dolist (tree forest)
+ (javaimp--find-node-in-tree tree pred unwrap))))
+
+(defun javaimp--find-node-in-tree (tree pred unwrap)
+ (when 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 child pred unwrap))))
+
+
+(defun javaimp--collect-nodes (pred forest)
+ (apply #'seq-concatenate 'list
+ (mapcar (lambda (tree)
+ (delq nil
+ (javaimp--collect-nodes-from-tree tree pred)))
+ forest)))
+
+(defun javaimp--collect-nodes-from-tree (tree pred)
+ (when tree
+ (cons (and (funcall 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-node-children tree))))))
+
+
+(defun javaimp--map-nodes (function pred forest)
+ "Recursively applies FUNCTION to each node's contents in FOREST
+and returns new tree. FUNCTION should return (t . VALUE) if the
+result for this node should be made a list of the form (VALUE
+. CHILDREN), or (nil . VALUE) for plain VALUE as the result (in
+this case children are discarded). The result for each node is
+additionally tested by PRED."
+ (delq nil
+ (mapcar (lambda (tree)
+ (javaimp--map-nodes-from-tree tree function pred))
+ forest)))
+
+(defun javaimp--map-nodes-from-tree (tree function pred)
+ (when tree
+ (let* ((cell (funcall function (javaimp-node-contents tree)))
+ (res
+ (if (car cell)
+ (let ((children
+ (delq nil
+ (mapcar (lambda (child)
+ (javaimp--map-nodes-from-tree
+ child function pred))
+ (javaimp-node-children tree)))))
+ (cons (cdr cell) children))
+ (cdr cell))))
+ (and (funcall pred res)
+ res))))
-(defun javaimp-print-id (id)
+(defun javaimp--get-root (node)
+ (while (javaimp-node-parent node)
+ (setq node (javaimp-node-parent node)))
+ node)
+
+
+
+;; Other
+
+(defsubst javaimp-print-id (id)
(format "%s:%s:%s"
(javaimp-id-artifact id)
(javaimp-id-group id)
(javaimp-id-version id)))
+(defsubst javaimp--get-file-ts (file)
+ (nth 5 (file-attributes file)))
;; TODO use functions `cygwin-convert-file-name-from-windows' and
;; `cygwin-convert-file-name-to-windows' when they are available
@@ -110,7 +294,6 @@ unchanged."
(car (apply #'process-lines javaimp-cygpath-program args))))
path))
-
(defun javaimp--call-build-tool (program handler &rest args)
"Runs PROGRAM with ARGS, then calls HANDLER in the temporary
buffer and returns its result"
@@ -137,61 +320,4 @@ buffer and returns its result"
(concat "[" path-separator "\n]+")
t)))
-
-;; Tree building & search
-
-(defun javaimp--build-tree (this parent-node all)
- (message "Building tree for module: %s" (javaimp-print-id (javaimp-module-id
this)))
- (let ((children
- ;; more or less reliable way to find children is to look for
- ;; modules with "this" as the parent
- (seq-filter (lambda (m)
- (equal (javaimp-module-parent-id m) (javaimp-module-id
this)))
- all)))
- (let* ((this-node (make-javaimp-node
- :parent parent-node
- :children nil
- :contents this))
- ;; recursively build child nodes
- (child-nodes
- (mapcar (lambda (child)
- (javaimp--build-tree child this-node all))
- children)))
- (setf (javaimp-node-children this-node) child-nodes)
- this-node)))
-
-(defun javaimp--find-node (predicate forest)
- (catch 'found
- (dolist (tree forest)
- (javaimp--find-node-in-tree-1 tree predicate))))
-
-(defun javaimp--find-node-in-tree-1 (tree predicate)
- (when tree
- (if (funcall predicate (javaimp-node-contents tree))
- (throw 'found tree))
- (dolist (child (javaimp-node-children tree))
- (javaimp--find-node-in-tree-1 child predicate))))
-
-
-(defun javaimp--collect-nodes (predicate forest)
- (apply #'seq-concatenate 'list
- (mapcar (lambda (tree)
- (javaimp--collect-nodes-from-tree tree predicate))
- forest)))
-
-(defun javaimp--collect-nodes-from-tree (tree &optional predicate)
- (when tree
- (append (when (or (not predicate)
- (funcall predicate (javaimp-node-contents tree)))
- (list tree))
- (apply #'seq-concatenate 'list
- (mapcar (lambda (child)
- (javaimp--collect-nodes-from-tree child predicate))
- (javaimp-node-children tree))))))
-
-(defun javaimp--get-root (node)
- (while (javaimp-node-parent node)
- (setq node (javaimp-node-parent node)))
- node)
-
(provide 'javaimp-util)
diff --git a/javaimp.el b/javaimp.el
index 8309d50..c1ba563 100644
--- a/javaimp.el
+++ b/javaimp.el
@@ -78,6 +78,7 @@
(require 'javaimp-maven)
(require 'javaimp-gradle)
(require 'javaimp-parse)
+(require 'cc-mode) ;for java-mode-syntax-table
@@ -148,6 +149,42 @@ files in the current project and add their fully-qualified
names
to the completion alternatives list."
:type 'boolean)
+(defcustom javaimp-imenu-group-methods t
+ "How to lay out methods in Imenu index.
+If t (the default), methods are grouped in their enclosing
+scopes. nil means use just flat list of simple method names.
+`qualified' means use flat list where each method name is
+prepended with nested scopes. See also
+`javaimp-format-method-name'."
+ :type '(choice :tag "Group methods"
+ (const :tag "Group into enclosing scopes" t)
+ (const :tag "Flat list of simple name" nil)
+ (const :tag "Flat list of qualified names" qualified)))
+
+(defcustom javaimp-cygpath-program
+ (if (eq system-type 'cygwin) "cygpath")
+ "Path to the `cygpath' program (Cygwin only). Customize it if
+the program is not on `exec-path'."
+ :type 'string)
+
+(defcustom javaimp-format-method-name #'javaimp-format-method-name-full
+ "Function to format method name, invoked with 3 arguments:
+NAME, ARGS and THROWS-ARGS. The last two are lists with elements
+of the form (TYPE . NAME). For THROWS-ARGS, only TYPE is
+present."
+ :type 'function)
+
+(defcustom javaimp-mvn-program "mvn"
+ "Path to the `mvn' program. Customize it if the program is not
+on `exec-path'."
+ :type 'string)
+
+(defcustom javaimp-gradle-program "gradle"
+ "Path to the `gradle' program. Customize it if the program is
+not on `exec-path'. If the visited project's directory contains
+gradlew program, it is used in preference."
+ :type 'string)
+
;; Variables
@@ -160,6 +197,17 @@ to the completion alternatives list."
(defvar javaimp--jdk-classes 'need-init)
+(defvar javaimp-syntax-table
+ (make-syntax-table java-mode-syntax-table) ;TODO don't depend
+ "Javaimp syntax table")
+
+(defvar javaimp--arglist-syntax-table
+ (let ((st (make-syntax-table javaimp-syntax-table)))
+ (modify-syntax-entry ?< "(>" st)
+ (modify-syntax-entry ?> ")<" st)
+ (modify-syntax-entry ?. "_" st) ; separates parts of fully-qualified type
+ st)
+ "Enables parsing angle brackets as lists")
;;;###autoload
@@ -291,13 +339,17 @@ any module file."
;; do not expose tree structure, return only modules
(defun javaimp-find-module (predicate)
- (let ((node (javaimp--find-node predicate javaimp-project-forest)))
- (and node
- (javaimp-node-contents node))))
+ "Returns first module in `javaimp-project-forest' for which
+PREDICATE returns non-nil."
+ (javaimp--find-node predicate javaimp-project-forest t))
(defun javaimp-collect-modules (predicate)
- (mapcar #'javaimp-node-contents
- (javaimp--collect-nodes predicate javaimp-project-forest)))
+ "Returns all modules in `javaimp-project-forest' for which
+PREDICATE returns non-nil."
+ (javaimp--collect-nodes predicate javaimp-project-forest))
+
+(defun javaimp-map-modules (function)
+ (javaimp--map-nodes function #'always javaimp-project-forest))
;;; Adding imports
@@ -425,11 +477,12 @@ prefix arg is given, don't do this filtering."
(defun javaimp--get-file-classes (file)
(with-temp-buffer
(insert-file-contents file)
+ (setq javaimp--parse-dirty-pos (point-min))
(let ((package (javaimp--parse-get-package)))
(mapcar (lambda (class)
(if package
(concat package "." class)))
- (javaimp--parse-get-file-classes)))))
+ (javaimp--parse-get-all-classlikes)))))
;; Organizing imports
@@ -549,17 +602,73 @@ is `ordinary' or `static'. Interactively, NEW-IMPORTS is
nil."
-;; Misc
-
-(defun javaimp-reset (arg)
- "Forget loaded trees state. With prefix arg, also reset jars
-cache."
- (interactive "P")
- (setq javaimp-project-forest nil
- javaimp--jdk-classes 'need-init)
- (when arg
- (setq javaimp-cached-jars nil)))
+;; Imenu support
+;;;###autoload
+(defun javaimp-imenu-create-index ()
+ "Function to use as `imenu-create-index-function'."
+ (let ((forest (save-excursion
+ (javaimp--parse-get-imenu-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))))
+ name-alist)
+ (mapc (lambda (entry)
+ (setf (alist-get (car entry) name-alist 0 nil #'equal)
+ (1+ (alist-get (car entry) name-alist 0 nil
#'equal))))
+ entries)
+ (mapc (lambda (entry)
+ ;; disambiguate same method names
+ (when (> (alist-get (car entry) name-alist 0 nil #'equal)
1)
+ (setcar entry
+ (format "%s [%s]"
+ (car entry)
+ (javaimp--concat-scope-parents
+ (nth 3 entry))))))
+ entries)))
+ ((eq javaimp-imenu-group-methods 'qualified)
+ ;; list of qualified methods
+ (mapc (lambda (entry)
+ ;; prepend parents to name
+ (setcar entry (concat (javaimp--concat-scope-parents
+ (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)))))
+
+ (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)))))
+ (lambda (res)
+ (or (functionp (nth 2 res)) ;entry
+ (cdr res))) ;non-empty sub-alist
+ forest)))))
+
+(defsubst javaimp-imenu--make-entry (scope)
+ (list (javaimp-scope-name scope)
+ (javaimp-scope-start scope)
+ #'javaimp-imenu--function
+ scope))
+
+(defun javaimp-imenu--function (index-name index-position scope)
+ (goto-char index-position)
+ (back-to-indentation))
;; Help
@@ -597,35 +706,49 @@ start (`javaimp-scope-start') instead."
(javaimp-scope-start scope)
(javaimp-scope-open-brace scope)))))))
-(defun javaimp-help-scopes-at-point ()
- "Shows enclosing scopes at point in a *javaimp-scopes* buffer,
-which is first cleared."
+
+(defun javaimp-help-reparse-and-show-scopes ()
+ "Reparse scopes and show them in a *javaimp-scopes* buffer."
(interactive)
- (let* ((parse-sexp-ignore-comments t) ; FIXME remove with major mode
- (parse-sexp-lookup-properties nil)
- (scopes (javaimp--parse-scopes nil))
- (file buffer-file-name)
- (pos (point))
- (buf (get-buffer-create "*javaimp-scopes*")))
+ (setq javaimp--parse-dirty-pos (point-min))
+ (let ((scopes (save-excursion
+ (javaimp--parse-get-all-scopes)))
+ (file buffer-file-name)
+ (buf (get-buffer-create "*javaimp-scopes*")))
(with-current-buffer buf
(setq buffer-read-only nil)
(erase-buffer)
- (insert (propertize (format "Scopes at position %d in file:\n %s\n\n"
- pos file)
+ (insert (propertize (format "%s\n\n" file)
'javaimp-help-file file))
(dolist (scope scopes)
- (insert (propertize
- (concat (symbol-name (javaimp-scope-type scope))
- " "
- (javaimp-scope-name scope)
- "\n")
- 'mouse-face 'highlight
- 'help-echo "mouse-2: go to this scope"
- 'javaimp-help-scope scope
- 'keymap javaimp-help-keymap)))
+ (let ((depth 0)
+ (tmp scope))
+ (while (setq tmp (javaimp-scope-parent tmp))
+ (setq depth (1+ depth)))
+ (insert (propertize
+ (format "%d: %010s %s\n"
+ depth
+ (symbol-name (javaimp-scope-type scope))
+ (javaimp-scope-name scope))
+ 'mouse-face 'highlight
+ 'help-echo "mouse-2: go to this scope"
+ 'javaimp-help-scope scope
+ 'keymap javaimp-help-keymap))))
(setq buffer-read-only t))
(display-buffer buf)))
+
+;; Misc
+
+(defun javaimp-reset (arg)
+ "Forget loaded trees state. With prefix arg, also reset jars
+cache."
+ (interactive "P")
+ (setq javaimp-project-forest nil
+ javaimp--jdk-classes 'need-init)
+ (when arg
+ (setq javaimp-cached-jars nil)))
+
(provide 'javaimp)
;;; javaimp.el ends here
diff --git a/testdata/test-get-file-classes-1.java
b/testdata/test-get-file-classes-1.java
deleted file mode 100644
index ba36d31..0000000
--- a/testdata/test-get-file-classes-1.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.foo;
-
-public class Top {
- public static class CInner1<T, S> {
- public void foo_bar() {
- if (true) {
- class CInner1_CLocal1 implements Serializable {
- void foo_bar() {
- System.out.println("");
- if (true) {
- class CInner1_CLocal1_CLocal1 extends Object {
- public void foo_bar() {
- System.out.println("");
- }
- }
- }
- }
- }
- }
-
- class CInner1_CLocal2 extends Object {
- void foo_bar() {
- System.out.println("");
- }
- }
- }
-
- private Object obj = new Object();
- class CInner1_CInner1 {
- }
- }
-
- interface IInner1 {
- interface IInner1_IInner1 extends A<B, C>, Serializable {
- void foo_bar();
- }
-
- static class IInner1_CInner1 {
- public void foo_bar() {
- if (true) {
- System.out.println("");
- }
- }
- }
- }
-
- enum EInner1 {
- A("a"),
- B("b");
- private EInner1() {
- }
- public void foo_bar() {
- System.out.println("");
- }
-
- enum EInner1_EInner1 {
- C, D
- }
- }
-}
diff --git a/testdata/test1-misc-classes.java b/testdata/test1-misc-classes.java
new file mode 100644
index 0000000..c010604
--- /dev/null
+++ b/testdata/test1-misc-classes.java
@@ -0,0 +1,125 @@
+package org.foo;
+
+public class Top {
+ public static class CInner1<T, S> {
+ public void foo() {
+ if (true) {
+ class CInner1_CLocal1 implements Serializable {
+ void foo() {
+ System.out.println("");
+ if (true) {
+ class CInner1_CLocal1_CLocal1 extends Object {
+ public void foo() {
+ System.out.println("");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ class CInner1_CLocal2 extends Object {
+ void foo() {
+ System.out.println("");
+ }
+ }
+ }
+
+ private Object obj = new Object();
+ private Object obj = new Object() {
+ @Override
+ public String toString() {
+ return "asdf";
+ }
+ };
+ abstract class CInner1_CInner1 {
+ public void foo() {
+ }
+
+ abstract void abstract_method();
+
+ public void bar() {
+ System.out.println("");
+ }
+
+ abstract void baz();
+ }
+ }
+
+ // public class LineCommentedClass {
+ // }
+
+ /*
+ public class BlockCommentedClass {
+ }
+ */
+
+ /*
+ * public class BlockCommentedClass2 {
+ * }
+ */
+
+ /**
+ * public class JavadocCommentedClass {
+ * }
+ */
+
+
+ interface IInner1 {
+ static void foo() {
+ if (true) {
+ System.out.println("");
+ }
+ }
+
+ String abstract_method();
+
+ static class IInner1_CInner1 {
+ public void foo() {
+ if (true) {
+ System.out.println("");
+ }
+ }
+ }
+
+ void baz();
+
+ default void defaultMethod(String arg1) {
+ System.out.println("");
+ }
+
+ interface IInner1_IInner1 extends A<B, C>, Serializable {
+ void foo();
+
+ default String defaultMethod(String arg2) {
+ System.out.println("");
+ }
+
+ void baz();
+ }
+ }
+
+ enum EnumInner1 {
+ A("a"),
+ B("b");
+
+ private EnumInner1() {
+ }
+
+ public void foo() {
+ System.out.println("");
+ }
+
+ enum EnumInner1_EInner1 {
+ C, D
+ }
+ }
+}
+
+class ColocatedTop {
+ void foo() {
+ }
+
+ void bar(String arg1, String arg2) {
+ }
+}
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [elpa] externals/javaimp 894f428: Support imenu, first working version,
Filipp Gunbin <=