Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 154 additions & 21 deletions eglot-java.el
Original file line number Diff line number Diff line change
Expand Up @@ -1434,26 +1434,156 @@ DESTINATION-DIR is the directory where the LSP server will be installed."

(defvar eglot-java-mode-map (make-sparse-keymap))

(defun eglot-java--jdt-uri-handler (_operation &rest args)
"Support Eclipse jdtls `jdt://' uri scheme."
(let* ((uri (car args))
(cache-dir (expand-file-name ".eglot-java" (project-root (project-current t))))
(source-file
(expand-file-name
(eglot-java--make-path
cache-dir
(save-match-data
(when (string-match "jdt://contents/\\(.*?\\)/\\(.*\\)\.class\\?" uri)
(format "%s.java" (replace-regexp-in-string "/" "." (match-string 2 uri) t t))))))))
(unless (file-readable-p source-file)
(let ((content (jsonrpc-request (eglot-java--find-server) :java/classFileContents (list :uri uri)))
(metadata-file (format "%s.%s.metadata"
(file-name-directory source-file)
(file-name-base source-file))))
(unless (file-directory-p cache-dir) (make-directory cache-dir t))
(with-temp-file source-file (insert content))
(with-temp-file metadata-file (insert uri))))
source-file))
(defun eglot-java--jdt-uri-exists-p (uri)
"Check if a jdt:// URI exists by attempting to fetch its contents."
(condition-case nil
(and (eglot-java--find-server)
(string-match "\\`jdt://" uri)
(jsonrpc-request (eglot-java--find-server)
:java/classFileContents
(list :uri uri))
t)
(error nil)))

(defun eglot-java--jdt-uri-readable-p (uri)
"Check if a jdt:// URI is readable."
(eglot-java--jdt-uri-exists-p uri))

(defun eglot-java--jdt-uri-insert-contents (uri &optional visit start end replace)
"Insert contents of jdt:// URI into current buffer.
VISIT, START, END, and REPLACE parameters follow the same semantics as
`insert-file-contents'."
(when (and start end)
(error "Partial reads not supported for jdt:// URIs"))

;; When VISIT is non-nil, set buffer-file-name like url-handlers does
(if visit (setq buffer-file-name uri))

(condition-case err
(let* ((server (eglot-java--find-server))
(content (when server
(jsonrpc-request server :java/classFileContents (list :uri uri))))
(start-pos (point)))
(unless content
(signal 'file-missing (list "Cannot read jdt:// URI" uri)))

(when replace
(delete-region (point-min) (point-max)))

(insert content)

;; Return list as expected by insert-file-contents
(list uri (length content)))
(error
(signal 'file-error (list "Cannot read jdt:// URI" uri (error-message-string err))))))

(defun eglot-java--jdt-uri-attributes (uri)
"Return minimal file attributes for jdt:// URI.
Returns a list compatible with `file-attributes' but with minimal information."
(when (eglot-java--jdt-uri-exists-p uri)
;; Return a minimal attributes list: (directory size mtime atime ctime modes links uid gid inode device)
(list nil ; not a directory
1 ; number of links
0 ; uid
0 ; gid
'(0 0) ; atime (dummy)
'(0 0) ; mtime (dummy)
'(0 0) ; ctime (dummy)
1024 ; size (dummy)
"r--r--r--" ; modes (read-only)
nil ; gid name
0 ; inode
0))) ; device

(defun eglot-java--jdt-uri-get-class-name (uri)
"Extract meaningful class name from jdt:// URI for buffer naming.
Converts jdt://contents/jar/package/Class.class?... to package.Class.java"
(save-match-data
(if (string-match "jdt://contents/[^/]+/\\(.+\\)\\.class\\?" uri)
(let* ((class-path (match-string 1 uri))
(class-name (replace-regexp-in-string "/" "." class-path)))
(format "%s.java" class-name))
;; Fallback to simple class name if regex doesn't match
(if (string-match "/\\([^/]+\\)\\.class" uri)
(format "%s.java" (match-string 1 uri))
"decompiled.java"))))

(defun eglot-java--jdt-uri-handler (operation &rest args)
"File name handler for Eclipse jdtls `jdt://' uri scheme.
This implements a complete file handler supporting read operations for jdt:// URIs
without creating temporary files."
(let ((filename (car args)))
(cond
;; File existence check
((eq operation 'file-exists-p)
(eglot-java--jdt-uri-exists-p filename))

;; File readability check
((eq operation 'file-readable-p)
(eglot-java--jdt-uri-readable-p filename))

;; Insert file contents into current buffer
((eq operation 'insert-file-contents)
(eglot-java--jdt-uri-insert-contents filename (cadr args) (caddr args) (cadddr args) (car (cddddr args))))

;; Write operations - return error
((memq operation '(write-region copy-file rename-file delete-file make-directory))
(signal 'file-error (list "Operation not supported for jdt:// URIs" operation filename)))

;; Expand file name - return as-is for jdt URIs
((eq operation 'expand-file-name)
filename)

;; File truename - return as-is for jdt URIs
((eq operation 'file-truename)
(if (and filename (stringp filename))
filename
(error "file-truename called with invalid argument: %s" filename)))

;; File local copy - not supported, return filename as-is
((eq operation 'file-local-copy)
nil)

;; File directory check - jdt URIs are not directories
((eq operation 'file-directory-p)
nil)

;; File regular check - jdt URIs are regular files
((eq operation 'file-regular-p)
(eglot-java--jdt-uri-exists-p filename))

;; Abbreviate file name - return abbreviated jdt URI
((eq operation 'abbreviate-file-name)
filename)

;; File name directory - return project root for jdt URIs
((eq operation 'file-name-directory)
(when-let ((server (eglot-java--find-server)))
(file-name-as-directory (project-root (eglot--project server)))))

;; File name nondirectory - return meaningful class name for buffer naming
((eq operation 'file-name-nondirectory)
(eglot-java--jdt-uri-get-class-name filename))

;; File name completion - not supported
((eq operation 'file-name-completion)
nil)

;; File remote check - JDT URIs are considered remote
((eq operation 'file-remote-p)
t)

;; Default case - let normal file operations handle it
(t
(let ((inhibit-file-name-handlers
(cons 'eglot-java--jdt-uri-handler
(and (eq inhibit-file-name-operation operation)
inhibit-file-name-handlers)))
(inhibit-file-name-operation operation))
(apply operation args))))))

;;;###autoload
(add-to-list 'auto-mode-alist '("\\`jdt://" . java-mode))

(add-to-list 'file-name-handler-alist '("\\`jdt://" . eglot-java--jdt-uri-handler))

Expand All @@ -1463,7 +1593,10 @@ DESTINATION-DIR is the directory where the LSP server will be installed."
"Hack until eglot is updated.
ARGS is a list with one element, a file path or potentially a URI.
If path is a jar URI, don't parse. If it is not a jar call ORIGINAL-FN."
(let ((path (file-truename (car args))))
(let* ((raw-path (car args))
(path (if (and (stringp raw-path) (string-match "\\`jdt://" raw-path))
raw-path
(file-truename raw-path))))
(if (equal "jdt" (url-type (url-generic-parse-url path)))
path
(apply original-fn args))))
Expand Down