;; -*- lexical-binding: t; -*- ;; IDE features ;; Corfu-mode provides inline autocompletion (use-package corfu :straight (:files (:defaults "extensions/corfu-echo.el")) :init (global-corfu-mode) (corfu-echo-mode) :config (defun corfu-move-to-minibuffer () (interactive) (when completion-in-region--data (let ((completion-extra-properties corfu--extra) completion-cycle-threshold completion-cycling) (apply #'consult-completion-in-region completion-in-region--data)))) (defun corfu-enable-in-minibuffer () "Enable Corfu in the minibuffer if `completion-at-point' is bound." (when (where-is-internal #'completion-at-point (list (current-local-map))) (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup corfu-popupinfo-delay nil) (corfu-mode 1))) (add-hook 'minibuffer-setup-hook #'corfu-enable-in-minibuffer) :general (corfu-map "S-SPC" #'corfu-insert-separator "M-m" #'corfu-move-to-minibuffer) :custom-face (corfu-echo ((t :inherit default)))) ;; Quick file overview for supported modes (use-package imenu :straight (:type built-in) :general (leader-map "m" #'imenu)) ;; Find definition/references (use-package xref :straight (:type built-in) :general (normal "M-," #'xref-pop-marker-stack) (normal "M-r" #'xref-find-references) :custom (xref-prompt-for-identifier nil)) (use-package eglot :commands (eglot) :config (add-to-list 'eglot-server-programs '(js-web-mode . ("typescript-language-server" "--stdio"))) (add-to-list 'eglot-server-programs `(html-web-mode . ,(eglot-alternatives '(("vscode-html-language-server" "--stdio") ("html-languageserver" "--stdio"))))) (defvar eglot-java-java-agent nil "Java agent JVM arg for eglot JDTLS.") ;; Custom eglot java server for deeper customization (defclass eglot-java-server (eglot-lsp-server) () :documentation "Eglot integration with JDTLS.") (defun eglot-java-contact (&optional interactive) `(eglot-java-server . ("jdtls" ,(if (s-blank? eglot-java-java-agent) "" (format "--jvm-arg=-javaagent:%s" eglot-java-java-agent)) :initializationOptions (:extendedClientCapabilities (:classFileContentsSupport t) :settings (:java (:home "/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home" :configuration (:runtimes [(:name "JavaSE-11" :path "/Library/Java/JavaVirtualMachines/amazon-corretto-11.jdk") (:name "JavaSE-17" :path "/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk" :default t)]))))))) (add-to-list 'eglot-server-programs '(java-mode . eglot-java-contact)) ;; Fix JDTLS's weird handling of workspaceEdit (cl-defmethod eglot-execute-command ((_server eglot-java-server) (_cmd (eql java.apply.workspaceEdit)) arguments) "Eclipse JDT breaks spec and replies with edits as arguments." (mapc #'eglot--apply-workspace-edit arguments)) ;; Support jdtls' ability to jump into class files (define-advice file-relative-name (:before-until (filename &optional directory) jdt-uri) (when (string-match-p "\\`jdt://" filename) filename)) (define-hash-table-test 'jdt-file-to-uri-hash 'equal 'sxhash-equal) (defvar eglot-java--jdt-file-to-uri (make-hash-table :test 'jdt-file-to-uri-hash) "Internal variable to map temporary filepaths to JDT URIs.") (define-advice eglot--path-to-uri (:before-until (path) jdt-path) (gethash (file-truename path) eglot-java--jdt-file-to-uri)) (defun jdt-class-file-name-handler (operation &rest args) "File name handler for jdtls' `jdt://' URIs." (cond ((eq operation 'temporary-file-directory) temporary-file-directory) ((member operation '(file-remote-p file-name-case-insensitive-p make-auto-save-file-name find-file-backup-name)) nil) (t (let* ((uri (car args)) (uri-hash (secure-hash 'md5 uri)) (filename (save-match-data (string-match "jdt://contents/\\(.*?\\)/\\(.*?\\)\.class\\?" uri) (format "%s.java" (replace-regexp-in-string "/" "." (match-string 2 uri) t t)))) (temp-dir (expand-file-name "eglot-jdtls" temporary-file-directory)) (uri-temp-dir (expand-file-name uri-hash temp-dir)) (filepath (concat (file-name-as-directory uri-temp-dir) filename)) (metadata-path (format "%s.%s.metadata" (file-name-directory filepath) (file-name-base filepath)))) (unless (gethash (file-truename filepath) eglot-java--jdt-file-to-uri) (puthash (file-truename filepath) uri eglot-java--jdt-file-to-uri)) (unless (or (file-readable-p filepath) (not (eglot-current-server))) (let ((contents (jsonrpc-request (eglot-current-server) :java/classFileContents (list :uri uri)))) (unless (file-directory-p uri-temp-dir) (make-directory uri-temp-dir t)) (with-temp-file filepath (insert contents)) (with-temp-file metadata-path (insert uri)))) (cond ((eq operation 'get-file-buffer) (get-buffer filename)) ((member operation '(expand-file-name directory-file-name file-truename file-name-directory file-name-nondirectory)) (apply operation filepath (cdr args))) ((eq operation 'insert-file-contents) (seq-let (uri visit beg end replace) args (let ((content (with-temp-buffer (insert-file-contents filepath nil beg end replace) (buffer-substring (point-min) (point-max))))) (insert content) (when visit (set-visited-file-name filepath t) (set-buffer-modified-p nil) (read-only-mode)) (list uri (length content))))) (t (let ((inhibit-file-name-handlers (cons 'jdt-class-file-name-handler (and (eq inhibit-file-name-operation operation) inhibit-file-name-handlers))) (inhibit-file-name-operation operation)) (apply operation args)))))))) (setq eglot-extend-to-xref t) (add-to-list 'file-name-handler-alist '("\\`jdt://" . jdt-class-file-name-handler)) (add-to-list 'auto-mode-alist '("\\`jdt://" . java-mode)) ;; Java build command (defun eglot-java-maven-build () (interactive) (if-let ((pom-path (locate-dominating-file default-directory "pom.xml"))) (let ((pom-uri (eglot--path-to-uri pom-path))) (jsonrpc-notify (eglot--current-server-or-lose) :java/projectConfigurationUpdate (list :uri pom-uri)) (jsonrpc-notify (eglot--current-server-or-lose) :java/buildWorkspace '((:json-false)))) (user-error "This doesn't appear to be a Maven project, could not find pom.xml."))) :init (defvar eglot-prefix-map (make-sparse-keymap) "Prefix keymap for eglot commands.") (general-def eglot-prefix-map "a" #'eglot-code-actions "r" #'eglot-reconnect "s" #'eglot-shutdown "R" #'eglot-rename "f" #'eglot-format-buffer "i" #'eglot-code-action-organize-imports "h" #'eldoc "e" #'flymake-show-buffer-diagnostics "E" #'flymake-show-project-diagnostics) :general (eglot-mode-map "C-c l" `(,eglot-prefix-map :which-key "eglot")) :hook (java-mode . eglot-ensure) (rust-mode . eglot-ensure) (js-mode . eglot-ensure) (typescript-mode . eglot-ensure) (python-mode . eglot-ensure) (js-web-mode . eglot-ensure) (html-web-mode . eglot-ensure) (scala-mode . eglot-ensure) (c-mode . eglot-ensure) :custom (eglot-confirm-server-initiated-edits nil)) ;; Some compilation-mode conveniences (use-package compile :straight (:type built-in) :commands compile :config (defun postprocess-compilation-buffer () (goto-char compilation-filter-start) (when (looking-at "\033c") (delete-region (point-min) (match-end 0))) (ansi-color-apply-on-region (point) (point-max))) (add-hook 'compilation-filter-hook 'postprocess-compilation-buffer)) ;; Code formatting library (use-package apheleia :straight (apheleia :host github :repo "raxod502/apheleia") :general ("C-c f" #'apheleia-format-buffer)) ;; Debugger interface (use-package realgud :defer t :config (provide 'init-ide)