]> git.eshelyaron.com Git - emacs.git/commitdiff
Add chapter on advanced server configuration to Eglot manual
authorJoão Távora <joaotavora@gmail.com>
Thu, 9 Mar 2023 01:20:11 +0000 (01:20 +0000)
committerJoão Távora <joaotavora@gmail.com>
Thu, 9 Mar 2023 11:14:44 +0000 (11:14 +0000)
* doc/misc/eglot.texi (Top): Add section "Advanced server configuration"
(Setting Up LSP Servers): Rework.
(Advanced server configuration): New chapter.

doc/misc/eglot.texi

index eed9744b9f04fe20d45da55e622a8fb1ec87d6ea..474800fb27010ae91a66044f58888690d2fefdee 100644 (file)
@@ -98,6 +98,7 @@ This manual documents how to configure, use, and customize Eglot.
 * Eglot and LSP Servers::       How to work with language servers.
 * Using Eglot::                 Important Eglot commands and variables.
 * Customizing Eglot::           Eglot customization and advanced features.
+* Advanced server configuration::  Fine-tune a specific language server
 * Troubleshooting Eglot::       Troubleshooting and reporting bugs.
 * GNU Free Documentation License::  The license for this manual.
 * Index::
@@ -226,11 +227,10 @@ This says to invoke @var{program} with zero or more arguments
 standard input and standard output streams.
 
 @item (@var{program} @var{args}@dots{} :initializationOptions @var{options}@dots{})
-Like above, but with @var{options} specifying the options to be
-used for constructing the @samp{initializationOptions} JSON object for
-the server.  @var{options} can also be a function of one argument, in
-which case it will be called with the server instance as the argument,
-and should return the JSON object to use for initialization.
+@var{program} is invoked with @var{args} but @var{options} specifies
+how to construct the @samp{:initializationOptions} JSON object to pass
+the server on during the LSP handshake (@pxref{Advanced server
+configuration}).
 
 @item (@var{host} @var{port} @var{args}@dots{})
 Here @var{host} is a string and @var{port} is a positive integer
@@ -970,139 +970,287 @@ mechanism.
 Set this variable to true if you'd like progress notifications coming
 from the LSP server to be handled as Emacs's progress reporting
 facilities.
+@end table
+
+@node Advanced server configuration
+@chapter Advanced server configuration
+
+Though many language servers work well out-of-the-box, most allow
+fine-grained control of their operation via specific configuration
+options that vary from server to server.  A small number of servers
+require such special configuration to work acceptably, or even to work
+at all.
+
+After having setup a server executable program in
+@code{eglot-server-programs} (@pxref{Setting Up LSP Servers}) and
+ensuring Eglot can invoke it, you may want to take advantage of some
+of these options.  You should first distinguish two main kinds of
+server configuration:
+
+@itemize @bullet
+@item
+User-specific, applying to all projects the server is used for;
+
+@item
+Project-specific, applying to a specific project.
+@end itemize
+
+When you have decided which kind you need, the following sections
+teach how Eglot's user variables can be used to achieve it:
+
+@menu
+* User-specific configuration::
+* Project-specific configuration::
+* JSONRPC objects in Elisp::
+@end menu
+
+It's important to note that not all servers allow both kinds of
+configuration, nor is it guaranteed that user options can be copied
+over project options, and vice-versa.  When in doubt, consult your
+language server's documentation.
+
+It's also worth noting that some language servers can read these
+settings from configuration files in the user's @code{HOME} directory
+or in a project's directory.  For example, the @command{pylsp} Python
+server reads the file @file{~/.config/pycodestyle} for user
+configuration.  The @command{clangd} C/C++ server reads both
+@file{~/.config/clangd/config.yaml} for user configuration and
+@file{.clangd} for project configuration.  It may be advantageous to
+use these mechanisms instead of Eglot's, as the latter have the
+advantage of working with other LSP clients.
+
+@node User-specific configuration
+@section User-specific configuration
+@cindex initializationOptions
+@cindex command-line arguments
+
+This kind of configuration applies to all projects the server is used
+for.  Here, there are again two main ways to do this inside Eglot.
+
+A common way is to pass command-line options to the server invocation
+via @code{eglot-server-programs}.  Let's say we want to configure
+where the @command{clangd} server reads its
+@code{compile_commands.json} from.  This can be done like so:
+
+@lisp
+(with-eval-after-load 'eglot
+  (add-to-list 'eglot-server-programs
+               `(c++-mode . ("clangd" "--compile-commands-dir=/tmp"))))
+
+@end lisp
+
+@noindent
+Another way is to have Eglot pass a JSON object to the server during
+the LSP handshake.  This is done using the
+@code{:initializationOptions} syntax of @code{eglot-server-programs}:
+
+@lisp
+(with-eval-after-load 'eglot
+  (add-to-list 'eglot-server-programs
+               `(c++-mode . ("clangd" :initializationOptions
+                                      (:compilationDatabasePath "/tmp")))))
+@end lisp
+
+@noindent
+The argument @code{(:compilationDatabasePath "/tmp")} is Emacs's
+representation in plist format of a simple JSON object
+@code{@{"compilationDatabasePath": "/tmp"@}}.  To learn how to
+represent more deeply nested options in this format, @xref{JSONRPC
+objects in Elisp}.
+
+In this case, the two examples achieve exactly the same, but notice
+how the option's name has changed between them.
 
