branch: externals/javaimp commit 9b49ee123c652a5ef5630badbcc8ae91b240c457 Author: Filipp Gunbin <fgun...@fastmail.fm> Commit: Filipp Gunbin <fgun...@fastmail.fm>
Include inner classes in completion candidates --- javaimp-tests.el | 22 +++++++++- javaimp-util.el | 52 +++++++++++++++++++++++ javaimp.el | 78 +++++++++++++++++------------------ testdata/test-get-file-classes-1.java | 60 +++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 42 deletions(-) diff --git a/javaimp-tests.el b/javaimp-tests.el index f382302..36b4686 100644 --- a/javaimp-tests.el +++ b/javaimp-tests.el @@ -6,7 +6,7 @@ ;; Maintainer: Filipp Gunbin <fgun...@fastmail.fm> (require 'ert) -(require 'javaimp-maven) +(require 'javaimp) (ert-deftest javaimp-test--maven-projects-from-xml--project () (with-temp-buffer @@ -21,3 +21,23 @@ (let ((projects (javaimp--maven-projects-from-xml (xml-parse-region (point-min) (point-max))))) (should (eql (length projects) 2))))) + + +(ert-deftest javaimp-test--get-package () + (with-temp-buffer + (insert "//package org.commented1; +/*package org.commented2;*/ + package org.foo;") + (should (equal (javaimp--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")))) diff --git a/javaimp-util.el b/javaimp-util.el index 9304fce..f2f4c00 100644 --- a/javaimp-util.el +++ b/javaimp-util.el @@ -157,4 +157,56 @@ buffer and returns its result" (setf (javaimp-node-children this-node) child-nodes) this-node))) + +;; Java source parsing + +(defun javaimp--get-package () + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (catch 'found + (while (re-search-forward "^\\s *package\\s +\\([^;]+\\)\\s *;" nil t) + (let ((state (syntax-ppss))) + (unless (syntax-ppss-context state) + (throw 'found (match-string 1))))))))) + +(defun javaimp--get-file-classes (file) + (with-temp-buffer + (insert-file-contents file) + (let ((parse-sexp-ignore-comments t) + (class-re (concat + (regexp-opt '("class" "interface" "enum") 'words) + (rx (and (+ (syntax whitespace)) + (group (+ (any alnum ?_))))))) + res) + (while (re-search-forward class-re nil t) + (let ((state (syntax-ppss)) + curr) + (unless (syntax-ppss-context state) + (setq curr (list (match-string 2))) + ;; collect enclosing classes, if any + (save-excursion + (catch 'stop + (while (nth 1 state) + ;; find innermost enclosing open-bracket + (goto-char (nth 1 state)) + (if (and (= (char-after) ?{) + (re-search-backward class-re nil t) + ;; if there's no paren in between - assume + ;; it's a valid class (not a method - this + ;; way we exclude local classes) + (not (save-match-data + (search-forward "(" (nth 1 state) t)))) + (progn + (push (match-string 2) curr) + (setq state (syntax-ppss))) + (setq curr nil) + (throw 'stop nil))))) + (when curr + (let ((package (javaimp--get-package))) + (if package (push package curr))) + (push (mapconcat #'identity curr ".") res))))) + (nreverse res)))) + (provide 'javaimp-util) diff --git a/javaimp.el b/javaimp.el index b3338c5..1ea804d 100644 --- a/javaimp.el +++ b/javaimp.el @@ -49,9 +49,6 @@ ;; from the build tool again. If a jar file was changed, its contents ;; are re-read. ;; -;; Currently inner classes are filtered out from completion alternatives. -;; You can always import top-level class and use qualified name. -;; ;; ;; Example: ;; @@ -275,16 +272,24 @@ any module file." ;; needs to be converted appropriately. (javaimp-cygpath-convert-maybe file 'windows))) (goto-char (point-min)) - (save-excursion - (while (re-search-forward "^classes/" nil t) - (replace-match ""))) - (save-excursion - (while (search-forward "/" nil t) - (replace-match "."))) - (let (result) - (while (re-search-forward "^\\([[:alnum:]._]+\\)\\.class$" nil t) - (push (match-string 1) result)) - result)))) + (let (result curr) + (while (re-search-forward + (rx (and bol + (? "classes/") ; prefix output by jmod + (group (+ (any alnum "_/$"))) + ".class" + eol)) + nil t) + (setq curr (match-string 1)) + (unless (or (string-suffix-p "module-info" curr) + (string-suffix-p "package-info" curr) + ;; like Provider$1.class + (string-match-p "\\$[[:digit:]]" curr)) + (push + (string-replace "/" "." + (string-replace "$" "." curr)) + result))) + result)))) ;; Tree search routines @@ -353,12 +358,11 @@ subdirectory - fallback to reading all jar files in lib-dir. sits dependencies. - If `javaimp-include-current-module-classes' is set, then add -current module's top-level classes. If there's no current -module, then add all top-level classes from the current file -tree: if there's a \"package\" directive in the current file and -it matches last components of the file name, then file tree -starts in the parent directory of the package, otherwise just use -current directory. +current module's classes. If there's no current module, then add +all classes from the current file tree: if there's a \"package\" +directive in the current file and it matches last components of +the file name, then file tree starts in the parent directory of +the package, otherwise just use current directory. - Keep only candidates whose class simple name (last component of a fully-qualified name) matches current `symbol-at-point'. If a @@ -426,38 +430,30 @@ prefix arg is given, don't do this filtering." (user-error "JRE lib dir \"%s\" doesn't exist" dir)))))) (defun javaimp--get-module-classes (module) - "Returns list of top-level classes in current module" + "Returns list of classes in current module" (append ;; source dirs - (seq-mapcat (lambda (dir) - (and (file-accessible-directory-p dir) - (javaimp--get-directory-classes dir))) + (seq-mapcat #'javaimp--get-directory-classes (javaimp-module-source-dirs module)) ;; additional source dirs (seq-mapcat (lambda (rel-dir) - (let ((dir (concat (javaimp-module-build-dir module) - (file-name-as-directory rel-dir)))) - (and (file-accessible-directory-p dir) - (javaimp--get-directory-classes dir)))) + (javaimp--get-directory-classes + (concat (javaimp-module-build-dir module) + (file-name-as-directory rel-dir)))) javaimp-additional-source-dirs))) (defun javaimp--dir-above-current-package () - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (when (re-search-forward "^\\s *package\\s +\\([^;]+\\)\\s *;" nil t) - (string-remove-suffix - (mapconcat #'file-name-as-directory - (split-string (match-string 1) "\\." t) nil) - default-directory))))) + (let ((package (javaimp--get-package))) + (when package + (string-remove-suffix + (mapconcat #'file-name-as-directory + (split-string package "\\." t) nil) + default-directory)))) (defun javaimp--get-directory-classes (dir) - (mapcar (lambda (filename) - (replace-regexp-in-string - "[/\\]+" "." - (string-remove-prefix dir (file-name-sans-extension filename)))) - (directory-files-recursively dir "\\.java\\'"))) + (if (file-accessible-directory-p dir) + (seq-mapcat #'javaimp--get-file-classes + (directory-files-recursively dir "\\.java\\'")))) ;; Organizing imports diff --git a/testdata/test-get-file-classes-1.java b/testdata/test-get-file-classes-1.java new file mode 100644 index 0000000..ba36d31 --- /dev/null +++ b/testdata/test-get-file-classes-1.java @@ -0,0 +1,60 @@ +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 + } + } +}