joaotavora / eglot

A client for Language Server Protocol servers
GNU General Public License v3.0
2.25k stars 201 forks source link

:initializationOptions key in eglot-server-programs does not work #940

Closed vitvakatu closed 2 years ago

vitvakatu commented 2 years ago

LSP transcript - M-x eglot-events-buffer (mandatory unless Emacs inoperable)

Not possible to provide - eglot is not starting.

Backtrace (mandatory, unless no error message seen or heard):

Debugger entered--Lisp error: (invalid-slot-name "#<eglot-lsp-server eglot-lsp-server-45c85222>" :initializationOptions)
  signal(invalid-slot-name ("#<eglot-lsp-server eglot-lsp-server-45c85222>" :initializationOptions))
  slot-missing(#<eglot-lsp-server eglot-lsp-server-45c85222> :initializationOptions oset ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check"))))))
  #f(compiled-function (obj slots) "Set slots of OBJ with SLOTS which is a list of name/value pairs.\nCalled from the constructor routine." #<bytecode 0x1af713d5d27c8517>)(#<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  apply(#f(compiled-function (obj slots) "Set slots of OBJ with SLOTS which is a list of name/value pairs.\nCalled from the constructor routine." #<bytecode 0x1af713d5d27c8517>) #<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  shared-initialize(#<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  #f(compiled-function (this &optional args) "Construct the new object THIS based on ARGS.\nARGS is a property list where odd numbered elements are tags, and\neven numbered elements are the values to store in the tagged slot.\nIf you overload the `initialize-instance', there you will need to\ncall `shared-initialize' yourself, or you can call `call-next-method'\nto have this constructor called automatically.  If these steps are\nnot taken, then new objects of your class will not have their values\ndynamically set from ARGS." #<bytecode 0x15852bd85dde58c4>)(#<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  apply(#f(compiled-function (this &optional args) "Construct the new object THIS based on ARGS.\nARGS is a property list where odd numbered elements are tags, and\neven numbered elements are the values to store in the tagged slot.\nIf you overload the `initialize-instance', there you will need to\ncall `shared-initialize' yourself, or you can call `call-next-method'\nto have this constructor called automatically.  If these steps are\nnot taken, then new objects of your class will not have their values\ndynamically set from ARGS." #<bytecode 0x15852bd85dde58c4>) (#<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check"))))))))
  #f(compiled-function (&rest cnm-args) #<bytecode 0x12388d69bf933f2a>)()
  #f(compiled-function (cl--cnm conn slots) #<bytecode 0xbce44c23eae7717>)(#f(compiled-function (&rest cnm-args) #<bytecode 0x12388d69bf933f2a>) #<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  apply(#f(compiled-function (cl--cnm conn slots) #<bytecode 0xbce44c23eae7717>) #f(compiled-function (&rest cnm-args) #<bytecode 0x12388d69bf933f2a>) (#<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check"))))))))
  #f(compiled-function (&rest args) #<bytecode 0x193e76b4af881e1b>)(#<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  apply(#f(compiled-function (&rest args) #<bytecode 0x193e76b4af881e1b>) #<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  initialize-instance(#<eglot-lsp-server eglot-lsp-server-45c85222> (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  #f(compiled-function (class &rest slots) "Default constructor for CLASS `eieio-default-superclass'.\nSLOTS are the initialization slots used by `initialize-instance'.\nThis static method is called when an object is constructed.\nIt allocates the vector used to represent an EIEIO object, and then\ncalls `initialize-instance' on that object." #<bytecode 0x1f7b812a4440be43>)(eglot-lsp-server :name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check"))))))
  apply(#f(compiled-function (class &rest slots) "Default constructor for CLASS `eieio-default-superclass'.\nSLOTS are the initialization slots used by `initialize-instance'.\nThis static method is called when an object is constructed.\nIt allocates the vector used to represent an EIEIO object, and then\ncalls `initialize-instance' on that object." #<bytecode 0x1f7b812a4440be43>) eglot-lsp-server (:name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  make-instance(eglot-lsp-server :name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown :process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check"))))))
  apply(make-instance eglot-lsp-server :name "EGLOT (rustfmt/rustic-mode)" :events-buffer-scrollback-size 2000000 :notification-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1bcedbab61>) :request-dispatcher #f(compiled-function (server method params) #<bytecode -0xa3d2f1b321bab61>) :on-shutdown eglot--on-shutdown (:process #f(compiled-function () #<bytecode -0x14193282e46f9648>) :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))))
  eglot--connect(rustic-mode (vc . "~/projects/rustfmt/") eglot-lsp-server ("rust-analyzer" :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))) "rustic")
  eglot(rustic-mode (vc . "~/projects/rustfmt/") eglot-lsp-server ("rust-analyzer" :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))) "rustic" t)
  funcall-interactively(eglot rustic-mode (vc . "~/projects/rustfmt/") eglot-lsp-server ("rust-analyzer" :initializationOptions ((:rust-analyzer (:checkOnSave (:extraArgs ("--target-dir" "/tmp/rust-analyzer-check")))))) "rustic" t)
  call-interactively(eglot record nil)
  command-execute(eglot record)
  execute-extended-command(nil "eglot" "eglot")
  funcall-interactively(execute-extended-command nil "eglot" "eglot")
  call-interactively(execute-extended-command nil nil)
  command-execute(execute-extended-command)

