emacs-elpa-diffs
[Top][All Lists]
Advanced

[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")))))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]