Eglot+jdtls: eglot--sig-info 解析返回结果失败

大佬们,我使用的 doom emacs 今天启动 eglot 报错了。

我排查发现是 id=3 的请求返回的结果解析错误,eglot--sig-infoactiveParamter 值为 nil 导致 3218 行 (when (= i active-param) 求值错误,它的上层函数是eglot-signature-eldoc-function 由于这里太复杂了(全是宏),我不知道怎么 debug 了 :grin: :grin: 请问大佬们怎么解决啊?

pyton 的没有任何问题,直接使用的doom的模块

eglot版本: e501275e06952889056268dabe08ccd0dbaf23e5

jdtls 版本: https://download.eclipse.org/jdtls/milestones/1.24.0/jdt-language-server-1.24.0-202306011728.tar.gz"

  1. 我的所有关于 eglot 的配置配置
;; https://github.com/joaotavora/eglot/discussions/888#discussioncomment-2386710
(cl-defmethod eglot-execute-command
  (_server (_cmd (eql java.apply.workspaceEdit)) arguments)
  "Eclipse JDT breaks spec and replies with edits as arguments."
  (mapc #'eglot--apply-workspace-edit arguments))

(cl-defgeneric +eglot/ext-uri-to-path (uri)
  "Support extension uri."
  nil)

;; https://github.com/eclipse/eclipse.jdt.ls/blob/b4e5cb4b693d5d503d90be89b0b9a8abe9db41a5/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTUtils.java#L809
(cl-defmethod +eglot/ext-uri-to-path (uri &context (major-mode java-mode))
  "Support Eclipse jdtls `jdt://' uri scheme."
  (when-let* ((jdt-scheme-p (string-prefix-p "jdt://" uri))
              (filename (save-match-data
                          (when (string-match "jdt://contents/\\(.*?\\)/\\(.*\\)\.class\\?" uri)
                            (format "%s.java"
                                    (replace-regexp-in-string "/" "." (match-string 2 uri) t t))))))
    (+eglot/create-source-file :java/classFileContents uri filename)))

;; https://github.com/emacs-lsp/lsp-mode/blob/d3bc47bde5ffc1bace40122a6ec0c6d8b9e84500/clients/lsp-clojure.el#L272
;; https://github.com/clojure-lsp/clojure-lsp/blob/master/lib/test/clojure_lsp/shared_test.clj
(cl-defmethod +eglot/ext-uri-to-path (uri &context (major-mode clojure-mode))
  "Support Clojure-lsp `zifile://', `jar:file://' uri scheme."
  (when-let* ((clj-scheme-p (or (string-prefix-p "jar:file://" uri)
                                (string-prefix-p "zipfile://" uri)))
              (filename (when (string-match "^\\(jar:file\\|zipfile\\)://.+\\(!/\\|::\\)\\(.+\\)" uri)
                          (let* ((ns-path (match-string 3 uri))
                                 (filename (replace-regexp-in-string "/" "." ns-path)))
                            filename))))
    (+eglot/create-source-file :clojure/dependencyContents uri filename)))

(defun +eglot/create-source-file (method uri filename)
  "Create source file and metadata in project root .eglot directory."
  (let* ((cache-dir (file-name-concat (project-root (eglot--current-project)) ".eglot"))
         (source-file (expand-file-name (file-name-concat cache-dir filename))))
    (unless (file-readable-p source-file)
      (let ((content (jsonrpc-request (eglot--current-server-or-lose) method (list :uri uri)))
            (metadata-file (+eglot/path-to-metadata-file 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/path-to-metadata-file (path)
  (format "%s.%s.metadata" (file-name-directory path) (file-name-base path)))

(defun +eglot/path-to-ext-uri (path)
  "Retrieve extension uri from metadata."
  (let ((metadata-file (+eglot/path-to-metadata-file path)))
    (when (file-exists-p metadata-file)
      (with-temp-buffer
        (insert-file-contents metadata-file)
        (buffer-string)))))

(define-advice eglot--uri-to-path (:around (orig-fn uri) advice)
  "Support non standard LSP uri scheme."
  (when (keywordp uri) (setq uri (substring (symbol-name uri) 1)))
  (or (+eglot/ext-uri-to-path uri)
      (funcall orig-fn uri)))

(define-advice eglot--path-to-uri (:around (orig-fn path) advice)
  "Support non standard LSP uri scheme."
  (or (+eglot/path-to-ext-uri path)
      (funcall orig-fn path)))

(after! eglot
  ;; 覆盖默认 eglot jump to reference 配置,
  (map! :map eglot-mode-map
        :n "gD" nil))

;; 设置lombok
(setq lombok-library-path (expand-file-name "java/lombok.jar" lsp-local-install-dir)
      jdk-base-path "/usr/lib/jvm/"
      eglot-extend-to-xref t)

(unless (file-exists-p lombok-library-path)
  (url-copy-file "https://projectlombok.org/downloads/lombok.jar" lombok-library-path))

(set-eglot-client! '(java-mode java-ts-mode)
                   ;; 服务器启动参数配置
                   `(,(expand-file-name "java/jdtls/bin/jdtls" lsp-local-install-dir)
                     "-configuration" ,(expand-file-name "java/jdtls/config_linux" lsp-local-install-dir)
                     "-data" ,(expand-file-name "java/workspace" lsp-local-install-dir)
                     ,(concat "--jvm-arg=-javaagent:" lombok-library-path)
                     ;; 初始化
                     :initializationOptions (:settings
                                             (:java
                                              (:configuration
                                               (:runtime [(:name "JavaSE-1.8" :path ,(expand-file-name "java-8-openjdk" jdk-base-path) :default t)
                                                          (:name "JavaSE-11" :path ,(expand-file-name "java-11-openjdk" jdk-base-path))
                                                          (:name "JavaSE-17" :path ,(expand-file-name "java-17-openjdk" jdk-base-path))])

                                               :format (:settings (:url ,(expand-file-name "formatter/java-google-style.xml" doom-private-dir)
                                                                   :profile "GoogleStyle"))

                                               ;; NOTE: https://github.com/redhat-developer/vscode-java/issues/406#issuecomment-356303715
                                               ;; > We enabled it by default so that workspace-wide errors can be reported (eg. removing a public method in one class would cause
                                               ;; compilation errors in other files consuming that method).
                                               ;; for large workspaces, it may make sense to be able to disable autobuild if it negatively impacts performance.
                                               :autobuild (:enabled t)

                                               ;; https://github.com/dgileadi/vscode-java-decompiler
                                               :contentProvider (:preferred "fernflower")))

                                             ;; WIP: support non standard LSP `java/classFileContents', `Location' items that have a `jdt://...' uri
                                             ;; https://github.com/eclipse/eclipse.jdt.ls/issues/1384
                                             ;; nvim impl demo: https://github.com/mfussenegger/dotfiles/commit/3cddf73cd43120da2655e2df6d79bdfd06697f0e
                                             ;; lsp-java impl demo: https://github.com/emacs-lsp/lsp-java/blob/master/lsp-java.el
                                             :extendedClientCapabilities (:classFileContentsSupport t)

                                             ;; bundles: decompilers, etc.
                                             ;; https://github.com/dgileadi/dg.jdt.ls.decompiler
                                             :bundles ,(let ((bundles-dir (expand-file-name "java/bundles" lsp-local-install-dir))
                                                             jdtls-bundles)
                                                         (require 'dash)
                                                         (->> (when (file-directory-p bundles-dir)
                                                                (directory-files bundles-dir t "\\.jar$"))
                                                              (append jdtls-bundles)
                                                              (apply #'vector))))))
;; 设置 Company 补全后端配置
(set-company-backend! 'eglot--managed-mode
  'company-capf
  '(:separate company-dabbrev company-yasnippet company-ispell company-abbrev))

;; 如果当前 java-mode 将自动启动 eglot
(add-hook! 'java-mode-local-vars-hook :append #'lsp!)

(after! nxml-mode
  ;; 设置 pom.xml 补全后端
  (set-company-backend! 'nxml-mode 'company-pom))

(set-file-template! "/resources/?.*/logback-spring\\.xml$" :trigger "__logback-spring" :mode 'nxml-mode :project t)
(set-file-template! "/resources/?.*/mybatis-spring\\.xml$" :trigger "__mybatis-spring" :mode 'nxml-mode :project t)
(set-file-template! "/test/[A-Z].*Test\\(s\\|Case\\)?\\.java$" :trigger "__test" :mode 'java-mode :project t)


  1. 报错日志
Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p nil)
  eglot--sig-info((:label "println(Object x) : void" :parameters [(:label "Object x")]) nil nil)
  #f(compiled-function (s) #<bytecode -0x7396529f3fe5a95>)((:label "println(Object x) : void" :parameters [(:label "Object x")]))
  mapconcat(#f(compiled-function (s) #<bytecode -0x7396529f3fe5a95>) [(:label "println() : void" :parameters []) (:label "println(Object x) : void" :parameters [(:label "Object x")]) (:label "println(String x) : void" :parameters [(:label "String x")]) (:label "println(char[] x) : void" :parameters [(:label "char[] x")]) (:label "println(double x) : void" :parameters [(:label "double x")]) (:label "println(float x) : void" :parameters [(:label "float x")]) (:label "println(long x) : void" :parameters [(:label "long x")]) (:label "println(int x) : void" :parameters [(:label "int x")]) (:label "println(char x) : void" :parameters [(:label "char x")]) (:label "println(boolean x) : void" :parameters [(:label "boolean x")])] "\n")
  #f(compiled-function (jsonrpc-lambda-elem11) #<bytecode -0x89666e83036998>)((:signatures [(:label "println() : void" :parameters []) (:label "println(Object x) : void" :parameters [(:label "Object x")]) (:label "println(String x) : void" :parameters [(:label "String x")]) (:label "println(char[] x) : void" :parameters [(:label "char[] x")]) (:label "println(double x) : void" :parameters [(:label "double x")]) (:label "println(float x) : void" :parameters [(:label "float x")]) (:label "println(long x) : void" :parameters [(:label "long x")]) (:label "println(int x) : void" :parameters [(:label "int x")]) (:label "println(char x) : void" :parameters [(:label "char x")]) (:label "println(boolean x) : void" :parameters [(:label "boolean x")])] :activeSignature 3 :activeParameter 0))
  jsonrpc-connection-receive(#<eglot-lsp-server eglot-lsp-server-15578cde2434> (:jsonrpc "2.0" :id 41 :result (:signatures [(:label "println() : void" :parameters []) (:label "println(Object x) : void" :parameters [(:label "Object x")]) (:label "println(String x) : void" :parameters [(:label "String x")]) (:label "println(char[] x) : void" :parameters [(:label "char[] x")]) (:label "println(double x) : void" :parameters [(:label "double x")]) (:label "println(float x) : void" :parameters [(:label "float x")]) (:label "println(long x) : void" :parameters [(:label "long x")]) (:label "println(int x) : void" :parameters [(:label "int x")]) (:label "println(char x) : void" :parameters [(:label "char x")]) (:label "println(boolean x) : void" :parameters [(:label "boolean x")])] :activeSignature 3 :activeParameter 0)))
  jsonrpc--process-filter(#<process EGLOT (java-learn/(java-mode java-ts-mode))> "Content-Length: 779\15\n\15\n{\"jsonrpc\":\"2.0\",\"id\":41,\"r...")
  1. eglot java 配置的日志输出是 · GitHub
  2. eglot python 输出日志 · GitHub

如果只是解决这个报错,可以把when的条件由(= i active-param)改成(and active-param (= i active-param))

怀疑这个问题根源还是eglot或者jdtls的某个bug,只是怀疑啊,我用jdtls但基本不用eglot。

ok, 我先临时这样设置,但是我想知道为啥 activeParamter 参数没有传过去,我看了, jdtls 是传过来了, 但是在 eglot–lambda 中没有设置,但是第一个候选能通过,真的太奇怪了 :smile: