[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/javaimp c06a1e57d7: First take at xref support
From: |
Filipp Gunbin |
Subject: |
[elpa] externals/javaimp c06a1e57d7: First take at xref support |
Date: |
Fri, 6 May 2022 19:03:08 -0400 (EDT) |
branch: externals/javaimp
commit c06a1e57d77304582b3fdb5b6d35a539f80e3511
Author: Filipp Gunbin <fgunbin@fastmail.fm>
Commit: Filipp Gunbin <fgunbin@fastmail.fm>
First take at xref support
---
javaimp-parse.el | 7 ++
javaimp-util.el | 8 +-
javaimp.el | 305 +++++++++++++++++++++++++++++++++++--------------------
tests/tests.el | 26 ++---
4 files changed, 220 insertions(+), 126 deletions(-)
diff --git a/javaimp-parse.el b/javaimp-parse.el
index 516c9273f7..fc221fb379 100644
--- a/javaimp-parse.el
+++ b/javaimp-parse.el
@@ -668,10 +668,12 @@ it with PRED, and its parents with PARENT-PRED."
scope))
(defun javaimp-parse-get-class-abstract-methods ()
+ "Return all scopes which are abstract methods in classes."
(javaimp-parse--all-scopes)
(javaimp-parse--class-abstract-methods))
(defun javaimp-parse-get-interface-abstract-methods ()
+ "Return all scopes which are abstract methods in interfaces."
(let ((interfaces (javaimp-parse-get-all-scopes
nil nil
(lambda (s)
@@ -680,6 +682,11 @@ it with PRED, and its parents with PARENT-PRED."
(seq-mapcat #'javaimp-parse--interface-abstract-methods
interfaces)))
+(defun javaimp-parse-fully-parsed-p ()
+ "Return non-nil if current buffer is fully parsed."
+ (and javaimp-parse--dirty-pos
+ (not (marker-position javaimp-parse--dirty-pos))))
+
(defmacro javaimp-parse-without-hook (&rest body)
"Execute BODY, temporarily removing
diff --git a/javaimp-util.el b/javaimp-util.el
index 30333137c2..2d1595670c 100644
--- a/javaimp-util.el
+++ b/javaimp-util.el
@@ -73,15 +73,15 @@ copying.")
;; arguments: MODULE and list of parent IDs. Should return a list
;; of strings - jar file names.
dep-jars-fetcher
- raw ;used only during parsing
+ ;; Set later on demand
+ ident-comp-table
+ ;; Used only during parsing
+ raw
)
(cl-defstruct javaimp-id
group artifact version)
-(cl-defstruct javaimp-cached-file
- file read-ts classes)
-
(defsubst javaimp-print-id (id)
(format "%s:%s:%s"
diff --git a/javaimp.el b/javaimp.el
index 97455f5cf5..d89af41e4e 100644
--- a/javaimp.el
+++ b/javaimp.el
@@ -128,6 +128,7 @@
(require 'javaimp-parse)
(require 'imenu)
+(require 'xref)
;; User options
@@ -193,6 +194,13 @@ class)."
+;; Structs
+
+(cl-defstruct javaimp-cached-file
+ file read-ts value)
+
+
+
;; Variables
(defvar javaimp-handler-regexp-alist
@@ -204,15 +212,15 @@ A handler function takes one argument, a FILE.")
(defvar javaimp-project-forest nil
"Visited projects")
-(defvar javaimp-jar-file-cache nil
+(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.")
+struct. Suitable for use with `javaimp--collect-from-file-cached'.")
-(defvar javaimp-source-file-cache nil
+(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.")
+struct. Suitable for use with `javaimp--collect-from-file-cached'.")
(defconst javaimp--jar-error-header
"There were errors when reading some of the dependency files,
@@ -316,15 +324,15 @@ output."
result)))
result))
-(defun javaimp--get-file-classes-cached (file cache-sym class-reader)
- "Return list of classes for FILE. 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 classes are read using CLASS-READER, which should be a
+(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. CLASS-READER may also be
-nil, in which case the symbol t is returned for a cache miss, and
-cache not updated."
+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=)))
@@ -333,17 +341,17 @@ cache not updated."
;; 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 class-reader
+ (setq cached-file (if fun
(make-javaimp-cached-file
:file file
:read-ts (javaimp--get-file-ts file)
- :classes (funcall class-reader 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-classes 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)
@@ -351,9 +359,8 @@ cache not updated."
-;; Some API functions
-;;
-;; do not expose tree structure, return only modules
+;; 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
@@ -391,31 +398,27 @@ its dependencies.
current module or source tree, see
`javaimp--get-current-source-dirs'."
(interactive
- (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))
- (module (when node
- (javaimp--update-module-maybe node)
- (javaimp-node-contents node)))
- (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-classes 'javaimp-jar-file-cache
- #'javaimp--read-jar-classes
- (javaimp-module-dep-jars
module)))
- ;; Current module or source tree
- (when javaimp-parse-current-module
- (seq-mapcat #'javaimp--get-directory-classes
- (javaimp--get-current-source-dirs module)))))
+ (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)
@@ -426,28 +429,41 @@ current module or source tree, see
(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 (concat (file-name-as-directory java-home) "jmods")))
+ (let ((dir (file-name-concat java-home "jmods")))
(if (file-directory-p dir)
- (javaimp--collect-classes
- 'javaimp-jar-file-cache #'javaimp--read-jar-classes
- (directory-files dir t "\\.jmod\\'"))
- (setq dir (mapconcat #'file-name-as-directory
- `(,java-home "jre" "lib") nil))
+ (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-classes
- 'javaimp-jar-file-cache #'javaimp--read-jar-classes
- (directory-files dir t "\\.jar\\'"))
+ (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-classes (cache-sym fun files)
+(defun javaimp--collect-from-files (fun files cache-sym)
(let (tmp unread res errors)
;; Collect from cache hits
(dolist (file files)
- (setq tmp (javaimp--get-file-classes-cached file cache-sym nil))
+ (setq tmp (javaimp--collect-from-file-cached file cache-sym nil))
(if (eq tmp t)
(push file unread)
(setq res (nconc res (copy-sequence tmp)))))
@@ -460,7 +476,7 @@ JAVA-HOME (earlier Java versions), read all .jar files in
it."
(i 0))
(dolist (file unread)
(setq tmp (condition-case err
- (javaimp--get-file-classes-cached file cache-sym fun)
+ (javaimp--collect-from-file-cached file cache-sym fun)
(t
(push (concat file ": " (error-message-string err))
errors)
@@ -470,7 +486,7 @@ JAVA-HOME (earlier Java versions), read all .jar files in
it."
(progress-reporter-update reporter i file))
(progress-reporter-done reporter)))
(when errors
- (with-output-to-temp-buffer "*Javaimp collect classes errors*"
+ (with-output-to-temp-buffer "*Javaimp errors*"
(princ javaimp--jar-error-header)
(terpri)
(dolist (err (nreverse errors))
@@ -479,19 +495,19 @@ JAVA-HOME (earlier Java versions), read all .jar files in
it."
res))
(defun javaimp--get-current-source-dirs (module)
- "Return list of source directories for inspection for Java
-sources. 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
+ "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)
- (concat (javaimp-module-build-dir module)
- (file-name-as-directory dir)))
+ (file-name-as-directory
+ (file-name-concat (javaimp-module-build-dir module) dir)))
javaimp-additional-source-dirs))
(list
(if-let ((package (save-excursion
@@ -499,62 +515,95 @@ If there's no such directive, then the last resort is just
(widen)
(javaimp-parse-get-package)))))
(string-remove-suffix
- (mapconcat #'file-name-as-directory (split-string package "\\." t)
nil)
+ (file-name-as-directory
+ (apply #'file-name-concat (split-string package "\\." t)))
default-directory)
default-directory))))
-(defun javaimp--get-directory-classes (dir)
+(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
+file name FILE, which is non-nil only if BUF is a temporary
+buffer. It should return a list of some values.
+
+Files which are not visited in some buffer in the current session
+are processed with `javaimp--collect-from-files', with CACHE-SYM
+given as corresponding argument. In this case we visit the file
+in a temp buffer, and so FILE given to FUN will be non-nil.
+
+Unparsed or partially parsed buffers (as determined by
+`javaimp-parse-fully-parsed-p') are processed in
+`dolist-with-progress-reporter', without cache.
+
+Finally, already parsed buffers are processed in
+`with-delayed-message', without cache."
(when (file-accessible-directory-p dir)
(let ((sources (seq-filter (lambda (f)
(not (file-symlink-p f)))
(directory-files-recursively dir "\\.java\\'")))
- files buffers)
+ files parsed-bufs unparsed-bufs)
(dolist (s sources)
(if-let ((buf (get-file-buffer s)))
- (push buf buffers)
+ (if (with-current-buffer buf
+ (javaimp-parse-fully-parsed-p))
+ (push buf parsed-bufs)
+ (push buf unparsed-bufs))
(push s files)))
(nconc
;; Read files
- (javaimp--collect-classes
- 'javaimp-source-file-cache #'javaimp--read-source-classes files)
- ;; Read buffers. Don't use cache, just collect what we have in
- ;; buffer.
+ (javaimp--collect-from-files
+ (lambda (file)
+ (with-temp-buffer
+ (insert-file-contents file)
+ (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)))))
+ ;; Read parsed buffers - usually will be quick
(with-delayed-message
- (1 (format "Reading %d buffers..." (length buffers)))
+ (1 (format "Reading %d buffers..." (length parsed-bufs)))
(seq-mapcat (lambda (buf)
- (with-current-buffer buf
- (save-excursion
- (save-restriction
- (widen)
- (javaimp--get-buffer-classes)))))
- buffers))))))
-
-(defun javaimp--read-source-classes (file)
- (with-temp-buffer
- (insert-file-contents file)
- ;; We need only class-likes, and this is temp buffer, so for
- ;; efficiency avoid parsing anything else
- (let ((javaimp-parse--scope-hook #'javaimp-parse--scope-class))
- (javaimp--get-buffer-classes))))
-
-(defun javaimp--get-buffer-classes ()
- "Return fully-qualified names of all class-like scopes in the
-current buffer. Anonymous classes are not included."
- (let ((package (javaimp-parse-get-package))
- (scopes (javaimp-parse-get-all-scopes
- nil nil (javaimp-scope-defun-p))))
- (mapcar (lambda (class)
- (if package
- (concat package "." class)
- class))
- (mapcar (lambda (scope)
- (let ((name (javaimp-scope-name scope))
- (parent-names (javaimp-scope-concat-parents
scope)))
- (if (string-empty-p parent-names)
- name
- (concat parent-names "." name))))
- scopes))))
-
+ (funcall fun buf nil))
+ parsed-bufs))))))
+
+(defun javaimp--collect-identifiers (buf file)
+ "Return all identifiers in buffer BUF, which is temporary if FILE
+is non-nil. Suitable for use with
+`javaimp--collect-from-source-dir', which see."
+ (with-current-buffer buf
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let* ((javaimp-parse--scope-hook ;optimization
+ (if file
+ #'javaimp-parse--scope-class
+ javaimp-parse--scope-hook))
+ (package (javaimp-parse-get-package))
+ (scopes (javaimp-parse-get-all-scopes
+ nil nil (javaimp-scope-defun-p))))
+ (mapcar (lambda (s)
+ (goto-char (javaimp-scope-open-brace s))
+ (propertize (javaimp-scope-name s)
+ 'file (or file buffer-file-name)
+ 'pos (point)
+ 'line (line-number-at-pos)
+ 'column (current-column)
+ 'package package
+ 'parents (javaimp-scope-concat-parents s)))
+ scopes))))))
+
+(defun javaimp--ident-to-fqcn (ident)
+ "Convert identifier IDENT to fully-qualified class name."
+ (mapconcat #'identity
+ (seq-filter (lambda (s) (not (string-empty-p s)))
+ (list
+ (get-text-property 0 'package ident)
+ (get-text-property 0 'parents ident)
+ ident))
+ "."))
;; Organizing imports
@@ -735,6 +784,42 @@ in a major mode hook."
+;; Xref support
+
+(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))))))
+
+(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql
'javaimp)))
+ (javaimp-xref--module-completion-table))
+
+(cl-defmethod xref-backend-definitions ((_backend (eql 'javaimp)) identifier)
+ (let* ((comp-table (javaimp-xref--module-completion-table))
+ (identifiers (all-completions identifier comp-table)))
+ (mapcar (lambda (ident)
+ (let* ((file (get-text-property 0 'file ident))
+ (buf (get-file-buffer file))
+ (loc (if buf
+ (xref-make-buffer-location
+ buf (get-text-property 0 'pos ident))
+ (xref-make-file-location
+ file
+ (get-text-property 0 'line ident)
+ (get-text-property 0 'column ident)))))
+ (xref-make ident loc)))
+ identifiers)))
+
+
+
;; Show scopes
(defvar javaimp-show-scopes-mode-map
@@ -1009,7 +1094,8 @@ using Javaimp facilities.
(add-function :override (local 'end-of-defun-function)
#'javaimp-end-of-defun)
(add-function :override (local 'add-log-current-defun-function)
- #'javaimp-add-log-current-defun))
+ #'javaimp-add-log-current-defun)
+ (add-hook 'xref-backend-functions #'javaimp-xref--backend nil t))
(remove-function (local 'imenu-create-index-function)
#'javaimp-imenu-create-index)
(remove-function (local 'beginning-of-defun-function)
@@ -1017,7 +1103,8 @@ using Javaimp facilities.
(remove-function (local 'end-of-defun-function)
#'javaimp-end-of-defun)
(remove-function (local 'add-log-current-defun-function)
- #'javaimp-add-log-current-defun)))
+ #'javaimp-add-log-current-defun)
+ (remove-hook 'xref-backend-functions #'javaimp-xref--backend t)))
;;;###autoload
@@ -1078,8 +1165,8 @@ any module's source file."
(defun javaimp-flush-cache ()
"Flush all caches."
(interactive)
- (setq javaimp-jar-file-cache nil
- javaimp-source-file-cache nil))
+ (setq javaimp-jar-file-classes-cache nil
+ javaimp-source-file-idents-cache nil))
(provide 'javaimp)
diff --git a/tests/tests.el b/tests/tests.el
index 832dbfc27a..c1c73ed951 100644
--- a/tests/tests.el
+++ b/tests/tests.el
@@ -9,16 +9,16 @@
(require 'ert-x)
(require 'javaimp)
-(ert-deftest javaimp-get-buffer-classes ()
- (with-temp-buffer
- (insert-file-contents (ert-resource-file "test1.java"))
- (should (equal (javaimp--get-buffer-classes)
- '("org.foo.Top"
- "org.foo.Top.CInner1"
- "org.foo.Top.CInner1.CInner1_CInner1"
- "org.foo.Top.IInner1"
- "org.foo.Top.IInner1.IInner1_CInner1"
- "org.foo.Top.IInner1.IInner1_IInner1"
- "org.foo.Top.EnumInner1"
- "org.foo.Top.EnumInner1.EnumInner1_EInner1"
- "org.foo.ColocatedTop")))))
+;; (ert-deftest javaimp-get-buffer-classes ()
+;; (with-temp-buffer
+;; (insert-file-contents (ert-resource-file "test1.java"))
+;; (should (equal (javaimp--get-buffer-classes)
+;; '("org.foo.Top"
+;; "org.foo.Top.CInner1"
+;; "org.foo.Top.CInner1.CInner1_CInner1"
+;; "org.foo.Top.IInner1"
+;; "org.foo.Top.IInner1.IInner1_CInner1"
+;; "org.foo.Top.IInner1.IInner1_IInner1"
+;; "org.foo.Top.EnumInner1"
+;; "org.foo.Top.EnumInner1.EnumInner1_EInner1"
+;; "org.foo.ColocatedTop")))))
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [elpa] externals/javaimp c06a1e57d7: First take at xref support,
Filipp Gunbin <=