+@node Project-specific configuration
+@section Project-specific configuration
 @vindex eglot-workspace-configuration
-@cindex server workspace configuration
-@item eglot-workspace-configuration
-This variable is meant to be set in the @file{.dir-locals.el} file, to
-provide per-project settings, as described below in more detail.
-@end table
+@cindex workspace configuration
+
+To set project-specific settings, which the LSP specification calls
+@dfn{workspace configuration}, the variable
+@code{eglot-workspace-configuration} may be used.
 
-Some language servers need to know project-specific settings, which
-the LSP calls @dfn{workspace configuration}.  Eglot allows such fine
-tuning of per-project settings via the variable
-@code{eglot-workspace-configuration}.  Eglot sends the settings in
-this variable to each server, and each server applies the portion of the
-settings relevant to it and ignores the rest.  These settings are
-communicated to the server initially (upon establishing the
-connection) or when the settings are changed, or in response to a
-configuration request from the server.
-
-In many cases, servers can be configured globally using a
-configuration file in the user's home directory or in the project
-directory, which the language server reads.  For example, the
-@command{pylsp} server for Python reads the file
-@file{~/.config/pycodestyle} and the @command{clangd} server reads the
-file @file{.clangd} anywhere in the current project's directory tree.
-If possible, we recommend using those configuration files that are
-independent of Eglot and Emacs; they have the advantage that they will
-work with other LSP clients as well.
-
-If you do need to provide Emacs-specific configuration for a language
-server, we recommend defining the appropriate value in the
-@file{.dir-locals.el} file in the project's directory.  The value of
-this variable should be a property list of the following format:
+This variable is a directory-local variable (@pxref{Directory
+Variables, , Per-directory Local Variables, emacs, 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 globally or in a major-mode hook.
+
+The most common way to set @code{eglot-workspace-configuration } is
+using a @file{.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
+@code{dir-locals-set-class-variables} function.  (@pxref{Directory
+Local Variables,,, elisp, GNU Emacs Lisp Reference Manual}).
+
+However you choose to set it, the variable's value is a plist
+(@pxref{Property Lists,,, elisp, GNU Emacs Lisp Reference Manual}) with
+the following format:
 
 @lisp
- (:@var{server} @var{plist}@dots{})
+ (@var{:server1} @var{plist1} @var{:server2} @var{plist2} @dots{})
 @end lisp
 
 @noindent
-Here @code{:@var{server}} identifies a particular language server and
-@var{plist} is the corresponding keyword-value property list of one or
-more parameter settings for that server, serialized by Eglot as a JSON
-object.  @var{plist} may be arbitrarily complex, generally containing
-other keyword-value property sublists corresponding to JSON subobjects.
-The JSON values @code{true}, @code{false}, @code{null} and @code{@{@}}
-are represented by the Lisp values @code{t}, @code{:json-false},
-@code{nil}, and @code{eglot-@{@}}, respectively.
+Here, @var{:server1} and @var{:server2} are keywords whose names
+identify the LSP language servers to target.  Consult server
+documentation to find out what name to use.  @var{plist1} and
+@var{plist2} are plists of options, possibly nesting other plists.
 
 @findex eglot-show-workspace-configuration
 When experimenting with workspace settings, you can use the command
 @kbd{M-x eglot-show-workspace-configuration} to inspect and debug the
-JSON value to be sent to the server.  This helper command works even
-before actually connecting to the server.
+value of this variable in its final JSON form, ready to be sent to the
+server (@pxref{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 @kbd{M-x
+eglot-signal-didChangeConfiguration} which notifies the server during
+an ongoing Eglot session.
+
+@subsection Examples
+
+For some users, setting @code{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 (@pxref{Association Lists,,, elisp, 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 @command{pylsp} and @command{gopls} LSP 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:
+
+@example
+pylsp.plugins.jedi_completion.include_params: true
+pylsp.plugins.jedi_completion.fuzzy: true
+pylsp.pylint.enabled: false
+gopls.usePlaceholders: true
+@end example
+
+To apply this to Eglot, and assuming you chose the
+@file{.dir-locals.el} file method, the contents of that file could be:
 
-Here's an example of defining the workspace-configuration settings for
-a project that uses two different language servers, one for Python,
-the other one for Go (presumably, the project is written in a
-combination of these two languages).  The server for Python in this
-case is @command{pylsp}, the server for Go is @command{gopls}.  The
-value of @code{eglot-workspace-configuration} in this case should be:
+@lisp
+((nil
+  . ((eglot-workspace-configuration
+      . (:pylsp (:plugins (:jedi_completion (:include_params t
+                                             :fuzzy t)
+                           :pylint (:enabled :json-false)))
+         :gopls (:usePlaceholders t)))))
+ (python-mode . ((indent-tabs-mode . nil)))
+ (go-mode     . ((indent-tabs-mode . t))))
+@end lisp
+
+@noindent
+This sets the value of @code{eglot-workspace-configuration} in all the
+buffers inside the project; each server will use only the section of
+the parameters intended for that server, and ignore the rest.  Note
+how alists are used for associating Emacs mode names with alists
+associating variable names with variable values.  Then notice how
+plists are used inside the value of
+@code{eglot-workspace-configuration}.
+
+This following form may also be used:
 
 @lisp
 ((python-mode
   . ((eglot-workspace-configuration
       . (:pylsp (:plugins (:jedi_completion (:include_params t
                                              :fuzzy t)
-                           :pylint (:enabled :json-false)))))))
+                           :pylint (:enabled :json-false)))))
+     (indent-tabs-mode . nil)))
  (go-mode
   . ((eglot-workspace-configuration
-      . (:gopls (:usePlaceholders t))))))
+      . (:gopls (:usePlaceholders t)))
+     (indent-tabs-mode . t))))
 @end lisp
 
 @noindent
