diff --git a/eglot-java.el b/eglot-java.el index e716944..c0329ac 100644 --- a/eglot-java.el +++ b/eglot-java.el @@ -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)) @@ -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))))