branch: externals/javaimp commit 894f4284b6d9fbf728fd7e9767925b757a56d94f Author: Filipp Gunbin <fgun...@fastmail.fm> Commit: Filipp Gunbin <fgun...@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) { + } +}