-This should go into the @file{.dir-locals.el} file in the project's
-root directory.  It sets up the value of
-@code{eglot-workspace-configuration} separately for each major mode.
-
-Alternatively, the same configuration could be defined as follows:
+This sets up the value of @code{eglot-workspace-configuration}
+separately depending on the major mode of each of that project's
+buffers.  @code{python-mode} buffers will have the variable set to
+@code{(:pylsp (:plugins ...))}.  @code{go-mode} buffers will have the
+variable set to @code{(:gopls (:usePlaceholders t))}.
+
+Some servers will issue workspace configuration for specific files
+inside your project.  For example, if you know @code{gopls} is asking
+about specific files in the @code{src/imported} subdirectory and you
+want to set a different option for @code{gopls.usePlaceholders} , you
+may use something like:
 
 @lisp
-((nil
+((python-mode
   . ((eglot-workspace-configuration
       . (:pylsp (:plugins (:jedi_completion (:include_params t
                                              :fuzzy t)
-                           :pylint (:enabled :json-false)))
-         :gopls (:usePlaceholders 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))))))
 @end lisp
 
-This is an equivalent setup which sets the value for all the
-major-modes inside the project; each server will use only the section
-of the parameters intended for that server, and ignore the rest.
-
-As yet another alternative, you can set the value of
-@code{eglot-workspace-configuration} programmatically, via the
-@code{dir-locals-set-class-variables} function, @pxref{Directory Local
-Variables,,, elisp, GNU Emacs Lisp Reference Manual}.
-
 Finally, if one needs to determine the workspace configuration based
 on some dynamic context, @code{eglot-workspace-configuration} can be
 set to a function.  The function is called with the
 @code{eglot-lsp-server} instance of the connected server (if any) and
 with @code{default-directory} set to the root of the project.  The
-function should return a value of the form described above.
+function should return a plist suitable for use as the variable's
+value.
 
-Some servers need special hand-holding to operate correctly.  If your
-server has some quirks or non-conformity, it's possible to extend
-Eglot via Elisp to adapt to it, by defining a suitable
-@code{eglot-initialization-options} method via @code{cl-defmethod}
-(@pxref{Generic Functions,,, elisp, GNU Emacs Lisp Reference Manual}).
+@node JSONRPC objects in Elisp
+@section JSONRPC objects in Elisp
 
-Here's an example:
+Emacs's preferred way of representing JSON is via Lisp lists.  In
+Eglot, the syntax of this list is the simplest possible (the one with
+fewer parenthesis), a plist (@pxref{Property Lists,,, elisp, GNU Emacs
+Lisp Reference Manual}).
 
-@lisp
-(require 'eglot)
+The plist may be arbitrarily complex, and generally containing other
+keyword-value property sub-plists corresponding to JSON sub-objects.
 
-(add-to-list 'eglot-server-programs
-             '((c++-mode c-mode) . (eglot-cquery "cquery")))
+For representing the JSON leaf values @code{true}, @code{false},
+@code{null} and @code{@{@}}, you can use the Lisp values @code{t},
+@code{:json-false}, @code{nil}, and @code{eglot-@{@}}, respectively.
 
-(defclass eglot-cquery (eglot-lsp-server) ()
-  :documentation "A custom class for cquery's C/C++ langserver.")
+For example, this plist:
 
-(cl-defmethod eglot-initialization-options ((server eglot-cquery))
-  "Passes through required cquery initialization options"
-  (let* ((root (car (project-roots (eglot--project server))))
-         (cache (expand-file-name ".cquery_cached_index/" root)))
-    (list :cacheDirectory (file-name-as-directory cache)
-          :progressReportFrequencyMs -1)))
+@lisp
+(:pylsp (:plugins (:jedi_completion (:include_params t
+                                             :fuzzy t)
+                           :pylint (:enabled :json-false)))
+ :gopls (:usePlaceholders t))
 @end lisp
 
-@noindent
-See the doc string of @code{eglot-initialization-options} for more
-details.
-@c FIXME: The doc string of eglot-initialization-options should be
-@c enhanced and extended.
+Is serialized by Eglot to the following JSON text:
+
+@example
+@{
+  "pylsp": @{
+    "plugins": @{
+      "jedi_completion": @{
+        "include_params": true,
+        "fuzzy": true
+      @},
+      "pylint": @{
+        "enabled": false
+      @}
+    @}
+  @},
+  "gopls": @{
+    "usePlaceholders":true
+  @},
+@}
+@end example
 
 @node Troubleshooting Eglot
 @chapter Troubleshooting Eglot