Setting formatter via initializationOptions doesn't work
chasecaleb opened this issue · 7 comments
Overview
I'm not sure if I'm doing it wrong or if there's a bug in either nil or Emacs eglot, so I'm hoping you can help me. I'm trying to switch from lsp-mode to eglot with Emacs 29 and I can't seem to get initialization-time formatter configuration to work. I expect that calling eglot-format-buffer
should work regardless of whether the formatter is configured during initialization or at run-time, but only the latter does anything.
For reference, here's an online version of eglot's info page: https://joaotavora.github.io/eglot/#User_002dspecific-configuration
Version info:
caleb@desktop> nil --version
nil 2023-08-09
caleb@desktop> emacs --version
GNU Emacs 29.1
Copyright (C) 2023 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of GNU Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.
caleb@desktop> uname -a
Linux desktop 6.4.11 #1-NixOS SMP PREEMPT_DYNAMIC Wed Aug 16 16:32:31 UTC 2023 x86_64 GNU/Linux
Broken: initializationOptions
Config:
(add-to-list 'eglot-server-programs
`(nix-mode . ("nil" :initializationOptions (:nil (:formatting (:command ["nixpkgs-fmt"]))))))
LSP messages shown after loading a nix file and running eglot-format-buffer
:
[internal] Wed Aug 23 17:01:54 2023:
(:message "Running language server: nil")
[client-request] (id:1) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
(:processId 1358112 :rootPath "/home/caleb/code/emacs.nix/" :rootUri "file:///home/caleb/code/emacs.nix" :initializationOptions
(:nil
(:formatting
(:command
["nixpkgs-fmt"])))
:capabilities
(:workspace
(:applyEdit t :executeCommand
(:dynamicRegistration :json-false)
:workspaceEdit
(:documentChanges t)
:didChangeWatchedFiles
(:dynamicRegistration t)
:symbol
(:dynamicRegistration :json-false)
:configuration t :workspaceFolders t)
:textDocument
(:synchronization
(:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
:completion
(:dynamicRegistration :json-false :completionItem
(:snippetSupport t :deprecatedSupport t :resolveSupport
(:properties
["documentation" "details" "additionalTextEdits"])
:tagSupport
(:valueSet
[1]))
:contextSupport t)
:hover
(:dynamicRegistration :json-false :contentFormat
["markdown" "plaintext"])
:signatureHelp
(:dynamicRegistration :json-false :signatureInformation
(:parameterInformation
(:labelOffsetSupport t)
:activeParameterSupport t))
:references
(:dynamicRegistration :json-false)
:definition
(:dynamicRegistration :json-false :linkSupport t)
:declaration
(:dynamicRegistration :json-false :linkSupport t)
:implementation
(:dynamicRegistration :json-false :linkSupport t)
:typeDefinition
(:dynamicRegistration :json-false :linkSupport t)
:documentSymbol
(:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
(:valueSet
[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
:documentHighlight
(:dynamicRegistration :json-false)
:codeAction
(:dynamicRegistration :json-false :codeActionLiteralSupport
(:codeActionKind
(:valueSet
["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
:isPreferredSupport t)
:formatting
(:dynamicRegistration :json-false)
:rangeFormatting
(:dynamicRegistration :json-false)
:rename
(:dynamicRegistration :json-false)
:inlayHint
(:dynamicRegistration :json-false)
:publishDiagnostics
(:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
(:valueSet
[1 2])))
:window
(:workDoneProgress t)
:general
(:positionEncodings
["utf-32" "utf-8" "utf-16"])
:experimental #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
()))
:workspaceFolders
[(:uri "file:///home/caleb/code/emacs.nix" :name "~/code/emacs.nix/")]))
[server-reply] (id:1) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 1 :result
(:capabilities
(:codeActionProvider t :completionProvider
(:triggerCharacters
["." "?"])
:definitionProvider t :documentFormattingProvider t :documentHighlightProvider t :documentLinkProvider
(:resolveProvider t)
:documentSymbolProvider t :hoverProvider t :referencesProvider t :renameProvider
(:prepareProvider t)
:selectionRangeProvider t :semanticTokensProvider
(:full
(:delta :json-false)
:legend
(:tokenModifiers
["builtin" "conditional" "definition" "delimiter" "escape" "parenthesis" "readonly" "unresolved" "withAttribute"]
:tokenTypes
["boolean" "comment" "constant" "function" "keyword" "number" "operator" "parameter" "path" "property" "punctuation" "string" "struct" "variable"])
:range t)
:textDocumentSync
(:change 2 :openClose t))
:serverInfo
(:name "nil" :version "2023-08-09")))
[client-notification] Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :method "initialized" :params #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
()))
[client-notification] Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix" :version 0 :languageId "nix" :text "{}:\n{\n foo = \"123\";\n}\n")))
[client-notification] Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
(:settings #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
())))
[server-request] (id:0) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 0 :method "workspace/configuration" :params
(:items
[(:section "nil")]))
[client-reply] (id:0) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 0 :result
[nil])
[server-request] (id:1) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 1 :method "client/registerCapability" :params
(:registrations
[(:id "workspace/didChangeWatchedFiles" :method "workspace/didChangeWatchedFiles" :registerOptions
(:watchers
[(:globPattern "/home/caleb/code/emacs.nix/flake.lock")
(:globPattern "/home/caleb/code/emacs.nix/flake.nix")]))]))
[client-reply] (id:1) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 1 :result nil)
[server-request] (id:2) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 2 :method "workspace/configuration" :params
(:items
[(:section "nil")]))
[client-reply] (id:2) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 2 :result
[nil])
[server-request] (id:3) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 3 :method "window/workDoneProgress/create" :params
(:token "nil/loadNixosOptionsProgress"))
[client-reply] (id:3) Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :id 3 :result nil)
[server-notification] Wed Aug 23 17:01:54 2023:
(:jsonrpc "2.0" :method "$/progress" :params
(:token "nil/loadNixosOptionsProgress" :value
(:kind "begin" :title "Loading NixOS options from 'nixpkgs'")))
[server-notification] Wed Aug 23 17:01:56 2023:
(:jsonrpc "2.0" :method "$/progress" :params
(:token "nil/loadNixosOptionsProgress" :value
(:kind "end")))
[client-request] (id:2) Wed Aug 23 17:01:56 2023:
(:jsonrpc "2.0" :id 2 :method "textDocument/formatting" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:options
(:tabSize 2 :insertSpaces t :insertFinalNewline t :trimFinalNewlines t)))
[server-reply] (id:2) Wed Aug 23 17:01:56 2023:
(:jsonrpc "2.0" :id 2 :result nil)
[client-request] (id:3) Wed Aug 23 17:01:57 2023:
(:jsonrpc "2.0" :id 3 :method "textDocument/hover" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:position
(:line 0 :character 0)))
[client-request] (id:4) Wed Aug 23 17:01:57 2023:
(:jsonrpc "2.0" :id 4 :method "textDocument/documentHighlight" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:position
(:line 0 :character 0)))
[server-reply] (id:3) Wed Aug 23 17:01:57 2023:
(:jsonrpc "2.0" :id 3 :result nil)
[server-reply] (id:4) Wed Aug 23 17:01:57 2023:
(:jsonrpc "2.0" :id 4 :result
[])
I notice there's a workspace/didChangeConfiguration
message here, even though I'm not telling eglot to do that since eglot-workspace-configuration
is nil, but I think (:settings #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data ())))
represents an empty table?
Working: post-initialization settings
If I use this config instead, formatting works:
(setq-default eglot-workspace-configuration
'(:nil (:formatting (:command ["nixpkgs-fmt"]))))
Messages:
[internal] Wed Aug 23 17:00:19 2023:
(:message "Running language server: /run/current-system/sw/bin/nil")
[client-request] (id:1) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
(:processId 1357372 :rootPath "/home/caleb/code/emacs.nix/" :rootUri "file:///home/caleb/code/emacs.nix" :initializationOptions #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
())
:capabilities
(:workspace
(:applyEdit t :executeCommand
(:dynamicRegistration :json-false)
:workspaceEdit
(:documentChanges t)
:didChangeWatchedFiles
(:dynamicRegistration t)
:symbol
(:dynamicRegistration :json-false)
:configuration t :workspaceFolders t)
:textDocument
(:synchronization
(:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
:completion
(:dynamicRegistration :json-false :completionItem
(:snippetSupport t :deprecatedSupport t :resolveSupport
(:properties
["documentation" "details" "additionalTextEdits"])
:tagSupport
(:valueSet
[1]))
:contextSupport t)
:hover
(:dynamicRegistration :json-false :contentFormat
["markdown" "plaintext"])
:signatureHelp
(:dynamicRegistration :json-false :signatureInformation
(:parameterInformation
(:labelOffsetSupport t)
:activeParameterSupport t))
:references
(:dynamicRegistration :json-false)
:definition
(:dynamicRegistration :json-false :linkSupport t)
:declaration
(:dynamicRegistration :json-false :linkSupport t)
:implementation
(:dynamicRegistration :json-false :linkSupport t)
:typeDefinition
(:dynamicRegistration :json-false :linkSupport t)
:documentSymbol
(:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
(:valueSet
[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
:documentHighlight
(:dynamicRegistration :json-false)
:codeAction
(:dynamicRegistration :json-false :codeActionLiteralSupport
(:codeActionKind
(:valueSet
["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
:isPreferredSupport t)
:formatting
(:dynamicRegistration :json-false)
:rangeFormatting
(:dynamicRegistration :json-false)
:rename
(:dynamicRegistration :json-false)
:inlayHint
(:dynamicRegistration :json-false)
:publishDiagnostics
(:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
(:valueSet
[1 2])))
:window
(:workDoneProgress t)
:general
(:positionEncodings
["utf-32" "utf-8" "utf-16"])
:experimental #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
()))
:workspaceFolders
[(:uri "file:///home/caleb/code/emacs.nix" :name "~/code/emacs.nix/")]))
[server-reply] (id:1) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 1 :result
(:capabilities
(:codeActionProvider t :completionProvider
(:triggerCharacters
["." "?"])
:definitionProvider t :documentFormattingProvider t :documentHighlightProvider t :documentLinkProvider
(:resolveProvider t)
:documentSymbolProvider t :hoverProvider t :referencesProvider t :renameProvider
(:prepareProvider t)
:selectionRangeProvider t :semanticTokensProvider
(:full
(:delta :json-false)
:legend
(:tokenModifiers
["builtin" "conditional" "definition" "delimiter" "escape" "parenthesis" "readonly" "unresolved" "withAttribute"]
:tokenTypes
["boolean" "comment" "constant" "function" "keyword" "number" "operator" "parameter" "path" "property" "punctuation" "string" "struct" "variable"])
:range t)
:textDocumentSync
(:change 2 :openClose t))
:serverInfo
(:name "nil" :version "2023-08-09")))
[client-notification] Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :method "initialized" :params #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
()))
[client-notification] Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix" :version 0 :languageId "nix" :text "{}:\n{\n foo = \"123\";\n}\n")))
[client-notification] Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
(:settings
(:nil
(:formatting
(:command
["nixpkgs-fmt"])))))
[server-request] (id:0) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 0 :method "workspace/configuration" :params
(:items
[(:section "nil")]))
[client-reply] (id:0) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 0 :result
[(:formatting
(:command
["nixpkgs-fmt"]))])
[server-request] (id:1) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 1 :method "client/registerCapability" :params
(:registrations
[(:id "workspace/didChangeWatchedFiles" :method "workspace/didChangeWatchedFiles" :registerOptions
(:watchers
[(:globPattern "/home/caleb/code/emacs.nix/flake.lock")
(:globPattern "/home/caleb/code/emacs.nix/flake.nix")]))]))
[client-reply] (id:1) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 1 :result nil)
[server-request] (id:2) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 2 :method "workspace/configuration" :params
(:items
[(:section "nil")]))
[client-reply] (id:2) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 2 :result
[(:formatting
(:command
["nixpkgs-fmt"]))])
[server-request] (id:3) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 3 :method "window/workDoneProgress/create" :params
(:token "nil/loadNixosOptionsProgress"))
[client-reply] (id:3) Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :id 3 :result nil)
[server-notification] Wed Aug 23 17:00:19 2023:
(:jsonrpc "2.0" :method "$/progress" :params
(:token "nil/loadNixosOptionsProgress" :value
(:kind "begin" :title "Loading NixOS options from 'nixpkgs'")))
[server-notification] Wed Aug 23 17:00:20 2023:
(:jsonrpc "2.0" :method "$/progress" :params
(:token "nil/loadNixosOptionsProgress" :value
(:kind "end")))
[client-request] (id:2) Wed Aug 23 17:00:26 2023:
(:jsonrpc "2.0" :id 2 :method "textDocument/hover" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:position
(:line 3 :character 1)))
[client-request] (id:3) Wed Aug 23 17:00:26 2023:
(:jsonrpc "2.0" :id 3 :method "textDocument/documentHighlight" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:position
(:line 3 :character 1)))
[server-reply] (id:2) Wed Aug 23 17:00:26 2023:
(:jsonrpc "2.0" :id 2 :result nil)
[server-reply] (id:3) Wed Aug 23 17:00:26 2023:
(:jsonrpc "2.0" :id 3 :result
[])
[client-request] (id:4) Wed Aug 23 17:00:28 2023:
(:jsonrpc "2.0" :id 4 :method "textDocument/formatting" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:options
(:tabSize 2 :insertSpaces t :insertFinalNewline t :trimFinalNewlines t)))
[server-reply] (id:4) Wed Aug 23 17:00:28 2023:
(:jsonrpc "2.0" :id 4 :result
[(:newText "{}:\n{\n foo = \"123\";\n}\n" :range
(:end
(:character 0 :line 4)
:start
(:character 0 :line 0)))])
[internal] (id:5) Wed Aug 23 17:00:29 2023:
(:deferring :textDocument/hover :id 5 :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:position
(:line 3 :character 1)))
[internal] (id:6) Wed Aug 23 17:00:29 2023:
(:deferring :textDocument/documentHighlight :id 6 :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:position
(:line 3 :character 1)))
[client-notification] Wed Aug 23 17:00:29 2023:
(:jsonrpc "2.0" :method "textDocument/didChange" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix" :version 1)
:contentChanges
[(:range
(:start
(:line 0 :character 0)
:end
(:line 4 :character 0))
:rangeLength 30 :text "{}:\n{\n foo = \"123\";\n}\n")]))
[internal] Wed Aug 23 17:00:29 2023:
(:maybe-run-deferred
(6 5))
[client-request] (id:6) Wed Aug 23 17:00:29 2023:
(:jsonrpc "2.0" :id 6 :method "textDocument/documentHighlight" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:position
(:line 3 :character 1)))
[client-request] (id:5) Wed Aug 23 17:00:29 2023:
(:jsonrpc "2.0" :id 5 :method "textDocument/hover" :params
(:textDocument
(:uri "file:///home/caleb/code/emacs.nix/foo.nix")
:position
(:line 3 :character 1)))
[server-reply] (id:6) Wed Aug 23 17:00:29 2023:
(:jsonrpc "2.0" :id 6 :result
[])
[server-reply] (id:5) Wed Aug 23 17:00:29 2023:
(:jsonrpc "2.0" :id 5 :result nil)
I have the same issue with the same setup.
I also observe that automatic formatting is broken with nil
.
I'm not familiar with emacs but the log seems weird since it sends initializationOptions
but responding [nil]
for workspace/configuration
requests. I tried to use initializationOptions
with workspace/configuration
disabled under nvim and it seems to work fine.
Could you set env var NIL_LOG
to be nil=debug
and provide the stderr of the server? If it's hard to retrieve the server's stderr, NIL_LOG_PATH
can also be set to a file path to write the log into.
I have this working with the following config. looks like, when you set initializationOptions
for a language server, the options are nested under the name of the language server already. so, having your options under (:nil)
is actually incorrect.
(use-package eglot
:ensure nil
:after inheritenv
:hook
(prog-mode . eglot-ensure)
(prog-mode . (lambda () (add-hook 'before-save-hook 'eglot-format nil t)))
:config
(with-eval-after-load 'eglot
(dolist (mode '((nix-mode . ("nil" :initializationOptions
(:formatting (:command [ "nixpkgs-fmt" ]))))))
(add-to-list 'eglot-server-programs mode))))
@cmacrae This might be irrelevant to the original issue, but I had some problems setting this up. I did it without using the with-eval-after-load
macro and that didn't work although I see the eglot-server-programs
getting updated correctly. Your config worked for me, could you please explain why you're doing it this way?
@yzhou216 glad it works for you :) without with-eval-after-load
eglot may not have finished loading. even though eglot-server-programs
may be getting updated, if eglot hasn't finished loading, evaluation of the list may somehow not be effective - i can only surmise without digging into eglot's machinery