To set project-specific settings, which the LSP specification calls
workspace configuration, the variable
eglot-workspace-configuration may be used.
This variable is a directory-local variable (see Per-directory Local Variables in The GNU Emacs Manual). It’s important to recognize that this variable really only makes sense when set directory-locally. It usually does not make sense to set it file-locally or in a major-mode hook.
The most common way to set eglot-workspace-configuration is
using a .dir-locals.el file in the root of your project. If you
can’t do that, you may also set it from Elisp code via the
dir-locals-set-class-variables function. (see Directory Local
Variables in GNU Emacs Lisp Reference Manual).
However you choose to set it, the variable’s value is a plist (see Property Lists in GNU Emacs Lisp Reference Manual) with the following format:
(:server1 plist1 :server2 plist2 ...)
Here, :server1 and :server2 are keywords whose names identify the LSP language servers to target. Consult server documentation to find out what name to use. plist1 and plist2 are plists of options, possibly nesting other plists.
When experimenting with workspace settings, you can use the command M-x eglot-show-workspace-configuration to inspect and debug the value of this variable in its final JSON form, ready to be sent to the server (see JSONRPC objects in Elisp). This helper command works even before actually connecting to the server.
These variable’s value doesn’t take effect immediately. That happens upon establishing the connection, in response to an explicit query from the server, or when issuing the command M-x eglot-signal-didChangeConfiguration which notifies the server during an ongoing Eglot session.
For some users, setting eglot-workspace-configuration is a
somewhat daunting task. One of the reasons is having to manage the
general Elisp syntax of per-mode directory-local variables, which uses
alists (see Association Lists in GNU Emacs Lisp Reference
Manual), and the specific syntax of Eglot’s variable, which uses
plists. Some examples are useful.
Let’s say you want to configure two language servers to be used in a
project written in a combination of the Python and Go languages. You
want to use the pylsp and gopls languages servers.
In the documentation of the servers in question (or in some other
editor’s configuration file, or in some blog article), you find the
following configuration options in informal dotted-notation syntax:
pylsp.plugins.jedi_completion.include_params: true pylsp.plugins.jedi_completion.fuzzy: true pylsp.pylint.enabled: false gopls.usePlaceholders: true
To apply this to Eglot, and assuming you chose the .dir-locals.el file method, the contents of that file could be:
((nil . ((eglot-workspace-configuration . (:pylsp (:plugins (:jedi_completion (:include_params t :fuzzy t) :pylint (:enabled :json-false))) :gopls (:usePlaceholders t))))) (python-base-mode . ((indent-tabs-mode . nil))) (go-mode . ((indent-tabs-mode . t))))
Note how alists are used for associating Emacs mode names with other
alists, associating variable names with variable values. Then, pay
special attention to how the value of
eglot-workspace-configuration is a plist and not an alist.
The above form sets the value of eglot-workspace-configuration in
all the buffers inside the project; each language server will then use
only the section of the parameters intended for it, and ignore the rest.
In our example, pylsp and gopls are solely
interested in the :pylsp and :gopls parameter section
names, respectively. However, section names don’t have to match the
server name and some servers may be even interested in multiple
sections: the typescript-language-server server is known to be
interested in the :javascript and :typescript section
names.
To avoid sending whole configuration sections to servers who are decidedly not interested in them, the following form may also be used:
((python-base-mode . ((eglot-workspace-configuration . (:pylsp (:plugins (:jedi_completion (:include_params t :fuzzy t) :pylint (:enabled :json-false))))) (indent-tabs-mode . nil))) (go-mode . ((eglot-workspace-configuration . (:gopls (:usePlaceholders t))) (indent-tabs-mode . t))))
This sets up the value of eglot-workspace-configuration
separately depending on the major mode of each of that project’s
buffers. python-base-mode buffers will have the variable set to
(:pylsp (:plugins ...)). go-mode buffers will have the
variable set to (:gopls (:usePlaceholders t)).
Some servers will issue workspace configuration for specific source
files inside your project. For example, if you know gopls is
asking about specific files in the src/imported subdirectory and
you want to set a different option for gopls.usePlaceholders ,
you may use something like:
((python-base-mode
. ((eglot-workspace-configuration
. (:pylsp (:plugins (:jedi_completion (:include_params t
:fuzzy t)
:pylint (:enabled :json-false)))))
(indent-tabs-mode nil)))
(go-mode
. ((eglot-workspace-configuration
. (:gopls (:usePlaceholders t)))
(indent-tabs-mode t)))
("src/imported"
. ((eglot-workspace-configuration
. (:gopls (:usePlaceholders nil))))))
Finally, if one needs to determine the workspace configuration based on
some dynamic context, eglot-workspace-configuration can be set to
a function instead of a plist. The function is called with the
eglot-lsp-server instance of the connected server (if any) and
with default-directory set to the root of the project. The
function should return a plist suitable for use as the variable’s value.