Minimum Reproducible Example (mandatory)

  1. I start Emacs GUI

  2. I downloaded the latest rust-analyzer executable from here: https://github.com/rust-lang/rust-analyzer/releases/tag/2022-04-25 and placed it to the directory in PATH.

  3. I can reproduce on any Rust file or repository, but let's take https://github.com/rust-lang/rustfmt. Go to file rustfmt/src/config/mod.rs.

  4. I have the following Eglot config in my Emacs config file:

    (use-package eglot
    :config
    (add-to-list 'eglot-server-programs '(rust-mode .
                                                  ("rust-analyzer"
                                                   :initializationOptions
                                                   ((:rust-analyzer
                                                     (:checkOnSave
                                                      (:extraArgs
                                                       ("--target-dir" "/tmp/rust-analyzer-check")))))))))
    ;; Rust
    (use-package rustic
    :config
    (setq rustic-lsp-client 'eglot)
    (setq indent-tabs-mode nil) ; use spaces instead of tabs
    (add-hook 'eglot--managed-mode-hook (lambda () (flymake-mode -1))))
  5. After running M-x eglot in any Rust file, I get the following error:

slot-missing: Invalid slot name: "#<eglot-lsp-server eglot-lsp-server-45ccf188>", :initializationOptions

I expect eglot to start normally, as the documentation states:

  • A list (PROGRAM [ARGS...] :initializationOptions OPTIONS), whereupon PROGRAM is called with ARGS as in the first option, and the LSP "initializationOptions" JSON object is constructed from OPTIONS. If OPTIONS is a unary function, it is called with the server instance and should return a JSON object.
basil-conto commented 2 years ago

See also https://github.com/joaotavora/eglot/discussions/845#discussioncomment-2539437.

EDIT

See also https://bugs.gnu.org/57725.

joaotavora commented 2 years ago

Thanks. This is a new feature that hasn't been properly tested.

joaotavora commented 2 years ago

@vitvakatu can you try with this patch to eglot.el (or simply evaluate the form)

diff --git a/eglot.el b/eglot.el
index ff94d5ca5f..3b1b1a9536 100644
--- a/eglot.el
+++ b/eglot.el
@@ -796,6 +796,8 @@ treated as in `eglot-dbind'."
   :documentation
   "Represents a server. Wraps a process for LSP communication.")

+(cl-defmethod initialize-instance :before ((_server eglot-lsp-server) &optional args)
+  (cl-remf args :initializationOptions))

 ;;; Process management
 (defvar eglot--servers-by-project (make-hash-table :test #'equal)