[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/javaimp 25f5e3f90b: Improve xref support for classes
From: |
Filipp Gunbin |
Subject: |
[elpa] externals/javaimp 25f5e3f90b: Improve xref support for classes |
Date: |
Mon, 9 May 2022 12:28:21 -0400 (EDT) |
branch: externals/javaimp
commit 25f5e3f90b8913819571384b7ff27c41ad92690a
Author: Filipp Gunbin <fgunbin@fastmail.fm>
Commit: Filipp Gunbin <fgunbin@fastmail.fm>
Improve xref support for classes
---
javaimp-gradle.el | 11 +-
javaimp-maven.el | 33 +--
javaimp-util.el | 56 ++---
javaimp.el | 619 +++++++++++++++++++++++++++++-------------------------
4 files changed, 396 insertions(+), 323 deletions(-)
diff --git a/javaimp-gradle.el b/javaimp-gradle.el
index 9ca1b4032f..365506d211 100644
--- a/javaimp-gradle.el
+++ b/javaimp-gradle.el
@@ -83,11 +83,11 @@ descriptor."
(cdr (assq 'parent-id alist)))
:file (cdr (assq 'file alist))
:file-orig file-orig
- ;; jar/war supported
- :final-name (when-let ((final-name (javaimp-cygpath-convert-file-name
- (cdr (assq 'final-name alist)))))
- (and (member (file-name-extension final-name) '("jar" "war"))
- final-name))
+ :artifact (when-let ((final-name (javaimp-cygpath-convert-file-name
+ (cdr (assq 'final-name alist)))))
+ ;; only jar/war supported
+ (and (member (file-name-extension final-name) '("jar" "war"))
+ final-name))
:source-dirs (mapcar #'file-name-as-directory
(javaimp-split-native-path
(cdr (assq 'source-dirs alist))))
@@ -95,6 +95,7 @@ descriptor."
(javaimp-cygpath-convert-file-name
(cdr (assq 'build-dir alist))))
:dep-jars (javaimp-split-native-path (cdr (assq 'dep-jars alist)))
+ :dep-jars-with-source t
:load-ts (current-time)
:dep-jars-fetcher #'javaimp-gradle--fetch-dep-jars
:raw nil))
diff --git a/javaimp-maven.el b/javaimp-maven.el
index 801e988f71..3157b4fb6d 100644
--- a/javaimp-maven.el
+++ b/javaimp-maven.el
@@ -107,7 +107,11 @@ resulting module trees."
(xml-parse-region start end)))
(defun javaimp-maven--module-from-xml (elt file-orig)
- (let ((build-elt (javaimp-xml-child 'build elt)))
+ (let* ((build-elt (javaimp-xml-child 'build elt))
+ (build-dir
+ (file-name-as-directory
+ (javaimp-cygpath-convert-file-name
+ (javaimp-xml-first-child (javaimp-xml-child 'directory
build-elt))))))
(make-javaimp-module
:id (javaimp-maven--id-from-xml elt)
:parent-id (javaimp-maven--id-from-xml (javaimp-xml-child 'parent elt))
@@ -115,14 +119,16 @@ resulting module trees."
;; later, see javaimp-maven--fill-modules-files
:file nil
:file-orig file-orig
- ;; jar/war supported
- :final-name (let ((packaging (or (javaimp-xml-first-child
- (javaimp-xml-child 'packaging elt))
- "jar")))
- (when (member packaging '("jar" "war"))
- (concat (javaimp-xml-first-child
- (javaimp-xml-child 'finalName build-elt))
- "." packaging)))
+ :artifact (let ((packaging (or (javaimp-xml-first-child
+ (javaimp-xml-child 'packaging elt))
+ "jar")))
+ ;; only jar/war supported
+ (when (member packaging '("jar" "war"))
+ (file-name-concat
+ build-dir
+ (concat (javaimp-xml-first-child
+ (javaimp-xml-child 'finalName build-elt))
+ "." packaging))))
:source-dirs (list (file-name-as-directory
(javaimp-cygpath-convert-file-name
(javaimp-xml-first-child
@@ -131,10 +137,11 @@ resulting module trees."
(javaimp-cygpath-convert-file-name
(javaimp-xml-first-child
(javaimp-xml-child 'testSourceDirectory
build-elt)))))
- :build-dir (file-name-as-directory
- (javaimp-cygpath-convert-file-name
- (javaimp-xml-first-child (javaimp-xml-child 'directory
build-elt))))
- :dep-jars nil ; dep-jars is initialized lazily on demand
+ :build-dir build-dir
+ ;; Because dependency list is retrieved by a separate command, we
+ ;; initialize it lazily on demand
+ :dep-jars t
+ :dep-jars-with-source t
:load-ts (current-time)
:dep-jars-fetcher #'javaimp-maven--fetch-dep-jars
:raw elt)))
diff --git a/javaimp-util.el b/javaimp-util.el
index 2d1595670c..935c033f4a 100644
--- a/javaimp-util.el
+++ b/javaimp-util.el
@@ -64,19 +64,30 @@ copying.")
id parent-id
file
file-orig
- ;; Artifact final name, may be relative to build dir
- final-name
- source-dirs build-dir
- dep-jars
+ (artifact
+ nil
+ :documentation "Artifact (usually jar) file name.")
+ source-dirs
+ build-dir
+ (dep-jars
+ nil
+ :documentation "List of dependency jars. t means that the value is not
+initialized - use `dep-jars-fetcher' to initialize.")
+ (dep-jars-with-source
+ nil
+ :documentation "Dependency jars which have source directory available in the
+same project. It is an alist with each element of the
+form (JAR-FILE . MODULE-ID). A value of t means the alist is not
+yet initialized.")
load-ts
- ;; Function to retrieve DEP-JARS for MODULE, called with two
- ;; arguments: MODULE and list of parent IDs. Should return a list
- ;; of strings - jar file names.
- dep-jars-fetcher
- ;; Set later on demand
- ident-comp-table
- ;; Used only during parsing
- raw
+ (dep-jars-fetcher
+ nil
+ :documentation "Function to retrieve DEP-JARS for MODULE,
+called with two arguments: MODULE and list of parent IDs. Should
+return a list of strings - jar file names.")
+ (raw
+ nil
+ :documentation "Internal, used only during parsing")
)
(cl-defstruct javaimp-id
@@ -155,26 +166,23 @@ UNWRAP is non-nil, then node contents is returned."
(defun javaimp-tree-collect-nodes (contents-pred forest)
"Return all nodes' contents for which CONTENTS-PRED returns
non-nil."
- (apply #'seq-concatenate 'list
- (mapcar (lambda (tree)
- (delq nil
- (javaimp-tree--collect-nodes-1 tree contents-pred)))
- forest)))
+ (delq nil
+ (seq-mapcat (lambda (tree)
+ (javaimp-tree--collect-nodes-1 tree contents-pred))
+ forest)))
(defun javaimp-tree--collect-nodes-1 (tree contents-pred)
(when tree
(cons (and (funcall contents-pred (javaimp-node-contents tree))
(javaimp-node-contents tree))
- (apply #'seq-concatenate 'list
- (mapcar (lambda (child)
- (delq nil
- (javaimp-tree--collect-nodes-1 child
contents-pred)))
- (javaimp-node-children tree))))))
+ (seq-mapcat (lambda (child)
+ (javaimp-tree--collect-nodes-1 child contents-pred))
+ (javaimp-node-children tree)))))
(defun javaimp-tree-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
+ "Recursively apply FUNCTION to each node's contents in FOREST and
+return 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
diff --git a/javaimp.el b/javaimp.el
index d89af41e4e..ca762986a6 100644
--- a/javaimp.el
+++ b/javaimp.el
@@ -100,14 +100,12 @@
;;
;; Source parsing
;;
-;; `javaimp-parse-current-module': defcustom which determines whether
-;; we parse the current module for the list of classes. Parsing is
-;; implemented in javaimp-parse.el using `syntax-ppss', generally is
-;; simple (we do not try to parse the source completely - just the
-;; interesting pieces), but can be time-consuming for large projects
-;; (to be improved). Currently, on the author's machine, source for
-;; java.util.Collections from JDK 11 (~ 5600 lines and > 1000
-;; "scopes") parses in ~1.5 seconds, which is not that bad...
+;; Parsing is implemented in javaimp-parse.el using `syntax-ppss',
+;; generally is simple (we do not try to parse the source completely -
+;; just the interesting pieces), but can be time-consuming for large
+;; projects (to be improved). Currently, on the author's machine,
+;; source for java.util.Collections from JDK 11 (~ 5600 lines and >
+;; 1000 "scopes") parses in ~1.5 seconds, which is not that bad...
;;
;; `javaimp-show-scopes': command to list all parsed "scopes" (blocks
;; of code in braces) in the current buffer, with support for
@@ -171,14 +169,6 @@ becomes \"generated-sources/<plugin_name>\" (note the
absence of
the leading slash)."
:type '(repeat (string :tag "Relative directory")))
-(defcustom javaimp-parse-current-module t
- "If non-nil, javaimp will try to parse current module's source
-files to determine completion alternatives, in addition to those
-from module dependencies. This can be time-consuming, that's why
-this defcustom exists, to turn it off if it's annoying (perhaps
-in per-directory locals)."
- :type 'boolean)
-
(defcustom javaimp-imenu-use-sub-alists nil
"If non-nil, make sub-alist for each containing scope (e.g. a
class)."
@@ -212,15 +202,13 @@ A handler function takes one argument, a FILE.")
(defvar javaimp-project-forest nil
"Visited projects")
-(defvar javaimp-jar-file-classes-cache nil
- "Jar file cache, an alist of (FILE . CACHED-FILE), where FILE is
-expanded file name and CACHED-FILE is javaimp-cached-file
-struct. Suitable for use with `javaimp--collect-from-file-cached'.")
-
-(defvar javaimp-source-file-idents-cache nil
- "Source file cache, an alist of (FILE . CACHED-FILE), where FILE
-is expanded file name and CACHED-FILE is javaimp-cached-file
-struct. Suitable for use with `javaimp--collect-from-file-cached'.")
+;; These variables are all alists of (FILE . CACHED-FILE), where FILE
+;; is expanded file name and CACHED-FILE is `javaimp-cached-file'
+;; struct. They're suitable for use with
+;; `javaimp--collect-from-file-cached'.
+(defvar javaimp--jar-idents-cache nil)
+(defvar javaimp--module-idents-cache nil)
+(defvar javaimp--source-idents-cache nil)
(defconst javaimp--jar-error-header
"There were errors when reading some of the dependency files,
@@ -249,221 +237,56 @@
https://docs.gradle.org/current/userguide/java_library_plugin.html\
(enum . "en")))
-;; Dependencies
+;; Subroutines
(defsubst javaimp--get-file-ts (file)
(file-attribute-modification-time (file-attributes file)))
-(defun javaimp--update-module-maybe (node)
- (let ((module (javaimp-node-contents node))
- need-update ids)
- ;; check if deps are initialized
- (unless (javaimp-module-dep-jars module)
- (message "Will load dependencies for %s" (javaimp-module-id module))
- (setq need-update t))
- ;; check if this or any parent build file has changed since we
- ;; loaded the module
- (let ((tmp node))
- (while tmp
- (let ((cur (javaimp-node-contents tmp)))
- (when (and (not need-update)
- (> (max (if (file-exists-p (javaimp-module-file cur))
- (float-time
- (javaimp--get-file-ts (javaimp-module-file
cur)))
- -1)
- (if (file-exists-p (javaimp-module-file-orig cur))
- (float-time
- (javaimp--get-file-ts
(javaimp-module-file-orig cur)))
- -1))
- (float-time (javaimp-module-load-ts module))))
- (message "Will reload dependencies for %s because build file
changed"
- (javaimp-module-id cur))
- (setq need-update t))
- (push (javaimp-module-id cur) ids))
- (setq tmp (javaimp-node-parent tmp))))
- (when need-update
- (setf (javaimp-module-dep-jars module)
- (funcall (javaimp-module-dep-jars-fetcher module) module ids))
- (setf (javaimp-module-load-ts module)
- (current-time)))))
-
-(defun javaimp--read-jar-classes (file)
- "Read FILE which should be a .jar or a .jmod and return classes
-contained in it as a list."
- (let ((ext (downcase (file-name-extension file))))
- (unless (member ext '("jar" "jmod"))
- (error "Unexpected file name: %s" file))
- (let ((javaimp-output-buf-name nil))
- (javaimp-call-java-program
- (symbol-value (intern (format "javaimp-%s-program" ext)))
- #'javaimp--read-jar-classes-handler
- (if (equal ext "jar") "tf" "list")
- ;; On cygwin, "jar/jmod" is a native windows program, so file
- ;; path needs to be converted appropriately.
- (javaimp-cygpath-convert-file-name file 'windows)))))
-
-(defun javaimp--read-jar-classes-handler ()
- "Used by `javaimp--read-jar-classes' to handle jar program
-output."
- (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))
-
-(defun javaimp--collect-from-file-cached (file cache-sym fun)
- "Return what FUN returns when invoked on FILE, with cache. Use
-CACHE-SYM as a cache, it should be an alist with elements of the
-form (FILE . CACHED-FILE). If not found in cache, or the cache
-is outdated, then values are read using FUN, which should be a
-function of one argument, a FILE. If that function throws an
-error, the cache for FILE is cleared. FUN may also be nil, in
-which case the symbol t is returned for a cache miss, and cache
-not updated."
- (condition-case err
- (let ((cached-file
- (alist-get file (symbol-value cache-sym) nil nil #'string=)))
- (when (or (not cached-file)
- ;; If the file doesn't exist this will be current
- ;; time, and thus condition always true
- (> (float-time (javaimp--get-file-ts file))
- (float-time (javaimp-cached-file-read-ts cached-file))))
- (setq cached-file (if fun
- (make-javaimp-cached-file
- :file file
- :read-ts (javaimp--get-file-ts file)
- :value (funcall fun file))
- t)))
- (if (eq cached-file t)
- t
- (setf (alist-get file (symbol-value cache-sym) nil 'remove #'string=)
- cached-file)
- (javaimp-cached-file-value cached-file)))
- (t
- ;; Clear on any error
- (setf (alist-get file (symbol-value cache-sym) nil 'remove #'string=) nil)
- (signal (car err) (cdr err)))))
-
-
-
-;; Some API functions. They do not expose tree structure, return only
-;; modules.
-
-(defun javaimp-find-module (predicate)
- "Returns first module in `javaimp-project-forest' for which
-PREDICATE returns non-nil."
- (javaimp-tree-find-node predicate javaimp-project-forest t))
-
-(defun javaimp-collect-modules (predicate)
- "Returns all modules in `javaimp-project-forest' for which
-PREDICATE returns non-nil."
- (javaimp-tree-collect-nodes predicate javaimp-project-forest))
-
-(defun javaimp-map-modules (function)
- (javaimp-tree-map-nodes function #'always javaimp-project-forest))
-
-
-;;; Adding imports
-
-;;;###autoload
-(defun javaimp-add-import (classname)
- "Import CLASSNAME in the current buffer and call `javaimp-organize-imports'.
-Interactively, provide completion alternatives relevant for this
-file, additionally filtering them by matching simple class
-name (without package) against `symbol-at-point' (with prefix arg
-- don't filter).
-The set of relevant classes is collected from the following:
-
-- If `javaimp-java-home' is set then add JDK classes, see
-`javaimp--get-jdk-classes'.
-
-- If current module can be determined, then add all classes from
-its dependencies.
-
-- If `javaimp-parse-current-module' is non-nil, also add classes in
-current module or source tree, see
-`javaimp--get-current-source-dirs'."
- (interactive
- (let* ((module (javaimp--detect-module))
- (classes
- (nconc
- ;; jdk
- (when javaimp-java-home
- (javaimp--get-jdk-classes javaimp-java-home))
- ;; Module dependencies. We build the list each time
- ;; because jars may change.
- (when module
- (javaimp--collect-from-files
- #'javaimp--read-jar-classes (javaimp-module-dep-jars module)
- 'javaimp-jar-file-classes-cache))
- ;; Current module or source tree
- (when javaimp-parse-current-module
- (mapcar #'javaimp--ident-to-fqcn
- (seq-mapcat
- (lambda (dir)
- (javaimp--collect-from-source-dir
- #'javaimp--collect-identifiers dir
- 'javaimp-source-file-idents-cache))
- (javaimp--get-current-source-dirs module))))))
- (completion-regexp-list
- (and (not current-prefix-arg)
- (symbol-at-point)
- (list (rx (and symbol-start
- (literal (symbol-name (symbol-at-point)))
- eol))))))
- (list (completing-read "Import: " classes nil t nil nil
- (symbol-name (symbol-at-point))))))
- (javaimp-organize-imports (list (cons classname 'normal))))
-
-(defun javaimp--detect-module ()
- (let* ((file (expand-file-name
- (or buffer-file-name
- (error "Buffer is not visiting a file!"))))
- (node (javaimp-tree-find-node
- (lambda (m)
- (seq-some (lambda (dir)
- (string-prefix-p dir file))
- (javaimp-module-source-dirs m)))
- javaimp-project-forest)))
- (when node
- (javaimp--update-module-maybe node)
- (javaimp-node-contents node))))
-
-(defun javaimp--get-jdk-classes (java-home)
- "If 'jmods' subdirectory exists in JAVA-HOME (Java 9+), read all
-.jmod files in it. Else, if 'jre/lib' subdirectory exists in
-JAVA-HOME (earlier Java versions), read all .jar files in it."
- (let ((dir (file-name-concat java-home "jmods")))
- (if (file-directory-p dir)
- (javaimp--collect-from-files
- #'javaimp--read-jar-classes (directory-files dir t "\\.jmod\\'")
- 'javaimp-jar-file-classes-cache)
- (setq dir (file-name-concat java-home "jre" "lib"))
- (if (file-directory-p dir)
- (javaimp--collect-from-files
- #'javaimp--read-jar-classes (directory-files dir t "\\.jar\\'")
- 'javaimp-jar-file-classes-cache)
- (user-error "Could not load JDK classes")))))
+(defun javaimp--collect-from-file (file cache-sym fun)
+ "Return what FUN returns when invoked on FILE, with cache. FILE
+may be just a filename, or a cons cell where car is filename.
+Use CACHE-SYM as a cache, it should be an alist with elements of
+the form (FILENAME . CACHED-FILE). If not found in cache, or the
+cache is outdated, then values are read using FUN, which should
+be a function of one argument, a FILE. If that function throws
+an error, the cache for FILENAME is cleared. FUN may also be
+nil, in which case the symbol t is returned for a cache miss, and
+cache not updated."
+ (let ((filename (if (consp file) (car file) file)))
+ (condition-case err
+ (let ((cached-file
+ (alist-get filename (symbol-value cache-sym) nil nil
#'string=)))
+ (when (or (not cached-file)
+ ;; If the file doesn't exist this will be current
+ ;; time, and thus condition always true
+ (> (float-time (javaimp--get-file-ts filename))
+ (float-time (javaimp-cached-file-read-ts cached-file))))
+ (setq cached-file (if fun
+ (make-javaimp-cached-file
+ :file filename
+ :read-ts (javaimp--get-file-ts filename)
+ :value (funcall fun file))
+ t)))
+ (if (eq cached-file t)
+ t
+ (setf (alist-get filename (symbol-value cache-sym) nil 'remove
#'string=)
+ cached-file)
+ (javaimp-cached-file-value cached-file)))
+ (t
+ ;; Clear on any error
+ (setf (alist-get filename (symbol-value cache-sym) nil 'remove
#'string=) nil)
+ (signal (car err) (cdr err))))))
(defun javaimp--collect-from-files (fun files cache-sym)
+ "Collect values for FILES in a flat list. Each element in FILES
+should be a file name, or a cons where car is a file name. FUN
+and CACHE-SYM are passed to `javaimp--collect-from-file', which
+see."
(let (tmp unread res errors)
;; Collect from cache hits
(dolist (file files)
- (setq tmp (javaimp--collect-from-file-cached file cache-sym nil))
+ (setq tmp (javaimp--collect-from-file file cache-sym nil))
(if (eq tmp t)
(push file unread)
(setq res (nconc res (copy-sequence tmp)))))
@@ -473,17 +296,19 @@ JAVA-HOME (earlier Java versions), read all .jar files in
it."
(format "Reading %d files (%d taken from cache) ..."
(length unread) (- (length files) (length
unread)))
0 (length unread)))
- (i 0))
+ (i 0)
+ filename)
(dolist (file unread)
- (setq tmp (condition-case err
- (javaimp--collect-from-file-cached file cache-sym fun)
+ (setq filename (if (consp file) (car file) file)
+ tmp (condition-case err
+ (javaimp--collect-from-file file cache-sym fun)
(t
- (push (concat file ": " (error-message-string err))
+ (push (concat filename ": " (error-message-string err))
errors)
nil)))
(setq res (nconc res (copy-sequence tmp)))
(setq i (1+ i))
- (progress-reporter-update reporter i file))
+ (progress-reporter-update reporter i filename))
(progress-reporter-done reporter)))
(when errors
(with-output-to-temp-buffer "*Javaimp errors*"
@@ -494,32 +319,6 @@ JAVA-HOME (earlier Java versions), read all .jar files in
it."
(terpri))))
res))
-(defun javaimp--get-current-source-dirs (module)
- "Return list of directories where Java sources reside.
-If MODULE is non-nil then result is module source dirs and
-additional source dirs. Otherwise, try to determine the root of
-source tree from 'package' directive in the current buffer. If
-there's no such directive, then the last resort is just
-`default-directory'."
- (if module
- (append
- (javaimp-module-source-dirs module)
- ;; additional source dirs
- (mapcar (lambda (dir)
- (file-name-as-directory
- (file-name-concat (javaimp-module-build-dir module) dir)))
- javaimp-additional-source-dirs))
- (list
- (if-let ((package (save-excursion
- (save-restriction
- (widen)
- (javaimp-parse-get-package)))))
- (string-remove-suffix
- (file-name-as-directory
- (apply #'file-name-concat (split-string package "\\." t)))
- default-directory)
- default-directory))))
-
(defun javaimp--collect-from-source-dir (fun dir cache-sym)
"For each Java source file in DIR, invoke FUN and collect results
in a flat list. FUN is given two arguments: a buffer BUF, and
@@ -558,16 +357,41 @@ Finally, already parsed buffers are processed in
(funcall fun (current-buffer) file)))
files cache-sym)
;; Parse unparsed buffers
- (let (tmp)
- (dolist-with-progress-reporter (buf unparsed-bufs tmp)
- (format "Parsing %d buffers..." (length unparsed-bufs))
- (setq tmp (nconc tmp (funcall fun buf nil)))))
+ (when unparsed-bufs
+ (let (tmp)
+ (dolist-with-progress-reporter (buf unparsed-bufs tmp)
+ (format "Parsing %d buffers..." (length unparsed-bufs))
+ (setq tmp (nconc tmp (funcall fun buf nil))))))
;; Read parsed buffers - usually will be quick
- (with-delayed-message
- (1 (format "Reading %d buffers..." (length parsed-bufs)))
- (seq-mapcat (lambda (buf)
- (funcall fun buf nil))
- parsed-bufs))))))
+ (when parsed-bufs
+ (with-delayed-message
+ (1 (format "Reading %d buffers..." (length parsed-bufs)))
+ (seq-mapcat (lambda (buf)
+ (funcall fun buf nil))
+ parsed-bufs)))))))
+
+
+(defun javaimp--get-current-source-dir ()
+ "Try to determine current root source directory from 'package'
+directive in the current buffer. If there's no such directive,
+then just return `default-directory'."
+ (if-let ((package (save-excursion
+ (save-restriction
+ (widen)
+ (javaimp-parse-get-package)))))
+ (string-remove-suffix
+ (file-name-as-directory
+ (apply #'file-name-concat (split-string package "\\." t)))
+ default-directory)
+ default-directory))
+
+
+
+;; Subroutines for identifiers
+
+(defun javaimp--read-dir-source-idents (dir)
+ (javaimp--collect-from-source-dir
+ #'javaimp--collect-identifiers dir 'javaimp--source-idents-cache))
(defun javaimp--collect-identifiers (buf file)
"Return all identifiers in buffer BUF, which is temporary if FILE
@@ -605,6 +429,241 @@ is non-nil. Suitable for use with
ident))
"."))
+
+(defun javaimp--read-jar-classes (file)
+ "Read FILE which should be a .jar or a .jmod and return classes
+contained in it as a list."
+ (let ((ext (downcase (file-name-extension file))))
+ (unless (member ext '("jar" "jmod"))
+ (error "Unexpected file name: %s" file))
+ (let ((javaimp-output-buf-name nil))
+ (javaimp-call-java-program
+ (symbol-value (intern (format "javaimp-%s-program" ext)))
+ #'javaimp--read-jar-classes-handler
+ (if (equal ext "jar") "tf" "list")
+ ;; On cygwin, "jar/jmod" is a native windows program, so file
+ ;; path needs to be converted appropriately.
+ (javaimp-cygpath-convert-file-name file 'windows)))))
+
+(defun javaimp--read-jar-classes-handler ()
+ "Used by `javaimp--read-jar-classes' to handle jar program
+output."
+ (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))
+
+
+
+;; Subroutines for working with modules
+
+(defun javaimp--detect-module ()
+ (let* ((file (expand-file-name
+ (or buffer-file-name
+ (error "Buffer is not visiting a file!"))))
+ (node (javaimp-tree-find-node
+ (lambda (m)
+ (seq-some (lambda (dir)
+ (string-prefix-p dir file))
+ (javaimp-module-source-dirs m)))
+ javaimp-project-forest)))
+ (when node
+ (javaimp--update-module-maybe node)
+ (javaimp-node-contents node))))
+
+(defun javaimp--update-module-maybe (node)
+ (let ((module (javaimp-node-contents node))
+ need-update ids)
+ ;; Check if deps are initialized
+ (when (eq (javaimp-module-dep-jars module) t)
+ (message "Will load dependencies for %s" (javaimp-module-id module))
+ (setq need-update t))
+ ;; Check if this or any parent build file has changed since we
+ ;; loaded the module
+ (let ((tmp node))
+ (while tmp
+ (let ((cur (javaimp-node-contents tmp)))
+ (when (and (not need-update)
+ (> (max (if (file-exists-p (javaimp-module-file cur))
+ (float-time
+ (javaimp--get-file-ts (javaimp-module-file
cur)))
+ -1)
+ (if (file-exists-p (javaimp-module-file-orig cur))
+ (float-time
+ (javaimp--get-file-ts
(javaimp-module-file-orig cur)))
+ -1))
+ (float-time (javaimp-module-load-ts module))))
+ (message "Will reload dependencies for %s because build file
changed"
+ (javaimp-module-id cur))
+ (setq need-update t))
+ (push (javaimp-module-id cur) ids))
+ (setq tmp (javaimp-node-parent tmp))))
+ (when need-update
+ (setf (javaimp-module-dep-jars module)
+ (funcall (javaimp-module-dep-jars-fetcher module) module ids))
+ (setf (javaimp-module-load-ts module)
+ (current-time)))
+ (when (or need-update
+ (eq (javaimp-module-dep-jars-with-source module) t))
+ ;; Find out which of dep jars are also available as sources in
+ ;; the current project
+ (let* ((all (mapcar (lambda (m)
+ (cons (javaimp-module-artifact m)
(javaimp-module-id m)))
+ (javaimp-collect-modules
+ (lambda (m)
+ (not (string-empty-p (javaimp-module-artifact
m)))))))
+ ;; FIXME we need elements from all as the result, is it
+ ;; reliable to just put all first?
+ (matches (seq-intersection
+ all
+ (javaimp-module-dep-jars module)
+ (lambda (el1 el2)
+ (string= (if (consp el1) (car el1) el1)
+ (if (consp el2) (car el2) el2))))))
+ (setf (javaimp-module-dep-jars-with-source module) matches)))))
+
+(defun javaimp--collect-module-dep-jars-classes (module)
+ "Return list of classes from MODULE's jar dependencies. We
+build the list each time because jars may change."
+ (let ((dep-jars-no-source
+ (seq-difference
+ (javaimp-module-dep-jars module)
+ (javaimp-module-dep-jars-with-source module)
+ (lambda (el1 el2)
+ (string= (if (consp el1) (car el1) el1)
+ (if (consp el2) (car el2) el2))))))
+ (javaimp--collect-from-files
+ #'javaimp--read-jar-classes
+ dep-jars-no-source
+ 'javaimp--jar-idents-cache)))
+
+(defun javaimp--collect-module-dep-jars-with-source-idents (module)
+ "Return list of identifiers from MODULE's dependencies for which
+we know where the source is. The list is cached by _artifact
+file_, so cache is refreshed only when artifact is rebuilt."
+ (javaimp--collect-from-files
+ (lambda (artifact-and-id)
+ (let* ((mod-id (cdr artifact-and-id))
+ (mod (javaimp-find-module
+ (lambda (m)
+ (equal (javaimp-module-id m) mod-id)))))
+ (if mod
+ (javaimp--read-module-source-idents mod)
+ (error "Could not find module %s! Please re-visit its \
+top-level project." (javaimp-print-id mod-id)))))
+ (javaimp-module-dep-jars-with-source module)
+ 'javaimp--module-idents-cache))
+
+(defun javaimp--read-module-source-idents (module)
+ (let ((source-dirs
+ (append
+ (javaimp-module-source-dirs module)
+ (mapcar (lambda (dir)
+ (file-name-as-directory
+ (file-name-concat (javaimp-module-build-dir module) dir)))
+ javaimp-additional-source-dirs))))
+ (seq-mapcat #'javaimp--read-dir-source-idents
+ source-dirs)))
+
+
+
+;; Some API functions. They do not expose tree structure, return only
+;; modules.
+
+(defun javaimp-find-module (predicate)
+ "Return first module in `javaimp-project-forest' for which
+PREDICATE returns non-nil."
+ (javaimp-tree-find-node predicate javaimp-project-forest t))
+
+(defun javaimp-collect-modules (predicate)
+ "Return all modules in `javaimp-project-forest' for which
+PREDICATE returns non-nil."
+ (javaimp-tree-collect-nodes predicate javaimp-project-forest))
+
+(defun javaimp-map-modules (function)
+ (javaimp-tree-map-nodes function #'always javaimp-project-forest))
+
+
+;;; Adding imports
+
+;;;###autoload
+(defun javaimp-add-import (classname)
+ "Import CLASSNAME in the current buffer and call `javaimp-organize-imports'.
+Interactively, provide completion alternatives relevant for this
+file, additionally filtering them by matching simple class
+name (without package) against `symbol-at-point' (with prefix arg
+- don't filter).
+
+The set of relevant classes is collected from the following:
+
+- If `javaimp-java-home' is set then add JDK classes, see
+`javaimp--get-jdk-classes'.
+
+- If current module can be determined, then add all classes from
+its jar dependencies, as well as its source dependencies.
+
+- Add classes in current module (if any) or source tree (see
+`javaimp--get-current-source-dir')."
+ (interactive
+ (let* ((module (javaimp--detect-module))
+ (classes
+ (nconc
+ ;; jdk
+ (when javaimp-java-home
+ (javaimp--get-jdk-classes javaimp-java-home))
+ (when module
+ (nconc
+ (javaimp--collect-module-dep-jars-classes module)
+ (mapcar #'javaimp--ident-to-fqcn
+ (javaimp--collect-module-dep-jars-with-source-idents
+ module))))
+ ;; Current module or source tree
+ (mapcar #'javaimp--ident-to-fqcn
+ (if module
+ (javaimp--read-module-source-idents module)
+ (javaimp--read-dir-source-idents
+ (javaimp--get-current-source-dir))))))
+ (completion-regexp-list
+ (and (not current-prefix-arg)
+ (symbol-at-point)
+ (list (rx (and symbol-start
+ (literal (symbol-name (symbol-at-point)))
+ eol))))))
+ (list (completing-read "Import: " classes nil t nil nil
+ (symbol-name (symbol-at-point))))))
+ (javaimp-organize-imports (list (cons classname 'normal))))
+
+(defun javaimp--get-jdk-classes (java-home)
+ "If 'jmods' subdirectory exists in JAVA-HOME (Java 9+), read all
+.jmod files in it. Else, if 'jre/lib' subdirectory exists in
+JAVA-HOME (earlier Java versions), read all .jar files in it."
+ (let ((dir (file-name-concat java-home "jmods")))
+ (if (file-directory-p dir)
+ (javaimp--collect-from-files
+ #'javaimp--read-jar-classes (directory-files dir t "\\.jmod\\'")
+ 'javaimp--jar-idents-cache)
+ (setq dir (file-name-concat java-home "jre" "lib"))
+ (if (file-directory-p dir)
+ (javaimp--collect-from-files
+ #'javaimp--read-jar-classes (directory-files dir t "\\.jar\\'")
+ 'javaimp--jar-idents-cache)
+ (user-error "Could not load JDK classes")))))
+
+
;; Organizing imports
@@ -789,15 +848,12 @@ in a major mode hook."
(defun javaimp-xref--backend () 'javaimp)
(defun javaimp-xref--module-completion-table ()
- (when-let ((mod (javaimp--detect-module)))
- (setf (javaimp-module-ident-comp-table mod)
- (or (javaimp-module-ident-comp-table mod)
- (seq-mapcat
- (lambda (dir)
- (javaimp--collect-from-source-dir
- #'javaimp--collect-identifiers dir
- 'javaimp-source-file-idents-cache))
- (javaimp--get-current-source-dirs mod))))))
+ (if-let ((module (javaimp--detect-module)))
+ (nconc
+ (javaimp--collect-module-dep-jars-with-source-idents module)
+ (javaimp--read-module-source-idents module))
+ (javaimp--read-dir-source-idents
+ (javaimp--get-current-source-dir))))
(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql
'javaimp)))
(javaimp-xref--module-completion-table))
@@ -1165,8 +1221,9 @@ any module's source file."
(defun javaimp-flush-cache ()
"Flush all caches."
(interactive)
- (setq javaimp-jar-file-classes-cache nil
- javaimp-source-file-idents-cache nil))
+ (setq javaimp--jar-idents-cache nil
+ javaimp--module-idents-cache nil
+ javaimp--source-idents-cache nil))
(provide 'javaimp)
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [elpa] externals/javaimp 25f5e3f90b: Improve xref support for classes,
Filipp Gunbin <=