aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoão Távora2022-10-20 13:50:09 +0100
committerJoão Távora2022-10-20 13:50:09 +0100
commit83fbda715973f57dc49fe002d255ecaff8273154 (patch)
treeab89ec338df6c9b818ba65fb2fcc1fd1e60d85b7
parent6f3ade1c08c6cbf56c0dc0d12e9508c261eb42bf (diff)
parent8b3a7003274de7b184b71c4552e6c4518948bcfe (diff)
downloademacs-83fbda715973f57dc49fe002d255ecaff8273154.tar.gz
emacs-83fbda715973f57dc49fe002d255ecaff8273154.zip
Merge branch 'feature/eglot2emacs'
-rw-r--r--doc/misc/Makefile.in2
-rw-r--r--doc/misc/eglot.texi1129
-rw-r--r--lisp/info-look.el1
-rw-r--r--lisp/progmodes/eglot.el3457
4 files changed, 4588 insertions, 1 deletions
diff --git a/doc/misc/Makefile.in b/doc/misc/Makefile.in
index 1d881a5fc7f..b6eef7ea799 100644
--- a/doc/misc/Makefile.in
+++ b/doc/misc/Makefile.in
@@ -68,7 +68,7 @@ DOCMISC_W32 = @DOCMISC_W32@
68 68
69## Info files to build and install on all platforms. 69## Info files to build and install on all platforms.
70INFO_COMMON = auth autotype bovine calc ccmode cl \ 70INFO_COMMON = auth autotype bovine calc ccmode cl \
71 dbus dired-x ebrowse ede ediff edt eieio \ 71 dbus dired-x ebrowse ede ediff edt eglot eieio \
72 emacs-mime epa erc ert eshell eudc efaq eww \ 72 emacs-mime epa erc ert eshell eudc efaq eww \
73 flymake forms gnus emacs-gnutls htmlfontify idlwave ido info.info \ 73 flymake forms gnus emacs-gnutls htmlfontify idlwave ido info.info \
74 mairix-el message mh-e modus-themes newsticker nxml-mode octave-mode \ 74 mairix-el message mh-e modus-themes newsticker nxml-mode octave-mode \
diff --git a/doc/misc/eglot.texi b/doc/misc/eglot.texi
new file mode 100644
index 00000000000..6a4127bed7c
--- /dev/null
+++ b/doc/misc/eglot.texi
@@ -0,0 +1,1129 @@
1\input texinfo @c -*-texinfo-*-
2@c %**start of header
3@setfilename ../../eglot.info
4@settitle Eglot: The Emacs Client for the Language Server Protocol
5@include docstyle.texi
6@syncodeindex vr cp
7@syncodeindex fn cp
8@c %**end of header
9
10@copying
11This manual is for Eglot, the Emacs LSP client.
12
13Copyright @copyright{} 2022 Free Software Foundation, Inc.
14
15@quotation
16Permission is granted to copy, distribute and/or modify this document
17under the terms of the GNU Free Documentation License, Version 1.3 or
18any later version published by the Free Software Foundation; with no
19Invariant Sections, with the Front-Cover Texts being ``A GNU Manual'',
20and with the Back-Cover Texts as in (a) below. A copy of the license
21is included in the section entitled ``GNU Free Documentation License''.
22
23(a) The FSF's Back-Cover Text is: ``You have the freedom to copy and
24modify this GNU manual.''
25@end quotation
26@end copying
27
28@dircategory Emacs misc features
29@direntry
30* Eglot: (eglot). Language Server Protocol client for Emacs.
31@end direntry
32
33@titlepage
34@sp 4
35@c The title is printed in a large font.
36@center @titlefont{User's Guide}
37@sp 1
38@center @titlefont{to}
39@sp 1
40@center @titlefont{Eglot: The Emacs LSP Client}
41@ignore
42@sp 2
43@center release 1.8
44@c -release-
45@end ignore
46@sp 3
47@center Jo@~ao T@'avora & Eli Zaretskii
48@c -date-
49
50@page
51@vskip 0pt plus 1filll
52@insertcopying
53@end titlepage
54
55@contents
56
57@ifnottex
58@node Top
59@top Eglot
60
61@cindex LSP
62@cindex language server protocol
63Eglot is the Emacs client for the @dfn{Language Server Protocol}
64(@acronym{LSP}). The name ``Eglot'' is an acronym that stands for
65``@emph{E}macs Poly@emph{glot}''.@footnote{
66A @dfn{polyglot} is a
67person who is able to use several languages.
68} Eglot provides infrastructure and a set of commands for enriching
69the source code editing capabilities of Emacs via LSP. LSP is a
70standardized communications protocol between source code editors (such
71as Emacs) and language servers, programs external to Emacs for
72analyzing source code on behalf of Emacs. The protocol allows Emacs
73to receive various source code services from the server, such as
74description and location of functions calls, types of variables, class
75definitions, syntactic errors, etc. This way, Emacs doesn't need to
76implement the language-specific parsing and analysis capabilities in
77its own code, but is still capable of providing sophisticated editing
78features that rely on such capabilities, such as automatic code
79completion, go-to definition of function/class, documentation of
80symbol at-point, refactoring, on-the-fly diagnostics, and more.
81
82Eglot itself is completely language-agnostic, but it can support any
83programming language for which there is a language server and an Emacs
84major mode.
85
86This manual documents how to configure, use, and customize Eglot.
87
88@insertcopying
89
90@menu
91* Quick Start:: For the impatient.
92* Eglot and LSP Servers:: How to work with language servers.
93* Using Eglot:: Important Eglot commands and variables.
94* Customizing Eglot:: Eglot customization and advanced features.
95* Troubleshooting Eglot:: Troubleshooting and reporting bugs.
96* GNU Free Documentation License:: The license for this manual.
97* Index::
98@end menu
99@end ifnottex
100
101@node Quick Start
102@chapter Quick Start
103@cindex quick start
104
105This chapter provides concise instructions for setting up and using
106Eglot with your programming project in common usage scenarios. For
107more detailed instructions regarding Eglot setup, @pxref{Eglot and LSP
108Servers}. @xref{Using Eglot}, for detailed description of using Eglot,
109and see @ref{Customizing Eglot}, for adapting Eglot to less common use
110patterns.
111
112Here's how to start using Eglot with your programming project:
113
114@enumerate
115@item
116Select and install a language server.
117
118Eglot comes pre-configured with many popular language servers, see the
119value of @code{eglot-server-programs}. If the server(s) mentioned
120there satisfy your needs for the programming language(s) with which
121you want to use Eglot, you just need to make sure those servers are
122installed on your system. Alternatively, install one or more servers
123of your choice and add them to the value of
124@code{eglot-server-programs}, as described in @ref{Setting Up LSP
125Servers}.
126
127@item
128Turn on Eglot for your project.
129
130To start using Eglot for a project, type @kbd{M-x eglot @key{RET}} in
131a buffer visiting any file that belongs to the project. This starts
132the language server configured for the programming language of that
133buffer, and causes Eglot to start managing all the files of the
134project which use the same programming language. The notion of a
135``project'' used by Eglot is the same Emacs uses (@pxref{Projects,,,
136emacs, GNU Emacs Manual}): in the simplest case, the ``project'' is
137the single file you are editing, but it can also be all the files in a
138single directory or a directory tree under some version control
139system, such as Git.
140
141Alternatively, you can start Eglot automatically from the major-mode
142hook of the mode used for the programming language; see @ref{Starting
143Eglot}.
144
145@item
146Use Eglot.
147
148Most Eglot facilities are integrated into Emacs features, such as
149ElDoc, Flymake, Xref, and Imenu. However, Eglot also provides
150commands of its own, mainly to perform tasks by the LSP server, such
151as @kbd{M-x eglot-rename} (to rename an identifier across the entire
152project), @kbd{M-x eglot-format} (to reformat and reindent code), and
153some others. @xref{Eglot Commands}, for the detailed list of Eglot
154commands.
155
156@item
157That's it!
158@end enumerate
159
160@node Eglot and LSP Servers
161@chapter Eglot and LSP Servers
162
163This chapter describes how to set up Eglot for your needs, and how to
164start it.
165
166@menu
167* Setting Up LSP Servers:: How to configure LSP servers for your needs.
168* Starting Eglot:: Ways of starting Eglot for your project.
169* Shutting Down LSP Servers::
170@end menu
171
172@node Setting Up LSP Servers
173@section Setting Up LSP Servers
174@cindex setting up LSP server for Eglot
175@cindex language server for Eglot
176
177For Eglot to be useful, it must first be combined with a suitable
178language server. Usually, that means running the server program
179locally as a child process of Emacs (@pxref{Processes,,, elisp, GNU
180Emacs Lisp Reference Manual}) and communicating with it via the
181standard input and output streams.
182
183The language server program must be installed separately, and is not
184further discussed in this manual; refer to the documentation of the
185particular server(s) you want to install.
186
187To use a language server, Eglot must know how to start it and which
188programming languages each server supports. Eglot comes with a fairly
189complete set of associations of major-modes to popular language
190servers predefined. This information is provided by the
191@code{eglot-server-programs} variable.
192
193@defvar eglot-server-programs
194This variable associates major modes with names and command-line
195arguments of the language server programs corresponding to the
196programming language of each major mode. It provides all the
197information that Eglot needs to know about the programming language of
198the source you are editing.
199
200The value of the variable is an alist, whose elements are of the form
201@w{@code{(@var{major-mode} . @var{server})}}.
202
203The @var{major-mode} of the alist elements can be either a symbol of
204an Emacs major mode or a list of the form @w{@code{(@var{mode}
205:language-id @var{id})}}, with @var{mode} being a major-mode symbol
206and @var{id} a string that identifies the language to the server. The
207latter form should be used if Eglot cannot by itself convert the
208major-mode to the language identifier string required by the server.
209In addition, @var{major-mode} can be a list of several major modes
210specified in one of the above forms -- this means a running instance
211of the associated server is responsible for files of multiple major
212modes or languages in the project.
213
214The @var{server} part of the alist elements can be one of the
215following:
216
217@table @code
218@item (@var{program} @var{args}@dots{})
219This says to invoke @var{program} with zero or more arguments
220@var{args}; the program is expected to communicate with Emacs via the
221standard input and standard output streams.
222
223@item (@var{program} @var{args}@dots{} :initializationOptions @var{options}@dots{})
224Like above, but with @var{options} specifying the options to be
225used for constructing the @samp{initializationOptions} JSON object for
226the server. @var{options} can also be a function of one argument, in
227which case it will be called with the server instance as the argument,
228and should return the JSON object to use for initialization.
229
230@item (@var{host} @var{port} @var{args}@dots{})
231Here @var{host} is a string and @var{port} is a positive integer
232specifying a TCP connection to a remote server. The @var{args} are
233passed to @code{open-network-stream}, e.g.@: if the connection needs
234to use encryption or other non-default parameters (@pxref{Network,,,
235elisp, GNU Emacs Lisp Reference Manual}).
236
237@item (@var{program} @var{args}@dots{} :autoport @var{moreargs}@dots{})
238@var{program} is started with a command line constructed from
239@var{args} followed by an available server port and the rest of
240arguments in @var{moreargs}; Eglot then establishes a TCP connection
241with the server via that port on the local host.
242
243@item @var{function}
244This should be a function of a single argument: non-@code{nil} if the
245connection was requested interactively (e.g., by the @code{eglot}
246command), otherwise @code{nil}. The function should return a value of
247any of the forms described above. This allows interaction with the
248user for determining the program to start and its command-line
249arguments.
250@end table
251
252@end defvar
253
254If you need to add server associations to the default list, use
255@code{add-to-list}. For example, if there is a hypothetical language
256server program @command{fools} for the language @code{Foo} which is
257supported by an Emacs major-mode @code{foo-mode}, you can add it to
258the alist like this:
259
260@lisp
261(add-to-list 'eglot-server-programs
262 '(foo-mode . ("fools" "--stdio")))
263@end lisp
264
265This will invoke the program @command{fools} with the command-line
266argument @option{--stdio} in support of editing source files for which
267Emacs turns on @code{foo-mode}, and will communicate with the program
268via the standard streams. As usual with invoking programs, the
269executable file @file{fools} should be in one of the directories
270mentioned by the @code{exec-path} variable (@pxref{Subprocess
271Creation,,, elisp, GNU Emacs Lisp Reference Manual}), for Eglot to be
272able to find it.
273
274@node Starting Eglot
275@section Starting Eglot
276@cindex starting Eglot
277@cindex activating Eglot for a project
278
279@findex eglot
280The most common way to start Eglot is to simply visit a source file of
281a given language and use the command @kbd{M-x eglot}. This starts the
282language server suitable for the visited file's major-mode, and
283attempts to connect to it. If the connection to the language server
284is successful, you will see the @code{[eglot:@var{project}]} indicator
285on the mode line which reflects the server that was started. If the
286server program couldn't be started or connection to it failed, you
287will see an error message; in that case, try to troubleshoot the
288problem as described in @ref{Troubleshooting Eglot}. Once a language
289server was successfully started and Eglot connected to it, you can
290immediately start using the Emacs features supported by Eglot, as
291described in @ref{Eglot Features}.
292
293A single Eglot session for a certain major-mode usually serves all the
294buffers under that mode which visit files from the same project, so
295you don't need to invoke @kbd{M-x eglot} again when you visit another
296file from the same project which is edited using the same major-mode.
297This is because Eglot uses the Emacs project infrastructure, as
298described in @ref{Eglot and Buffers}, and this knows about files that
299belong to the same project. Thus, after starting an Eglot session for
300some buffer, that session is automatically reused when visiting files
301in the same project with the same major-mode.
302
303@findex eglot-ensure
304Alternatively, you could configure Eglot to start automatically for
305one or more major-modes from the respective mode hooks. Here's an
306example for a hypothetical @code{foo-mode}:
307
308@lisp
309 (add-hook 'foo-mode-hook 'eglot-ensure)
310@end lisp
311
312@noindent
313The function @code{eglot-ensure} will start an Eglot session for each
314buffer in which @code{foo-mode} is turned on, if there isn't already
315an Eglot session that handles the buffer. Note that this variant of
316starting an Eglot session is non-interactive, so it should be used
317only when you are confident that Eglot can be started reliably for any
318file which may be visited with the major-mode in question.
319
320When Eglot connects to a language server for the first time in an
321Emacs session, it runs the hook @code{eglot-connect-hook}
322(@pxref{Eglot Variables}).
323
324@node Shutting Down LSP Servers
325@section Shutting Down LSP Servers
326@cindex shutting down LSP server
327
328When Eglot is turned on, it arranges for turning itself off
329automatically if the language server process terminates. Turning off
330Eglot means that it shuts down the server connection, ceases its
331management of all the buffers that use the server connection which was
332terminated, deactivates its minor mode, and restores the original
333values of the Emacs variables that Eglot changed when it was turned
334on. @xref{Eglot and Buffers}, for more details of what Eglot
335management of a buffer entails.
336
337@findex eglot-shutdown
338You can also shut down a language server manually, by using the
339command @kbd{M-x eglot-shutdown}. This prompts for the server (unless
340there's only one connection and it's used in the current buffer), and
341then shuts it down. By default, it also kills the server's events
342buffer (@pxref{Troubleshooting Eglot}), but a prefix argument prevents
343that.
344
345Alternatively, you can customize the variable
346@code{eglot-autoshutdown} to a non-@code{nil} value, in which case
347Eglot will automatically shut down the language server process when
348the last buffer served by that language server is killed. The default
349of this variable is @code{nil}, so that visiting another file would
350automatically activate Eglot even when the project which started Eglot
351with the server no longer has any buffer associated with it. This
352default allows you to start a server only once in each Emacs session.
353
354@node Using Eglot
355@chapter Using Eglot
356
357This chapter describes in detail the features that Eglot provides and
358how it does that. It also provides reference sections for Eglot
359commands and variables.
360
361@menu
362* Eglot Features::
363* Eglot and Buffers::
364* Eglot Commands::
365* Eglot Variables::
366@end menu
367
368@node Eglot Features
369@section Eglot Features
370@cindex features in buffers supported by Eglot
371
372Once Eglot is enabled in a buffer, it uses LSP and the language-server
373capabilities to activate, enable, and enhance modern IDE features in
374Emacs. The features themselves are usually provided via other Emacs
375packages. These are the main features that Eglot enables and provides:
376
377@itemize @bullet
378@item
379At-point documentation: when point is at or near a symbol or an
380identifier, the information about the symbol/identifier, such as the
381signature of a function or class method and server-generated
382diagnostics, is made available via the ElDoc package (@pxref{Lisp
383Doc,,, emacs, GNU Emacs Manual}). This allows major modes to provide
384extensive help and documentation about the program identifiers.
385
386@item
387On-the-fly diagnostic annotations with server-suggested fixes, via the
388Flymake package (@pxref{Top,,, flymake, GNU Flymake manual}). This
389improves and enhances the Flymake diagnostics, replacing the other
390Flymake backends.
391
392@item
393Finding definitions and uses of identifiers, via Xref (@pxref{Xref,,,
394emacs, GNU Emacs Manual}). Eglot provides a backend for the Xref
395capabilities which uses the language-server understanding of the
396program source. In particular, it eliminates the need to generate
397tags tables (@pxref{Tags tables,,, emacs, GNU Emacs Manual}) for
398languages that are only supported by the @code{etags} backend.
399
400@item
401Buffer navigation by name of function, class, method, etc., via Imenu
402(@pxref{Imenu,,, emacs, GNU Emacs Manual}). Eglot provides its own
403variant of @code{imenu-create-index-function}, which generates the
404index for the buffer based on language-server program source analysis.
405
406@item
407Enhanced completion of symbol at point by the
408@code{completion-at-point} command (@pxref{Symbol Completion,,, emacs,
409GNU Emacs Manual}). This uses the language-server's parser data for
410the completion candidates.
411
412@item
413Automatic reformatting of source code as you type it. This is similar
414to what the @code{eglot-format} command does (see below), but is
415activated automatically as you type.
416
417@item
418If a completion package such as @code{company-mode}, a popular
419third-party completion package, is installed, Eglot enhances it by
420providing completion candidates based on the language-server analysis
421of the source code. (@code{company-mode} can be installed from GNU ELPA.)
422
423@item
424If @code{yasnippet}, a popular third-party package for automatic
425insertion of code templates (snippets), is installed, and the language
426server supports snippet completion candidates, Eglot arranges for the
427completion package to instantiate these snippets using
428@code{yasnippet}. (@code{yasnippet} can be installed from GNU ELPA.)
429
430@item
431If the popular third-party package @code{markdown-mode} is installed,
432and the server provides at-point documentation formatted as Markdown
433in addition to plain text, Eglot arranges for the ElDoc package to
434enrich this text with e.g. fontification before displaying it to the
435user.
436
437@item
438In addition to enabling and enhancing other features and packages,
439Eglot also provides a small number of user commands based directly on
440the capabilities of language servers. These commands are:
441
442@table @kbd
443@item M-x eglot-rename
444This prompts for a new name for the symbol at point, and then modifies
445all the project source files to rename the symbol to the new name,
446based on editing data received from the language-server. @xref{Eglot
447and Buffers}, for the details of how project files are defined.
448
449@item M-x eglot-format
450This reformats and prettifies the current active region according to
451source formatting rules of the language-server. If the region is not
452active, it reformats the entire buffer instead.
453
454@item M-x eglot-format-buffer
455This reformats and prettifies the current buffer according to source
456formatting rules of the language-server.
457
458@cindex code actions
459@item M-x eglot-code-actions
460@itemx M-x eglot-code-action-organize-imports
461@itemx M-x eglot-code-action-quickfix
462@itemx M-x eglot-code-action-extract
463@itemx M-x eglot-code-action-inline
464@itemx M-x eglot-code-action-rewrite
465These command allow you to invoke the so-called @dfn{code actions}:
466requests for the language-server to provide editing commands for
467various code fixes, typically either to fix an error diagnostic or to
468beautify/refactor code. For example,
469@code{eglot-code-action-organize-imports} rearranges the program
470@dfn{imports}---declarations of modules whose capabilities the program
471uses. These commands affect all the files that belong to the
472project. The command @kbd{M-x eglot-code-actions} will pop up a menu
473of code applicable actions at point.
474@end table
475
476@end itemize
477
478Not all servers support the full set of LSP capabilities, but most of
479them support enough to enable the basic set of features mentioned
480above. Conversely, some servers offer capabilities for which no
481equivalent Emacs package exists yet, and so Eglot cannot (yet) expose
482these capabilities to Emacs users.
483
484@node Eglot and Buffers
485@section Buffers, Projects, and Eglot
486@cindex buffers managed by Eglot
487@cindex projects and Eglot
488
489@cindex workspace
490One of the main strong points of using a language server is that a
491language server has a broad view of the program: it considers more
492than just the single source file you are editing. Ideally, the
493language server should know about all the source files of your program
494which are written in the language supported by the server. In the
495language-server parlance, the set of the source files of a program is
496known as a @dfn{workspace}. The Emacs equivalent of a workspace is a
497@dfn{project} (@pxref{Projects,,, emacs, GNU Emacs Manual}). Eglot
498fully supports Emacs projects, and considers the file in whose buffer
499Eglot is turned on as belonging to a project. In the simplest case,
500that file is the entire project, i.e.@: your project consists of a
501single file. But there are other more complex projects:
502
503@itemize @bullet
504@item
505A single-directory project: several source files in a single common
506directory.
507
508@item
509A VC project: source files in a directory hierarchy under some VCS,
510e.g.@: a Git repository (@pxref{Version Control,,, emacs, GNU Emacs
511Manual}).
512
513@item
514An EDE project: source files in a directory hierarchy managed via the
515Emacs Development Environment (@pxref{EDE,,, emacs, GNU Emacs
516Manual}).
517@end itemize
518
519Eglot uses the Emacs's project management infrastructure to figure out
520which files and buffers belong to what project, so any kind of project
521supported by that infrastructure is automatically supported by Eglot.
522
523When Eglot starts a server program, it does so in the project's root
524directory, which is usually the top-level directory of the project's
525directory hierarchy. This ensures the language server has the same
526comprehensive view of the project's files as you do.
527
528For example, if you visit the file @file{~/projects/fooey/lib/x.foo}
529and @file{x.foo} belongs to a project rooted at
530@file{~/projects/fooey} (perhaps because a @file{.git} directory
531exists there), then @kbd{M-x eglot} causes the server program to start
532with that root as the current working directory. The server then will
533analyze not only the file @file{lib/x.foo} you visited, but likely
534also all the other @file{*.foo} files under the
535@file{~/projects/fooey} directory.
536
537In some cases, additional information specific to a given project will
538need to be provided to the language server when starting it. The
539variable @code{eglot-workspace-configuration} (@pxref{Customizing
540Eglot}) exists for that purpose. It specifies the parameters and
541their values to communicate to each language server which needs that.
542
543When Eglot is active for a project, it performs several background
544activities on behalf of the project and its buffers:
545
546@itemize @bullet
547@cindex mode-line indication of language server
548@cindex mouse clicks on mode-line, and Eglot
549@vindex eglot-menu
550@item
551All of the project's file-visiting buffers under the same major-mode
552are served by a single language-server connection. (If the project
553uses several programming languages, there will usually be a separate
554server connection for each group of files written in the same language
555and using the same Emacs major-mode.) Eglot adds the
556@samp{[eglot:@var{project}]} indication to the mode line of
557each such buffer, where @var{server} is the name of the server and
558@var{project} identifies the project by its root directory. Clicking
559the mouse on the Eglot mode-line indication activates a menu with
560server-specific items.
561
562@item
563For each buffer in which Eglot is active, it notifies the language
564server that Eglot is @dfn{managing} the file visited by that buffer.
565This tells the language server that the file's contents on disk may no
566longer be up-to-date due to unsaved edits. Eglot reports to the
567server any changes in the text of each managed buffer, to make the
568server aware of unsaved changes. This includes your editing of the
569buffer and also changes done automatically by other Emacs features and
570commands. Killing a buffer relinquishes its management by Eglot and
571notifies the server that the file on disk is up-to-date.
572
573@vindex eglot-managed-mode-hook
574@vindex eglot-managed-p
575@item
576Eglot turns on a special minor mode in each buffer it manages. This
577minor mode ensures the server is notified about files Eglot manages,
578and also arranges for other Emacs features supported by Eglot
579(@pxref{Eglot Features}) to receive information from the language
580server, by changing the settings of these features. Unlike other
581minor-modes, this special minor mode is not activated manually by the
582user, but automatically as result of starting an Eglot session for the
583buffer. However, this minor mode provides a hook variable
584@code{eglot-managed-mode-hook} that can be used to customize the Eglot
585management of the buffer. This hook is run both when the minor mode
586is turned on and when it's turned off; use the variable
587@code{eglot-managed-p} to tell if current buffer is still being
588managed or not. When Eglot stops managing the buffer, this minor mode
589is turned off, and all the settings that Eglot changed are restored to
590their original values.
591
592@item
593When you visit a file under the same project, whether an existing or a
594new file, its buffer is automatically added to the set of buffers
595managed by Eglot, and the server which supports the buffer's
596major-mode is notified about that. Thus, visiting a non-existent file
597@file{/home/joe/projects/fooey/lib/y.foo} in the above example will
598notify the server of the @file{*.foo} files' language that a new file
599was added to the project, even before the file appears on disk. The
600special Eglot minor mode is also turned on automatically in the buffer
601visiting the file.
602@end itemize
603
604@node Eglot Commands
605@section Eglot Commands
606@cindex commands, Eglot
607
608This section provides a reference of the most commonly used Eglot
609commands:
610
611@ftable @code
612@item M-x eglot
613This command adds the current buffer and the file it visits to the
614group of buffers and files managed by Eglot on behalf of a suitable
615language server. If a language server for the buffer's
616@code{major-mode} (@pxref{Major Modes,,, emacs, GNU Emacs Manual}) is
617not yet running, it will be started; otherwise the buffer and its file
618will be added to those managed by an existing server session.
619
620The command attempts to figure out the buffer's major mode and the
621suitable language server; in case it fails, it might prompt for the
622major mode to use and for the server program to start. If invoked
623with a prefix argument @kbd{C-u}, it always prompts for the server
624program, and if invoked with @kbd{C-u C-u}, also prompt for the major
625mode.
626
627If the language server is successfully started and contacted, this
628command arranges for any other buffers belonging to the same project
629and using the same major mode to use the same language-server session.
630That includes any buffers created by visiting files after this command
631succeeds to connect to a language server.
632
633All the Emacs features that are capable of using Eglot services
634(@pxref{Eglot Features}) are automatically configured by this command
635to start using the language server via Eglot. To customize which
636Emacs features will be configured to use Eglot, use the
637@code{eglot-stay-out-of} option (@pxref{Customizing Eglot}).
638
639@item M-x eglot-reconnect
640Shuts down an the current connection to the language server and
641immediately restarts it using the same options used originally. This
642can sometimes be useful to unclog a partially malfunctioning server
643connection.
644
645@item M-x eglot-shutdown
646Shuts down a language server. This commands prompts for a language
647server to shut down (unless there's only one server session, and it
648manages the current buffer). Then the command shuts down the server
649and stops managing the buffers the server was used for. Emacs
650features (@pxref{Eglot Features}) that Eglot configured to work with
651the language server are restored back to their original configuration.
652
653Normally, this command kills the buffers used for communicating with
654the language server, but if invoked with a prefix argument @kbd{C-u},
655the command doesn't kill those buffers, allowing them to be used for
656diagnostics and problem reporting (@pxref{Troubleshooting Eglot}).
657
658@item M-x eglot-shutdown-all
659This command shuts down all the language servers active in the current
660Emacs session. As with @code{eglot-shutdown}, invoking this command
661with a prefix argument avoids killing the buffers used for
662communications with the language servers.
663
664@item M-x eglot-rename
665This command renames the program symbol (a.k.a.@: @dfn{identifier}) at
666point to another name. It prompts for the new name of the symbol, and
667then modifies all the files in the project which arte managed by the
668language server of the current buffer to implement the renaming.
669
670@item M-x eglot-format
671This command reformats the active region according to the
672language-server rules. If no region is active, it reformats the
673entire current buffer.
674
675@item M-x eglot-format-buffer
676This command reformats the current buffer, in the same manner as
677@code{eglot-format} does.
678
679@item M-x eglot-code-actions
680@itemx mouse-1
681This command asks the server for any @dfn{code actions} applicable at
682point. It can also be invoked by @kbd{mouse-1} clicking on
683diagnostics provided by the server.
684
685@item M-x eglot-code-action-organize-imports
686@itemx M-x eglot-code-action-quickfix
687@itemx M-x eglot-code-action-extract
688@itemx M-x eglot-code-action-inline
689@itemx M-x eglot-code-action-rewrite
690These commands invoke specific code actions supported by the language
691server.
692@c FIXME: Need more detailed description of each action.
693@end ftable
694
695The following Eglot commands are used less commonly, mostly for
696diagnostic and troubleshooting purposes:
697
698@ftable @code
699@item M-x eglot-events-buffer
700This command pops up the events buffer used for communication with the
701language server of the current buffer.
702
703@item M-x eglot-stderr-buffer
704This command pops up the buffer with the debug info printed by the
705language server to its standard error stream.
706
707@item M-x eglot-forget-pending-continuations
708Forget pending requests for the server of the current buffer.
709@c FIXME: Better description of the need.
710
711@item M-x eglot-signal-didChangeConfiguration
712This command updates the language server configuration according to
713the current value of the variable @code{eglot-workspace-configuration}
714(@pxref{Customizing Eglot}).
715
716@item M-x eglot-clear-status
717Clear the last JSONRPC error for the server of the current buffer.
718Eglot keeps track of erroneous situations encountered by the server in
719its mode-line indication so that the user may inspect the
720communication leading up to it (@pxref{Troubleshooting Eglot}). If
721the situation is deemed uninteresting or temporary, this command can
722be used to ``forget'' the error. Note that the command @code{M-x
723eglot-reconnect} can sometimes be used to unclog a temporarily
724malfunctioning server.
725@end ftable
726
727As described in @ref{Eglot Features} most features associated with
728Eglot are actually provided by other Emacs packages and features, and
729Eglot only enhances them by allowing them to use the information
730coming from the language servers. For completeness, here's the list
731of commands of those other packages that are very commonly used in
732Eglot-managed buffers:
733
734@c Not @ftable, because the index entries should mention Eglot
735@table @code
736@cindex eldoc, and Eglot
737@cindex documentation using Eglot
738@item M-x eldoc
739Ask the ElDoc system for help at point.
740
741@cindex flymake, and Eglot
742@cindex on-the-fly diagnostics using Eglot
743@item M-x flymake-show-buffer-diagnostics
744Ask Flymake system to display diagnostics for the current buffer.
745
746@item M-x flymake-show-project-diagnostics
747Ask Flymake to list diagnostics for all the files in the current
748project.
749
750@cindex xref, and Eglot
751@cindex finding definitions of identifiers using Eglot
752@item M-x xref-find-definitions
753Ask Xref to go the definition of the identifier at point.
754
755@cindex imenu navigation using Eglot
756@item M-x imenu
757Let the user navigate the program source code using buffer index,
758categorizing program elements by syntactic class (class, method,
759variable, etc.) and offering completion.
760
761@cindex symbol completion using Eglot
762@item M-x completion-at-point
763Request completion of the symbol at point.
764@end table
765
766@node Eglot Variables
767@section Eglot Variables
768@cindex variables, Eglot
769
770This section provides a reference of the Eglot' user options.
771
772@vtable @code
773@item eglot-autoreconnect
774This option controls the ability to reconnect automatically to the
775language server when Eglot detects that the server process terminated
776unexpectedly. The default value 3 means to attempt reconnection only
777if the previous successful connection lasted for more than that number
778of seconds; a different positive value changes the minimal length of
779the connection to trigger reconnection. A value of @code{t} means
780always reconnect automatically, and @code{nil} means never reconnect.
781
782@item eglot-connect-timeout
783This specifies the number of seconds before connection attempt to a
784language server times out. The value of @code{nil} means never time
785out. The default is 30 seconds.
786
787@item eglot-sync-connect
788This setting is mainly important for connections which are slow to
789establish. Whereas the variable @code{eglot-connect-timeout} controls
790how long to wait for, this variable controls whether to block Emacs's
791user interface while waiting. The default value is 3; a positive
792value means block for that many seconds, then wait for the connection
793in the background. The value of @code{t} means block during the whole
794waiting period. The value of @code{nil} or zero means don't block at
795all during the waiting period.
796
797@item eglot-events-buffer-size
798This determines the size of the Eglot events buffer. @xref{Eglot
799Commands, eglot-events-buffer}, for how to display that buffer. If
800the value is changed, for it to take effect the connection should be
801restarted using @kbd{eglot-shutdown} followed by
802@kbd{eglot-reconnect}.
803@c FIXME: Shouldn't the defcustom do this by itself using the :set
804@c attribute?
805@xref{Troubleshooting Eglot}, for when this could be useful.
806
807@item eglot-autoshutdown
808If this is non-@code{nil}, Eglot shuts down a language server when the
809last buffer managed by it is killed. @xref{Shutting Down LSP Servers}.
810The default is @code{nil}; if you want to shut down a server, use
811@kbd{M-x eglot-shutdown} (@pxref{Eglot Commands}).
812
813@item eglot-confirm-server-initiated-edits
814Various Eglot commands and code actions result in the language server
815sending editing commands to Emacs. If this option's value is
816non-@code{nil} (the default), Eglot will ask for confirmation before
817performing edits initiated by the server or edits whose scope affects
818buffers other than the one where the user initiated the request.
819
820@item eglot-ignored-server-capabilities
821This variable's value is a list of language server capabilities that
822Eglot should not use. The default is @code{nil}: Eglot uses all of
823the capabilities supported by each server.
824
825@item eglot-extend-to-xref
826If this is non-@code{nil}, and @kbd{M-.}
827(@code{xref-find-definitions}) lands you in a file outside of your
828project, such as a system-installed library or header file,
829transiently consider that file as managed by the same language server.
830That file is still outside your project (i.e. @code{project-find-file}
831won't find it), but Eglot and the server will consider it to be part
832of the workspace. The default is @code{nil}.
833
834@item eglot-mode-map
835This variable is the keymap for binding Eglot-related command. It is
836in effect only as long as the buffer is managed by Eglot. By default,
837it is empty, with the single exception: @kbd{C-h .} is remapped to
838invoke @code{eldoc-doc-buffer}. You can bind additional commands in
839this map. For example:
840
841@lisp
842 (define-key eglot-mode-map (kbd "C-c r") 'eglot-rename)
843 (define-key eglot-mode-map (kbd "C-c o") 'eglot-code-action-organize-imports)
844 (define-key eglot-mode-map (kbd "C-c h") 'eldoc)
845 (define-key eglot-mode-map (kbd "<f6>") 'xref-find-definitions)
846@end lisp
847
848@end vtable
849
850Additional variables, which are relevant for customizing the server
851connections, are documented in @ref{Customizing Eglot}.
852
853@node Customizing Eglot
854@chapter Customizing Eglot
855@cindex customizing Eglot
856
857A large part of customizing Eglot to your needs and preferences should
858actually be done via options of the Emacs packages and features which
859Eglot supports and enhances (@pxref{Eglot Features}). For example:
860
861@itemize @bullet
862@item
863To configure the face used for server-derived errors and warnings,
864customize the Flymake faces @code{flymake-error} and
865@code{flymake-error}.
866
867@item
868To configure the amount of space taken up by documentation in the
869echo area, customize the ElDoc variable
870@code{eldoc-echo-area-use-multiline-p}.
871
872@item
873To completely change how ElDoc displays the at-point documentation
874destination, customize the ElDoc variable
875@code{eldoc-display-functions}.
876@end itemize
877
878For this reason, this manual describes only how to customize the
879Eglot's own operation, which mainly has to do with the server
880connections and the server features to be used by Eglot.
881
882@c @table, not @vtable, because some of the variables are indexed
883@c elsewhere
884@table @code
885@item eglot-server-programs
886This variable determines which language server to start for each
887supported major mode, and how to invoke that server's program.
888@xref{Setting Up LSP Servers}, for the details.
889
890@vindex eglot-strict-mode
891@item eglot-strict-mode
892This is @code{nil} by default, meaning that Eglot is generally lenient
893about non-conforming servers. If you need to debug a server, set this
894to @w{@code{(disallow-non-standard-keys enforce-required-keys)}}.
895
896@vindex eglot-server-initialized-hook
897@item eglot-server-initialized-hook
898A hook run after the server object is successfully initialized.
899
900@vindex eglot-connect-hook
901@item eglot-connect-hook
902A hook run after connection to the server is successfully
903established. @xref{Starting Eglot}.
904
905@item eglot-managed-mode-hook
906A hook run after Eglot started or stopped managing a buffer.
907@xref{Eglot and Buffers}, for details of its usage.
908
909@vindex eglot-stay-out-of
910@item eglot-stay-out-of
911This variable's value lists Emacs features that Eglot shouldn't
912automatically try to manage on user's behalf. It is useful, for
913example, when you need to use non-LSP Flymake or Company back-ends.
914To have Eglot stay away of some Emacs feature, add that feature's
915symbol or a regexp that will match a symbol's name to the list: for
916example, the symbol @code{xref} to leave Xref alone, or the string
917@samp{company} to stay away of your Company customizations. Here's an
918example:
919
920@lisp
921(add-to-list 'eglot-stay-out-of 'flymake)
922@end lisp
923
924Note that you can still configure the excluded Emacs features manually
925to use Eglot in your @code{eglot-managed-mode-hook} or via some other
926mechanism.
927
928@vindex eglot-workspace-configuration
929@cindex server workspace configuration
930@item eglot-workspace-configuration
931This variable is meant to be set in the @file{.dir-locals.el} file, to
932provide per-project settings, as described below in more detail.
933@end table
934
935Some language servers need to know project-specific settings, which
936the LSP calls @dfn{workspace configuration}. Eglot allows such fine
937tuning of per-project settings via the variable
938@code{eglot-workspace-configuration}. Eglot sends the portion of the
939settings contained in this variable to each server for which such
940settings were defined in the variable. These settings are
941communicated to the server initially (upon establishing the
942connection) or when the settings are changed, or in response to the
943configuration request from the server.
944
945In many cases, servers can be configured globally using a
946configuration file in the user's home directory or in the project
947directory, which the language server reads. For example, the
948@command{pylsp} server for Python reads the file
949@file{~/.config/pycodestyle} and the @command{clangd} server reads the
950file @file{.clangd} anywhere in the current project's directory tree.
951If possible, we recommend to use these configuration files that are
952independent of Eglot and Emacs; they have the advantage that they will
953work with other LSP clients as well.
954
955If you do need to provide Emacs-specific configuration for a language
956server, we recommend to define the appropriate value in the
957@file{.dir-locals.el} file in the project's directory. The value of
958this variable should be a property list of the following format:
959
960@lisp
961 (:@var{server} @var{plist}@dots{})
962@end lisp
963
964@noindent
965Here @code{:@var{server}} identifies a particular language server and
966@var{plist} is the corresponding keyword-value property list of one or
967more parameter settings for that server, serialized by Eglot as a JSON
968object. @var{plist} may be arbitrarity complex, generally containing
969other keywork-value property sublists corresponding to JSON subobjects.
970The JSON values @code{true}, @code{false}, @code{null} and @code{@{@}}
971are represented by the Lisp values @code{t}, @code{:json-false},
972@code{nil}, and @code{eglot-@{@}}, respectively.
973
974@findex eglot-show-workspace-configuration
975When experimenting with workspace settings, you can use the command
976@kbd{M-x eglot-show-workspace-configuration} to inspect and debug the
977JSON value to be sent to the server. This helper command works even
978before actually connecting to the server.
979
980Here's an example of defining the workspace-configuration settings for
981a project that uses two different language servers, one for Python,
982whose server is @command{pylsp}, the other one for Go, with
983@command{gopls} as its server (presumably, the project is written in a
984combination of these two languages):
985
986@lisp
987((python-mode
988 . ((eglot-workspace-configuration
989 . (:pylsp (:plugins (:jedi_completion (:include_params t
990 :fuzzy t)
991 :pylint (:enabled :json-false)))))))
992 (go-mode
993 . ((eglot-workspace-configuration
994 . (:gopls (:usePlaceholders t))))))
995@end lisp
996
997@noindent
998This should go into the @file{.dir-locals.el} file in the project's
999root directory. It sets up the value of
1000@code{eglot-workspace-configuration} separately for each major mode.
1001
1002Alternatively, the same configuration could be defined as follows:
1003
1004@lisp
1005((nil
1006 . ((eglot-workspace-configuration
1007 . (:pylsp (:plugins (:jedi_completion (:include_params t
1008 :fuzzy t)
1009 :pylint (:enabled :json-false)))
1010 :gopls (:usePlaceholders t))))))
1011@end lisp
1012
1013This is an equivalent setup which sets the value for all the
1014major-modes inside the project; Eglot will use for each server only
1015the section of the parameters intended for that server.
1016
1017As yet another alternative, you can set the value of
1018@code{eglot-workspace-configuration} programmatically, via the
1019@code{dir-locals-set-class-variables} function, @pxref{Directory Local
1020Variables,,, elisp, GNU Emacs Lisp Reference Manual}.
1021
1022Finally, if one needs to determine the workspace configuration based
1023on some dynamic context, @code{eglot-workspace-configuration} can be
1024set to a function. The function is called with the
1025@code{eglot-lsp-server} instance of the connected server (if any) and
1026with @code{default-directory} set to the root of the project. The
1027function should return a value of the form described above.
1028
1029Some servers need special hand-holding to operate correctly. If your
1030server has some quirks or non-conformity, it's possible to extend
1031Eglot via Elisp to adapt to it, by defining a suitable
1032@code{eglot-initialization-options} method via @code{cl-defmethod}
1033(@pxref{Generic Functions,,, elisp, GNU Emacs Lisp Reference Manual}).
1034
1035Here's an example:
1036
1037@lisp
1038(add-to-list 'eglot-server-programs
1039 '((c++ mode c-mode) . (eglot-cquery "cquery")))
1040
1041(defclass eglot-cquery (eglot-lsp-server) ()
1042 :documentation "A custom class for cquery's C/C++ langserver.")
1043
1044(cl-defmethod eglot-initialization-options ((server eglot-cquery))
1045 "Passes through required cquery initialization options"
1046 (let* ((root (car (project-roots (eglot--project server))))
1047 (cache (expand-file-name ".cquery_cached_index/" root)))
1048 (list :cacheDirectory (file-name-as-directory cache)
1049 :progressReportFrequencyMs -1)))
1050@end lisp
1051
1052@noindent
1053See the doc string of @code{eglot-initialization-options} for more
1054details.
1055@c FIXME: The doc string of eglot-initialization-options should be
1056@c enhanced and extended.
1057
1058@node Troubleshooting Eglot
1059@chapter Troubleshooting Eglot
1060@cindex troubleshooting Eglot
1061
1062This section documents commands and variables that can be used to
1063troubleshoot Eglot problems. It also provides guidelines for
1064reporting Eglot bugs in a way that facilitates their resolution.
1065
1066When you encounter problems with Eglot, try first using the commands
1067@kbd{M-x eglot-events-server} and @kbd{M-x eglot-stderr-buffer}. They
1068pop up special buffers that can be used to inspect the communications
1069between the Eglot and language server. In many cases, this will
1070indicate the problems or at least provide a hint.
1071
1072A common and easy-to-fix cause of performance problems is the length
1073of these two buffers. If Eglot is operating correctly but slowly,
1074customize the variable @code{eglot-events-buffer-size} (@pxref{Eglot
1075Variables}) to limit logging, and thus speed things up.
1076
1077If you need to report an Eglot bug, please keep in mind that, because
1078there are so many variables involved, it is generally both very
1079@emph{difficult} and @emph{absolutely essential} to reproduce bugs
1080exactly as they happened to you, the user. Therefore, every bug
1081report should include:
1082
1083@enumerate
1084@item
1085The transcript of events obtained from the buffer popped up by
1086@kbd{M-x eglot-events-buffer}. If the transcript can be narrowed down
1087to show the problematic exchange, so much the better. This is
1088invaluable for the investigation and reproduction of the problem.
1089
1090@item
1091If Emacs signaled an error (an error message was seen or heard), make
1092sure to repeat the process after toggling @code{debug-on-error} on
1093(via @kbd{M-x toggle-debug-on-error}). This normally produces a
1094backtrace of the error that should also be attached to the bug report.
1095
1096@item
1097An explanation how to obtain and install the language server you used.
1098If possible, try to replicate the problem with the C/C@t{++} or Python
1099servers, as these are very easy to install.
1100
1101@item
1102A description of how to setup the @emph{minimal} project (one or two
1103files and their contents) where the problem happens.
1104
1105@item
1106A recipe to replicate the problem with @emph{a clean Emacs run}. This
1107means @kbd{emacs -Q} invocation or a very minimal (no more that 10
1108lines) @file{.emacs} initialization file. @code{eglot-ensure} and
1109@code{use-package} calls are generally @emph{not} needed.
1110
1111@item
1112Make sure to double check all the above elements and re-run the
1113recipe to see that the problem is reproducible.
1114@end enumerate
1115
1116Please keep in mind that some problems reported against Eglot may
1117actually be bugs in the language server or the Emacs feature/package
1118that used Eglot to communicate with the language server.
1119
1120@node GNU Free Documentation License
1121@appendix GNU Free Documentation License
1122@include doclicense.texi
1123
1124@node Index
1125@unnumbered Index
1126@printindex cp
1127
1128@bye
1129
diff --git a/lisp/info-look.el b/lisp/info-look.el
index ce0a08dcbe6..2eec6f49f5c 100644
--- a/lisp/info-look.el
+++ b/lisp/info-look.el
@@ -1051,6 +1051,7 @@ Return nil if there is nothing appropriate in the buffer near point."
1051 ("eieio" "Function Index") 1051 ("eieio" "Function Index")
1052 ("gnutls" "(emacs-gnutls)Variable Index" "(emacs-gnutls)Function Index") 1052 ("gnutls" "(emacs-gnutls)Variable Index" "(emacs-gnutls)Function Index")
1053 ("mm" "(emacs-mime)Index") 1053 ("mm" "(emacs-mime)Index")
1054 ("eglot" "Index")
1054 ("epa" "Variable Index" "Function Index") 1055 ("epa" "Variable Index" "Function Index")
1055 ("ert" "Index") 1056 ("ert" "Index")
1056 ("eshell" "Function and Variable Index") 1057 ("eshell" "Function and Variable Index")
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
new file mode 100644
index 00000000000..0a7cb2a9aac
--- /dev/null
+++ b/lisp/progmodes/eglot.el
@@ -0,0 +1,3457 @@
1;;; eglot.el --- The Emacs Client for LSP servers -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2018-2022 Free Software Foundation, Inc.
4
5;; Version: 1.9
6;; Author: João Távora <joaotavora@gmail.com>
7;; Maintainer: João Távora <joaotavora@gmail.com>
8;; URL: https://github.com/joaotavora/eglot
9;; Keywords: convenience, languages
10;; Package-Requires: ((emacs "26.3") (jsonrpc "1.0.14") (flymake "1.2.1") (project "0.3.0") (xref "1.0.1") (eldoc "1.11.0") (seq "2.23"))
11
12;; This is is a GNU ELPA :core package. Avoid adding functionality
13;; that is not available in the version of Emacs recorded above or any
14;; of the package dependencies.
15
16;; This file is part of GNU Emacs.
17
18;; GNU Emacs is free software: you can redistribute it and/or modify
19;; it under the terms of the GNU General Public License as published by
20;; the Free Software Foundation, either version 3 of the License, or
21;; (at your option) any later version.
22
23;; GNU Emacs is distributed in the hope that it will be useful,
24;; but WITHOUT ANY WARRANTY; without even the implied warranty of
25;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26;; GNU General Public License for more details.
27
28;; You should have received a copy of the GNU General Public License
29;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
30
31;;; Commentary:
32
33;; Eglot ("Emacs Polyglot") is an Emacs LSP client that stays out of
34;; your way.
35;;
36;; Typing M-x eglot in some source file is often enough to get you
37;; started, if the language server you're looking to use is installed
38;; in your system. Please refer to the manual, available from
39;; https://joaotavora.github.io/eglot/ or from M-x info for more usage
40;; instructions.
41;;
42;; If you wish to contribute changes to Eglot, please do read the user
43;; manual first. Additionally, take the following in consideration:
44
45;; * Eglot's main job is to hook up the information that language
46;; servers offer via LSP to Emacs's UI facilities: Xref for
47;; definition-chasing, Flymake for diagnostics, Eldoc for at-point
48;; documentation, etc. Eglot's job is generally *not* to provide
49;; such a UI itself, though a small number of simple
50;; counter-examples do exist, for example in the `eglot-rename'
51;; command. When a new UI is evidently needed, consider adding a
52;; new package to Emacs, or extending an existing one.
53;;
54;; * Eglot was designed to function with just the UI facilities found
55;; in the latest Emacs core, as long as those facilities are also
56;; available as GNU ELPA :core packages. Historically, a number of
57;; :core packages were added or reworked in Emacs to make this
58;; possible. This principle should be upheld when adding new LSP
59;; features or tweaking exising ones. Design any new facilities in
60;; a way that they could work in the absence of LSP or using some
61;; different protocol, then make sure Eglot can link up LSP
62;; information to it.
63
64;; * There are few Eglot configuration variables. This principle
65;; should also be upheld. If Eglot had these variables, it could be
66;; duplicating configuration found elsewhere, bloating itself up,
67;; and making it generally hard to integrate with the ever growing
68;; set of LSP features and Emacs packages. For instance, this is
69;; why one finds a single variable
70;; `eglot-ignored-server-capabilities' instead of a number of
71;; capability-specific flags, or why customizing the display of
72;; LSP-provided documentation is done via ElDoc's variables, not
73;; Eglot's.
74;;
75;; * Linking up LSP information to other libraries is generally done
76;; in the `eglot--managed-mode' minor mode function, by
77;; buffer-locally setting the other library's variables to
78;; Eglot-specific versions. When deciding what to set the variable
79;; to, the general idea is to choose a good default for beginners
80;; that doesn't clash with Emacs's defaults. The settings are only
81;; in place during Eglot's LSP-enriched tenure over a project. Even
82;; so, some of those decisions will invariably aggravate a minority
83;; of Emacs power users, but these users can use `eglot-stay-out-of'
84;; and `eglot-managed-mode-hook' to quench their OCD.
85;;
86;; * On occasion, to enable new features, Eglot can have soft
87;; dependencies on popular libraries that are not in Emacs core.
88;; "Soft" means that the dependency doesn't impair any other use of
89;; Eglot beyond that feature. Such is the case of the snippet
90;; functionality, via the Yasnippet package, Markdown formatting of
91;; at-point documentation via the markdown-mode package, and nicer
92;; looking completions when the Company package is used.
93
94;;; Code:
95
96(require 'imenu)
97(require 'cl-lib)
98(require 'project)
99(require 'url-parse)
100(require 'url-util)
101(require 'pcase)
102(require 'compile) ; for some faces
103(require 'warnings)
104(require 'flymake)
105(require 'xref)
106(eval-when-compile
107 (require 'subr-x))
108(require 'jsonrpc)
109(require 'filenotify)
110(require 'ert)
111(require 'array)
112
113;; ElDoc is preloaded in Emacs, so `require'-ing won't guarantee we are
114;; using the latest version from GNU Elpa when we load eglot.el. Use an
115;; heuristic to see if we need to `load' it in Emacs < 28.
116(if (and (< emacs-major-version 28)
117 (not (boundp 'eldoc-documentation-strategy)))
118 (load "eldoc")
119 (require 'eldoc))
120
121;; Similar issue as above for Emacs 26.3 and seq.el.
122(if (< emacs-major-version 27)
123 (load "seq")
124 (require 'seq))
125
126;; forward-declare, but don't require (Emacs 28 doesn't seem to care)
127(defvar markdown-fontify-code-blocks-natively)
128(defvar company-backends)
129(defvar company-tooltip-align-annotations)
130
131
132
133;;; User tweakable stuff
134(defgroup eglot nil
135 "Interaction with Language Server Protocol servers."
136 :prefix "eglot-"
137 :group 'applications)
138
139(defun eglot-alternatives (alternatives)
140 "Compute server-choosing function for `eglot-server-programs'.
141Each element of ALTERNATIVES is a string PROGRAM or a list of
142strings (PROGRAM ARGS...) where program names an LSP server
143program to start with ARGS. Returns a function of one argument.
144When invoked, that function will return a list (ABSPATH ARGS),
145where ABSPATH is the absolute path of the PROGRAM that was
146chosen (interactively or automatically)."
147 (lambda (&optional interactive)
148 ;; JT@2021-06-13: This function is way more complicated than it
149 ;; could be because it accounts for the fact that
150 ;; `eglot--executable-find' may take much longer to execute on
151 ;; remote files.
152 (let* ((listified (cl-loop for a in alternatives
153 collect (if (listp a) a (list a))))
154 (err (lambda ()
155 (error "None of '%s' are valid executables"
156 (mapconcat #'car listified ", ")))))
157 (cond (interactive
158 (let* ((augmented (mapcar (lambda (a)
159 (let ((found (eglot--executable-find
160 (car a) t)))
161 (and found
162 (cons (car a) (cons found (cdr a))))))
163 listified))
164 (available (remove nil augmented)))
165 (cond ((cdr available)
166 (cdr (assoc
167 (completing-read
168 "[eglot] More than one server executable available:"
169 (mapcar #'car available)
170 nil t nil nil (car (car available)))
171 available #'equal)))
172 ((cdr (car available)))
173 (t
174 ;; Don't error when used interactively, let the
175 ;; Eglot prompt the user for alternative (github#719)
176 nil))))
177 (t
178 (cl-loop for (p . args) in listified
179 for probe = (eglot--executable-find p t)
180 when probe return (cons probe args)
181 finally (funcall err)))))))
182
183(defvar eglot-server-programs `((rust-mode . ,(eglot-alternatives '("rust-analyzer" "rls")))
184 (cmake-mode . ("cmake-language-server"))
185 (vimrc-mode . ("vim-language-server" "--stdio"))
186 (python-mode
187 . ,(eglot-alternatives
188 '("pylsp" "pyls" ("pyright-langserver" "--stdio") "jedi-language-server")))
189 ((js-mode typescript-mode)
190 . ("typescript-language-server" "--stdio"))
191 (sh-mode . ("bash-language-server" "start"))
192 ((php-mode phps-mode)
193 . ("php" "vendor/felixfbecker/\
194language-server/bin/php-language-server.php"))
195 ((c++-mode c-mode) . ,(eglot-alternatives
196 '("clangd" "ccls")))
197 (((caml-mode :language-id "ocaml")
198 (tuareg-mode :language-id "ocaml") reason-mode)
199 . ("ocamllsp"))
200 (ruby-mode
201 . ("solargraph" "socket" "--port" :autoport))
202 (haskell-mode
203 . ("haskell-language-server-wrapper" "--lsp"))
204 (elm-mode . ("elm-language-server"))
205 (mint-mode . ("mint" "ls"))
206 (kotlin-mode . ("kotlin-language-server"))
207 (go-mode . ("gopls"))
208 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
209 "languageserver::run()"))
210 (java-mode . ("jdtls"))
211 (dart-mode . ("dart" "language-server"
212 "--client-id" "emacs.eglot-dart"))
213 (elixir-mode . ("language_server.sh"))
214 (ada-mode . ("ada_language_server"))
215 (scala-mode . ("metals-emacs"))
216 (racket-mode . ("racket" "-l" "racket-langserver"))
217 ((tex-mode context-mode texinfo-mode bibtex-mode)
218 . ("digestif"))
219 (erlang-mode . ("erlang_ls" "--transport" "stdio"))
220 (yaml-mode . ("yaml-language-server" "--stdio"))
221 (nix-mode . ("rnix-lsp"))
222 (gdscript-mode . ("localhost" 6008))
223 ((fortran-mode f90-mode) . ("fortls"))
224 (futhark-mode . ("futhark" "lsp"))
225 (lua-mode . ("lua-lsp"))
226 (zig-mode . ("zls"))
227 (css-mode . ,(eglot-alternatives '(("vscode-css-language-server" "--stdio") ("css-languageserver" "--stdio"))))
228 (html-mode . ,(eglot-alternatives '(("vscode-html-language-server" "--stdio") ("html-languageserver" "--stdio"))))
229 (json-mode . ,(eglot-alternatives '(("vscode-json-language-server" "--stdio") ("json-languageserver" "--stdio"))))
230 (dockerfile-mode . ("docker-langserver" "--stdio"))
231 ((clojure-mode clojurescript-mode clojurec-mode)
232 . ("clojure-lsp"))
233 (csharp-mode . ("omnisharp" "-lsp"))
234 (purescript-mode . ("purescript-language-server" "--stdio"))
235 (perl-mode . ("perl" "-MPerl::LanguageServer" "-e" "Perl::LanguageServer::run"))
236 (markdown-mode . ("marksman" "server")))
237 "How the command `eglot' guesses the server to start.
238An association list of (MAJOR-MODE . CONTACT) pairs. MAJOR-MODE
239identifies the buffers that are to be managed by a specific
240language server. The associated CONTACT specifies how to connect
241to a server for those buffers.
242
243MAJOR-MODE can be:
244
245* In the most common case, a symbol such as `c-mode';
246
247* A list (MAJOR-MODE-SYMBOL :LANGUAGE-ID ID) where
248 MAJOR-MODE-SYMBOL is the aforementioned symbol and ID is a
249 string identifying the language to the server;
250
251* A list combining the previous two alternatives, meaning
252 multiple major modes will be associated with a single server
253 program. This association is such that the same resulting
254 server process will manage buffers of different major modes.
255
256CONTACT can be:
257
258* In the most common case, a list of strings (PROGRAM [ARGS...]).
259 PROGRAM is called with ARGS and is expected to serve LSP requests
260 over the standard input/output channels.
261
262* A list (PROGRAM [ARGS...] :initializationOptions OPTIONS),
263 whereupon PROGRAM is called with ARGS as in the first option,
264 and the LSP \"initializationOptions\" JSON object is
265 constructed from OPTIONS. If OPTIONS is a unary function, it
266 is called with the server instance and should return a JSON
267 object.
268
269* A list (HOST PORT [TCP-ARGS...]) where HOST is a string and
270 PORT is a positive integer for connecting to a server via TCP.
271 Remaining ARGS are passed to `open-network-stream' for
272 upgrading the connection with encryption or other capabilities.
273
274* A list (PROGRAM [ARGS...] :autoport [MOREARGS...]), whereupon a
275 combination of previous options is used. First, an attempt is
276 made to find an available server port, then PROGRAM is launched
277 with ARGS; the `:autoport' keyword substituted for that number;
278 and MOREARGS. Eglot then attempts to establish a TCP
279 connection to that port number on the localhost.
280
281* A cons (CLASS-NAME . INITARGS) where CLASS-NAME is a symbol
282 designating a subclass of `eglot-lsp-server', for representing
283 experimental LSP servers. INITARGS is a keyword-value plist
284 used to initialize the object of CLASS-NAME, or a plain list
285 interpreted as the previous descriptions of CONTACT. In the
286 latter case that plain list is used to produce a plist with a
287 suitable :PROCESS initarg to CLASS-NAME. The class
288 `eglot-lsp-server' descends from `jsonrpc-process-connection',
289 which you should see for the semantics of the mandatory
290 :PROCESS argument.
291
292* A function of a single argument producing any of the above
293 values for CONTACT. The argument's value is non-nil if the
294 connection was requested interactively (e.g. from the `eglot'
295 command), and nil if it wasn't (e.g. from `eglot-ensure'). If
296 the call is interactive, the function can ask the user for
297 hints on finding the required programs, etc. Otherwise, it
298 should not ask the user for any input, and return nil or signal
299 an error if it can't produce a valid CONTACT.")
300
301(defface eglot-highlight-symbol-face
302 '((t (:inherit bold)))
303 "Face used to highlight the symbol at point.")
304
305(defface eglot-mode-line
306 '((t (:inherit font-lock-constant-face :weight bold)))
307 "Face for package-name in Eglot's mode line.")
308
309(defface eglot-diagnostic-tag-unnecessary-face
310 '((t (:inherit shadow)))
311 "Face used to render unused or unnecessary code.")
312
313(defface eglot-diagnostic-tag-deprecated-face
314 '((t . (:inherit shadow :strike-through t)))
315 "Face used to render deprecated or obsolete code.")
316
317(defcustom eglot-autoreconnect 3
318 "Control ability to reconnect automatically to the LSP server.
319If t, always reconnect automatically (not recommended). If nil,
320never reconnect automatically after unexpected server shutdowns,
321crashes or network failures. A positive integer number says to
322only autoreconnect if the previous successful connection attempt
323lasted more than that many seconds."
324 :type '(choice (boolean :tag "Whether to inhibit autoreconnection")
325 (integer :tag "Number of seconds")))
326
327(defcustom eglot-connect-timeout 30
328 "Number of seconds before timing out LSP connection attempts.
329If nil, never time out."
330 :type 'number)
331
332(defcustom eglot-sync-connect 3
333 "Control blocking of LSP connection attempts.
334If t, block for `eglot-connect-timeout' seconds. A positive
335integer number means block for that many seconds, and then wait
336for the connection in the background. nil has the same meaning
337as 0, i.e. don't block at all."
338 :type '(choice (boolean :tag "Whether to inhibit autoreconnection")
339 (integer :tag "Number of seconds")))
340
341(defcustom eglot-autoshutdown nil
342 "If non-nil, shut down server after killing last managed buffer."
343 :type 'boolean)
344
345(defcustom eglot-send-changes-idle-time 0.5
346 "Don't tell server of changes before Emacs's been idle for this many seconds."
347 :type 'number)
348
349(defcustom eglot-events-buffer-size 2000000
350 "Control the size of the Eglot events buffer.
351If a number, don't let the buffer grow larger than that many
352characters. If 0, don't use an event's buffer at all. If nil,
353let the buffer grow forever.
354
355For changes on this variable to take effect on a connection
356already started, you need to restart the connection. That can be
357done by `eglot-reconnect'."
358 :type '(choice (const :tag "No limit" nil)
359 (integer :tag "Number of characters")))
360
361(defcustom eglot-confirm-server-initiated-edits 'confirm
362 "Non-nil if server-initiated edits should be confirmed with user."
363 :type '(choice (const :tag "Don't show confirmation prompt" nil)
364 (symbol :tag "Show confirmation prompt" 'confirm)))
365
366(defcustom eglot-extend-to-xref nil
367 "If non-nil, activate Eglot in cross-referenced non-project files."
368 :type 'boolean)
369
370(defcustom eglot-menu-string "eglot"
371 "String displayed in mode line when Eglot is active."
372 :type 'string)
373
374(defvar eglot-withhold-process-id nil
375 "If non-nil, Eglot will not send the Emacs process id to the language server.
376This can be useful when using docker to run a language server.")
377
378;; Customizable via `completion-category-overrides'.
379(when (assoc 'flex completion-styles-alist)
380 (add-to-list 'completion-category-defaults '(eglot (styles flex basic))))
381
382
383;;; Constants
384;;;
385(defconst eglot--symbol-kind-names
386 `((1 . "File") (2 . "Module")
387 (3 . "Namespace") (4 . "Package") (5 . "Class")
388 (6 . "Method") (7 . "Property") (8 . "Field")
389 (9 . "Constructor") (10 . "Enum") (11 . "Interface")
390 (12 . "Function") (13 . "Variable") (14 . "Constant")
391 (15 . "String") (16 . "Number") (17 . "Boolean")
392 (18 . "Array") (19 . "Object") (20 . "Key")
393 (21 . "Null") (22 . "EnumMember") (23 . "Struct")
394 (24 . "Event") (25 . "Operator") (26 . "TypeParameter")))
395
396(defconst eglot--kind-names
397 `((1 . "Text") (2 . "Method") (3 . "Function") (4 . "Constructor")
398 (5 . "Field") (6 . "Variable") (7 . "Class") (8 . "Interface")
399 (9 . "Module") (10 . "Property") (11 . "Unit") (12 . "Value")
400 (13 . "Enum") (14 . "Keyword") (15 . "Snippet") (16 . "Color")
401 (17 . "File") (18 . "Reference") (19 . "Folder") (20 . "EnumMember")
402 (21 . "Constant") (22 . "Struct") (23 . "Event") (24 . "Operator")
403 (25 . "TypeParameter")))
404
405(defconst eglot--tag-faces
406 `((1 . eglot-diagnostic-tag-unnecessary-face)
407 (2 . eglot-diagnostic-tag-deprecated-face)))
408
409(defvaralias 'eglot-{} 'eglot--{})
410(defconst eglot--{} (make-hash-table :size 1) "The empty JSON object.")
411
412(defun eglot--executable-find (command &optional remote)
413 "Like Emacs 27's `executable-find', ignore REMOTE on Emacs 26."
414 (if (>= emacs-major-version 27) (executable-find command remote)
415 (executable-find command)))
416
417
418;;; Message verification helpers
419;;;
420(eval-and-compile
421 (defvar eglot--lsp-interface-alist
422 `(
423 (CodeAction (:title) (:kind :diagnostics :edit :command :isPreferred))
424 (ConfigurationItem () (:scopeUri :section))
425 (Command ((:title . string) (:command . string)) (:arguments))
426 (CompletionItem (:label)
427 (:kind :detail :documentation :deprecated :preselect
428 :sortText :filterText :insertText :insertTextFormat
429 :textEdit :additionalTextEdits :commitCharacters
430 :command :data :tags))
431 (Diagnostic (:range :message) (:severity :code :source :relatedInformation :codeDescription :tags))
432 (DocumentHighlight (:range) (:kind))
433 (FileSystemWatcher (:globPattern) (:kind))
434 (Hover (:contents) (:range))
435 (InitializeResult (:capabilities) (:serverInfo))
436 (Location (:uri :range))
437 (LocationLink (:targetUri :targetRange :targetSelectionRange) (:originSelectionRange))
438 (LogMessageParams (:type :message))
439 (MarkupContent (:kind :value))
440 (ParameterInformation (:label) (:documentation))
441 (Position (:line :character))
442 (Range (:start :end))
443 (Registration (:id :method) (:registerOptions))
444 (ResponseError (:code :message) (:data))
445 (ShowMessageParams (:type :message))
446 (ShowMessageRequestParams (:type :message) (:actions))
447 (SignatureHelp (:signatures) (:activeSignature :activeParameter))
448 (SignatureInformation (:label) (:documentation :parameters :activeParameter))
449 (SymbolInformation (:name :kind :location)
450 (:deprecated :containerName))
451 (DocumentSymbol (:name :range :selectionRange :kind)
452 ;; `:containerName' isn't really allowed , but
453 ;; it simplifies the impl of `eglot-imenu'.
454 (:detail :deprecated :children :containerName))
455 (TextDocumentEdit (:textDocument :edits) ())
456 (TextEdit (:range :newText))
457 (VersionedTextDocumentIdentifier (:uri :version) ())
458 (WorkspaceEdit () (:changes :documentChanges))
459 (WorkspaceSymbol (:name :kind) (:containerName :location :data)))
460 "Alist (INTERFACE-NAME . INTERFACE) of known external LSP interfaces.
461
462INTERFACE-NAME is a symbol designated by the spec as
463\"interface\". INTERFACE is a list (REQUIRED OPTIONAL) where
464REQUIRED and OPTIONAL are lists of KEYWORD designating field
465names that must be, or may be, respectively, present in a message
466adhering to that interface. KEY can be a keyword or a cons (SYM
467TYPE), where type is used by `cl-typep' to check types at
468runtime.
469
470Here's what an element of this alist might look like:
471
472 (Command ((:title . string) (:command . string)) (:arguments))"))
473
474(eval-and-compile
475 (defvar eglot-strict-mode
476 '(;; Uncomment next lines for fun and debugging
477 ;; disallow-non-standard-keys
478 ;; enforce-required-keys
479 ;; enforce-optional-keys
480 )
481 "How strictly to check LSP interfaces at compile- and run-time.
482
483Value is a list of symbols (if the list is empty, no checks are
484performed).
485
486If the symbol `disallow-non-standard-keys' is present, an error
487is raised if any extraneous fields are sent by the server. At
488compile-time, a warning is raised if a destructuring spec
489includes such a field.
490
491If the symbol `enforce-required-keys' is present, an error is
492raised if any required fields are missing from the message sent
493from the server. At compile-time, a warning is raised if a
494destructuring spec doesn't use such a field.
495
496If the symbol `enforce-optional-keys' is present, nothing special
497happens at run-time. At compile-time, a warning is raised if a
498destructuring spec doesn't use all optional fields.
499
500If the symbol `disallow-unknown-methods' is present, Eglot warns
501on unknown notifications and errors on unknown requests."))
502
503(cl-defun eglot--check-object (interface-name
504 object
505 &optional
506 (enforce-required t)
507 (disallow-non-standard t)
508 (check-types t))
509 "Check that OBJECT conforms to INTERFACE. Error otherwise."
510 (cl-destructuring-bind
511 (&key types required-keys optional-keys &allow-other-keys)
512 (eglot--interface interface-name)
513 (when-let ((missing (and enforce-required
514 (cl-set-difference required-keys
515 (eglot--plist-keys object)))))
516 (eglot--error "A `%s' must have %s" interface-name missing))
517 (when-let ((excess (and disallow-non-standard
518 (cl-set-difference
519 (eglot--plist-keys object)
520 (append required-keys optional-keys)))))
521 (eglot--error "A `%s' mustn't have %s" interface-name excess))
522 (when check-types
523 (cl-loop
524 for (k v) on object by #'cddr
525 for type = (or (cdr (assoc k types)) t) ;; FIXME: enforce nil type?
526 unless (cl-typep v type)
527 do (eglot--error "A `%s' must have a %s as %s, but has %s"
528 interface-name )))
529 t))
530
531(eval-and-compile
532 (defun eglot--keywordize-vars (vars)
533 (mapcar (lambda (var) (intern (format ":%s" var))) vars))
534
535 (defun eglot--ensure-type (k) (if (consp k) k (cons k t)))
536
537 (defun eglot--interface (interface-name)
538 (let* ((interface (assoc interface-name eglot--lsp-interface-alist))
539 (required (mapcar #'eglot--ensure-type (car (cdr interface))))
540 (optional (mapcar #'eglot--ensure-type (cadr (cdr interface)))))
541 (list :types (append required optional)
542 :required-keys (mapcar #'car required)
543 :optional-keys (mapcar #'car optional))))
544
545 (defun eglot--check-dspec (interface-name dspec)
546 "Check destructuring spec DSPEC against INTERFACE-NAME."
547 (cl-destructuring-bind (&key required-keys optional-keys &allow-other-keys)
548 (eglot--interface interface-name)
549 (cond ((or required-keys optional-keys)
550 (let ((too-many
551 (and
552 (memq 'disallow-non-standard-keys eglot-strict-mode)
553 (cl-set-difference
554 (eglot--keywordize-vars dspec)
555 (append required-keys optional-keys))))
556 (ignored-required
557 (and
558 (memq 'enforce-required-keys eglot-strict-mode)
559 (cl-set-difference
560 required-keys (eglot--keywordize-vars dspec))))
561 (missing-out
562 (and
563 (memq 'enforce-optional-keys eglot-strict-mode)
564 (cl-set-difference
565 optional-keys (eglot--keywordize-vars dspec)))))
566 (when too-many (byte-compile-warn
567 "Destructuring for %s has extraneous %s"
568 interface-name too-many))
569 (when ignored-required (byte-compile-warn
570 "Destructuring for %s ignores required %s"
571 interface-name ignored-required))
572 (when missing-out (byte-compile-warn
573 "Destructuring for %s is missing out on %s"
574 interface-name missing-out))))
575 (t
576 (byte-compile-warn "Unknown LSP interface %s" interface-name))))))
577
578(cl-defmacro eglot--dbind (vars object &body body)
579 "Destructure OBJECT, binding VARS in BODY.
580VARS is ([(INTERFACE)] SYMS...)
581Honour `eglot-strict-mode'."
582 (declare (indent 2) (debug (sexp sexp &rest form)))
583 (let ((interface-name (if (consp (car vars))
584 (car (pop vars))))
585 (object-once (make-symbol "object-once"))
586 (fn-once (make-symbol "fn-once")))
587 (cond (interface-name
588 (eglot--check-dspec interface-name vars)
589 `(let ((,object-once ,object))
590 (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once
591 (eglot--check-object ',interface-name ,object-once
592 (memq 'enforce-required-keys eglot-strict-mode)
593 (memq 'disallow-non-standard-keys eglot-strict-mode)
594 (memq 'check-types eglot-strict-mode))
595 ,@body)))
596 (t
597 `(let ((,object-once ,object)
598 (,fn-once (lambda (,@vars) ,@body)))
599 (if (memq 'disallow-non-standard-keys eglot-strict-mode)
600 (cl-destructuring-bind (&key ,@vars) ,object-once
601 (funcall ,fn-once ,@vars))
602 (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once
603 (funcall ,fn-once ,@vars))))))))
604
605
606(cl-defmacro eglot--lambda (cl-lambda-list &body body)
607 "Function of args CL-LAMBDA-LIST for processing INTERFACE objects.
608Honour `eglot-strict-mode'."
609 (declare (indent 1) (debug (sexp &rest form)))
610 (let ((e (cl-gensym "jsonrpc-lambda-elem")))
611 `(lambda (,e) (eglot--dbind ,cl-lambda-list ,e ,@body))))
612
613(cl-defmacro eglot--dcase (obj &rest clauses)
614 "Like `pcase', but for the LSP object OBJ.
615CLAUSES is a list (DESTRUCTURE FORMS...) where DESTRUCTURE is
616treated as in `eglot-dbind'."
617 (declare (indent 1) (debug (sexp &rest (sexp &rest form))))
618 (let ((obj-once (make-symbol "obj-once")))
619 `(let ((,obj-once ,obj))
620 (cond
621 ,@(cl-loop
622 for (vars . body) in clauses
623 for vars-as-keywords = (eglot--keywordize-vars vars)
624 for interface-name = (if (consp (car vars))
625 (car (pop vars)))
626 for condition =
627 (cond (interface-name
628 (eglot--check-dspec interface-name vars)
629 ;; In this mode, in runtime, we assume
630 ;; `eglot-strict-mode' is partially on, otherwise we
631 ;; can't disambiguate between certain types.
632 `(ignore-errors
633 (eglot--check-object
634 ',interface-name ,obj-once
635 t
636 (memq 'disallow-non-standard-keys eglot-strict-mode)
637 t)))
638 (t
639 ;; In this interface-less mode we don't check
640 ;; `eglot-strict-mode' at all: just check that the object
641 ;; has all the keys the user wants to destructure.
642 `(null (cl-set-difference
643 ',vars-as-keywords
644 (eglot--plist-keys ,obj-once)))))
645 collect `(,condition
646 (cl-destructuring-bind (&key ,@vars &allow-other-keys)
647 ,obj-once
648 ,@body)))
649 (t
650 (eglot--error "%S didn't match any of %S"
651 ,obj-once
652 ',(mapcar #'car clauses)))))))
653
654
655;;; API (WORK-IN-PROGRESS!)
656;;;
657(cl-defmacro eglot--when-live-buffer (buf &rest body)
658 "Check BUF live, then do BODY in it." (declare (indent 1) (debug t))
659 (let ((b (cl-gensym)))
660 `(let ((,b ,buf)) (if (buffer-live-p ,b) (with-current-buffer ,b ,@body)))))
661
662(cl-defmacro eglot--when-buffer-window (buf &body body)
663 "Check BUF showing somewhere, then do BODY in it." (declare (indent 1) (debug t))
664 (let ((b (cl-gensym)))
665 `(let ((,b ,buf))
666 ;;notice the exception when testing with `ert'
667 (when (or (get-buffer-window ,b) (ert-running-test))
668 (with-current-buffer ,b ,@body)))))
669
670(cl-defmacro eglot--widening (&rest body)
671 "Save excursion and restriction. Widen. Then run BODY." (declare (debug t))
672 `(save-excursion (save-restriction (widen) ,@body)))
673
674(cl-defgeneric eglot-handle-request (server method &rest params)
675 "Handle SERVER's METHOD request with PARAMS.")
676
677(cl-defgeneric eglot-handle-notification (server method &rest params)
678 "Handle SERVER's METHOD notification with PARAMS.")
679
680(cl-defgeneric eglot-execute-command (server command arguments)
681 "Ask SERVER to execute COMMAND with ARGUMENTS.")
682
683(cl-defgeneric eglot-initialization-options (server)
684 "JSON object to send under `initializationOptions'."
685 (:method (s)
686 (let ((probe (plist-get (eglot--saved-initargs s) :initializationOptions)))
687 (cond ((functionp probe) (funcall probe s))
688 (probe)
689 (t eglot--{})))))
690
691(cl-defgeneric eglot-register-capability (server method id &rest params)
692 "Ask SERVER to register capability METHOD marked with ID."
693 (:method
694 (_s method _id &rest _params)
695 (eglot--warn "Server tried to register unsupported capability `%s'"
696 method)))
697
698(cl-defgeneric eglot-unregister-capability (server method id &rest params)
699 "Ask SERVER to register capability METHOD marked with ID."
700 (:method
701 (_s method _id &rest _params)
702 (eglot--warn "Server tried to unregister unsupported capability `%s'"
703 method)))
704
705(cl-defgeneric eglot-client-capabilities (server)
706 "What the Eglot LSP client supports for SERVER."
707 (:method (s)
708 (list
709 :workspace (list
710 :applyEdit t
711 :executeCommand `(:dynamicRegistration :json-false)
712 :workspaceEdit `(:documentChanges t)
713 :didChangeWatchedFiles
714 `(:dynamicRegistration
715 ,(if (eglot--trampish-p s) :json-false t))
716 :symbol `(:dynamicRegistration :json-false)
717 :configuration t
718 :workspaceFolders t)
719 :textDocument
720 (list
721 :synchronization (list
722 :dynamicRegistration :json-false
723 :willSave t :willSaveWaitUntil t :didSave t)
724 :completion (list :dynamicRegistration :json-false
725 :completionItem
726 `(:snippetSupport
727 ,(if (eglot--snippet-expansion-fn)
728 t
729 :json-false)
730 :deprecatedSupport t
731 :tagSupport (:valueSet [1]))
732 :contextSupport t)
733 :hover (list :dynamicRegistration :json-false
734 :contentFormat
735 (if (fboundp 'gfm-view-mode)
736 ["markdown" "plaintext"]
737 ["plaintext"]))
738 :signatureHelp (list :dynamicRegistration :json-false
739 :signatureInformation
740 `(:parameterInformation
741 (:labelOffsetSupport t)
742 :activeParameterSupport t))
743 :references `(:dynamicRegistration :json-false)
744 :definition (list :dynamicRegistration :json-false
745 :linkSupport t)
746 :declaration (list :dynamicRegistration :json-false
747 :linkSupport t)
748 :implementation (list :dynamicRegistration :json-false
749 :linkSupport t)
750 :typeDefinition (list :dynamicRegistration :json-false
751 :linkSupport t)
752 :documentSymbol (list
753 :dynamicRegistration :json-false
754 :hierarchicalDocumentSymbolSupport t
755 :symbolKind `(:valueSet
756 [,@(mapcar
757 #'car eglot--symbol-kind-names)]))
758 :documentHighlight `(:dynamicRegistration :json-false)
759 :codeAction (list
760 :dynamicRegistration :json-false
761 :codeActionLiteralSupport
762 '(:codeActionKind
763 (:valueSet
764 ["quickfix"
765 "refactor" "refactor.extract"
766 "refactor.inline" "refactor.rewrite"
767 "source" "source.organizeImports"]))
768 :isPreferredSupport t)
769 :formatting `(:dynamicRegistration :json-false)
770 :rangeFormatting `(:dynamicRegistration :json-false)
771 :rename `(:dynamicRegistration :json-false)
772 :publishDiagnostics (list :relatedInformation :json-false
773 ;; TODO: We can support :codeDescription after
774 ;; adding an appropriate UI to
775 ;; Flymake.
776 :codeDescriptionSupport :json-false
777 :tagSupport
778 `(:valueSet
779 [,@(mapcar
780 #'car eglot--tag-faces)])))
781 :experimental eglot--{})))
782
783(cl-defgeneric eglot-workspace-folders (server)
784 "Return workspaceFolders for SERVER."
785 (let ((project (eglot--project server)))
786 (vconcat
787 (mapcar (lambda (dir)
788 (list :uri (eglot--path-to-uri dir)
789 :name (abbreviate-file-name dir)))
790 `(,(project-root project) ,@(project-external-roots project))))))
791
792(defclass eglot-lsp-server (jsonrpc-process-connection)
793 ((project-nickname
794 :documentation "Short nickname for the associated project."
795 :accessor eglot--project-nickname
796 :reader eglot-project-nickname)
797 (major-modes
798 :documentation "Major modes server is responsible for in a given project."
799 :accessor eglot--major-modes)
800 (language-id
801 :documentation "Language ID string for the mode."
802 :accessor eglot--language-id)
803 (capabilities
804 :documentation "JSON object containing server capabilities."
805 :accessor eglot--capabilities)
806 (server-info
807 :documentation "JSON object containing server info."
808 :accessor eglot--server-info)
809 (shutdown-requested
810 :documentation "Flag set when server is shutting down."
811 :accessor eglot--shutdown-requested)
812 (project
813 :documentation "Project associated with server."
814 :accessor eglot--project)
815 (spinner
816 :documentation "List (ID DOING-WHAT DONE-P) representing server progress."
817 :initform `(nil nil t) :accessor eglot--spinner)
818 (inhibit-autoreconnect
819 :initform t
820 :documentation "Generalized boolean inhibiting auto-reconnection if true."
821 :accessor eglot--inhibit-autoreconnect)
822 (file-watches
823 :documentation "Map ID to list of WATCHES for `didChangeWatchedFiles'."
824 :initform (make-hash-table :test #'equal) :accessor eglot--file-watches)
825 (managed-buffers
826 :documentation "List of buffers managed by server."
827 :accessor eglot--managed-buffers)
828 (saved-initargs
829 :documentation "Saved initargs for reconnection purposes."
830 :accessor eglot--saved-initargs)
831 (inferior-process
832 :documentation "Server subprocess started automatically."
833 :accessor eglot--inferior-process))
834 :documentation
835 "Represents a server. Wraps a process for LSP communication.")
836
837(cl-defmethod initialize-instance :before ((_server eglot-lsp-server) &optional args)
838 (cl-remf args :initializationOptions))
839
840
841;;; Process management
842(defvar eglot--servers-by-project (make-hash-table :test #'equal)
843 "Keys are projects. Values are lists of processes.")
844
845(defun eglot-shutdown (server &optional _interactive timeout preserve-buffers)
846 "Politely ask SERVER to quit.
847Interactively, read SERVER from the minibuffer unless there is
848only one and it's managing the current buffer.
849
850Forcefully quit it if it doesn't respond within TIMEOUT seconds.
851TIMEOUT defaults to 1.5 seconds. Don't leave this function with
852the server still running.
853
854If PRESERVE-BUFFERS is non-nil (interactively, when called with a
855prefix argument), do not kill events and output buffers of
856SERVER."
857 (interactive (list (eglot--read-server "Shutdown which server"
858 (eglot-current-server))
859 t nil current-prefix-arg))
860 (eglot--message "Asking %s politely to terminate" (jsonrpc-name server))
861 (unwind-protect
862 (progn
863 (setf (eglot--shutdown-requested server) t)
864 (jsonrpc-request server :shutdown nil :timeout (or timeout 1.5))
865 (jsonrpc-notify server :exit nil))
866 ;; Now ask jsonrpc.el to shut down the server.
867 (jsonrpc-shutdown server (not preserve-buffers))
868 (unless preserve-buffers (kill-buffer (jsonrpc-events-buffer server)))))
869
870(defun eglot-shutdown-all (&optional preserve-buffers)
871 "Politely ask all language servers to quit, in order.
872PRESERVE-BUFFERS as in `eglot-shutdown', which see."
873 (interactive (list current-prefix-arg))
874 (cl-loop for ss being the hash-values of eglot--servers-by-project
875 do (cl-loop for s in ss do (eglot-shutdown s nil preserve-buffers))))
876
877(defun eglot--on-shutdown (server)
878 "Called by jsonrpc.el when SERVER is already dead."
879 ;; Turn off `eglot--managed-mode' where appropriate.
880 (dolist (buffer (eglot--managed-buffers server))
881 (let (;; Avoid duplicate shutdowns (github#389)
882 (eglot-autoshutdown nil))
883 (eglot--when-live-buffer buffer (eglot--managed-mode-off))))
884 ;; Kill any expensive watches
885 (maphash (lambda (_id watches)
886 (mapcar #'file-notify-rm-watch watches))
887 (eglot--file-watches server))
888 ;; Kill any autostarted inferior processes
889 (when-let (proc (eglot--inferior-process server))
890 (delete-process proc))
891 ;; Sever the project/server relationship for `server'
892 (setf (gethash (eglot--project server) eglot--servers-by-project)
893 (delq server
894 (gethash (eglot--project server) eglot--servers-by-project)))
895 (cond ((eglot--shutdown-requested server)
896 t)
897 ((not (eglot--inhibit-autoreconnect server))
898 (eglot--warn "Reconnecting after unexpected server exit.")
899 (eglot-reconnect server))
900 ((timerp (eglot--inhibit-autoreconnect server))
901 (eglot--warn "Not auto-reconnecting, last one didn't last long."))))
902
903(defun eglot--all-major-modes ()
904 "Return all known major modes."
905 (let ((retval))
906 (mapatoms (lambda (sym)
907 (when (plist-member (symbol-plist sym) 'derived-mode-parent)
908 (push sym retval))))
909 retval))
910
911(defvar eglot--command-history nil
912 "History of CONTACT arguments to `eglot'.")
913
914(defun eglot--lookup-mode (mode)
915 "Lookup `eglot-server-programs' for MODE.
916Return (MANAGED-MODES LANGUAGE-ID CONTACT-PROXY).
917
918MANAGED-MODES is a list with MODE as its first elements.
919Subsequent elements are other major modes also potentially
920managed by the server that is to manage MODE.
921
922If not specified in `eglot-server-programs' (which see),
923LANGUAGE-ID is determined from MODE's name.
924
925CONTACT-PROXY is the value of the corresponding
926`eglot-server-programs' entry."
927 (cl-loop
928 for (modes . contact) in eglot-server-programs
929 for mode-symbols = (cons mode
930 (delete mode
931 (mapcar #'car
932 (mapcar #'eglot--ensure-list
933 (eglot--ensure-list modes)))))
934 thereis (cl-some
935 (lambda (spec)
936 (cl-destructuring-bind (probe &key language-id &allow-other-keys)
937 (eglot--ensure-list spec)
938 (and (provided-mode-derived-p mode probe)
939 (list
940 mode-symbols
941 (or language-id
942 (or (get mode 'eglot-language-id)
943 (get spec 'eglot-language-id)
944 (string-remove-suffix "-mode" (symbol-name mode))))
945 contact))))
946 (if (or (symbolp modes) (keywordp (cadr modes)))
947 (list modes) modes))))
948
949(defun eglot--guess-contact (&optional interactive)
950 "Helper for `eglot'.
951Return (MANAGED-MODE PROJECT CLASS CONTACT LANG-ID). If INTERACTIVE is
952non-nil, maybe prompt user, else error as soon as something can't
953be guessed."
954 (let* ((guessed-mode (if buffer-file-name major-mode))
955 (main-mode
956 (cond
957 ((and interactive
958 (or (>= (prefix-numeric-value current-prefix-arg) 16)
959 (not guessed-mode)))
960 (intern
961 (completing-read
962 "[eglot] Start a server to manage buffers of what major mode? "
963 (mapcar #'symbol-name (eglot--all-major-modes)) nil t
964 (symbol-name guessed-mode) nil (symbol-name guessed-mode) nil)))
965 ((not guessed-mode)
966 (eglot--error "Can't guess mode to manage for `%s'" (current-buffer)))
967 (t guessed-mode)))
968 (triplet (eglot--lookup-mode main-mode))
969 (managed-modes (car triplet))
970 (language-id (or (cadr triplet)
971 (string-remove-suffix "-mode" (symbol-name guessed-mode))))
972 (guess (caddr triplet))
973 (guess (if (functionp guess)
974 (funcall guess interactive)
975 guess))
976 (class (or (and (consp guess) (symbolp (car guess))
977 (prog1 (unless current-prefix-arg (car guess))
978 (setq guess (cdr guess))))
979 'eglot-lsp-server))
980 (program (and (listp guess)
981 (stringp (car guess))
982 ;; A second element might be the port of a (host, port)
983 ;; pair, but in that case it is not a string.
984 (or (null (cdr guess)) (stringp (cadr guess)))
985 (car guess)))
986 (base-prompt
987 (and interactive
988 "Enter program to execute (or <host>:<port>): "))
989 (full-program-invocation
990 (and program
991 (cl-every #'stringp guess)
992 (combine-and-quote-strings guess)))
993 (prompt
994 (and base-prompt
995 (cond (current-prefix-arg base-prompt)
996 ((null guess)
997 (format "[eglot] Sorry, couldn't guess for `%s'!\n%s"
998 main-mode base-prompt))
999 ((and program
1000 (not (file-name-absolute-p program))
1001 (not (eglot--executable-find program t)))
1002 (if full-program-invocation
1003 (concat (format "[eglot] I guess you want to run `%s'"
1004 full-program-invocation)
1005 (format ", but I can't find `%s' in PATH!"
1006 program)
1007 "\n" base-prompt)
1008 (eglot--error
1009 (concat "`%s' not found in PATH, but can't form"
1010 " an interactive prompt for to fix %s!")
1011 program guess))))))
1012 (contact
1013 (or (and prompt
1014 (split-string-and-unquote
1015 (read-shell-command
1016 prompt
1017 full-program-invocation
1018 'eglot-command-history)))
1019 guess)))
1020 (list managed-modes (eglot--current-project) class contact language-id)))
1021
1022(defvar eglot-lsp-context)
1023(put 'eglot-lsp-context 'variable-documentation
1024 "Dynamically non-nil when searching for projects in LSP context.")
1025
1026(defvar eglot--servers-by-xrefed-file
1027 (make-hash-table :test 'equal :weakness 'value))
1028
1029(defun eglot--current-project ()
1030 "Return a project object for Eglot's LSP purposes.
1031This relies on `project-current' and thus on
1032`project-find-functions'. Functions in the latter
1033variable (which see) can query the value `eglot-lsp-context' to
1034decide whether a given directory is a project containing a
1035suitable root directory for a given LSP server's purposes."
1036 (let ((eglot-lsp-context t))
1037 (or (project-current) `(transient . ,default-directory))))
1038
1039;;;###autoload
1040(defun eglot (managed-major-mode project class contact language-id
1041 &optional interactive)
1042 "Manage a project with a Language Server Protocol (LSP) server.
1043
1044The LSP server of CLASS is started (or contacted) via CONTACT.
1045If this operation is successful, current *and future* file
1046buffers of MANAGED-MAJOR-MODE inside PROJECT become \"managed\"
1047by the LSP server, meaning information about their contents is
1048exchanged periodically to provide enhanced code-analysis via
1049`xref-find-definitions', `flymake-mode', `eldoc-mode',
1050`completion-at-point', among others.
1051
1052Interactively, the command attempts to guess MANAGED-MAJOR-MODE
1053from current buffer, CLASS and CONTACT from
1054`eglot-server-programs' and PROJECT from
1055`project-find-functions'. The search for active projects in this
1056context binds `eglot-lsp-context' (which see).
1057
1058If it can't guess, the user is prompted. With a single
1059\\[universal-argument] prefix arg, it always prompt for COMMAND.
1060With two \\[universal-argument] prefix args, also prompts for
1061MANAGED-MAJOR-MODE.
1062
1063PROJECT is a project object as returned by `project-current'.
1064
1065CLASS is a subclass of `eglot-lsp-server'.
1066
1067CONTACT specifies how to contact the server. It is a
1068keyword-value plist used to initialize CLASS or a plain list as
1069described in `eglot-server-programs', which see.
1070
1071LANGUAGE-ID is the language ID string to send to the server for
1072MANAGED-MAJOR-MODE, which matters to a minority of servers.
1073
1074INTERACTIVE is t if called interactively."
1075 (interactive (append (eglot--guess-contact t) '(t)))
1076 (let* ((current-server (eglot-current-server))
1077 (live-p (and current-server (jsonrpc-running-p current-server))))
1078 (if (and live-p
1079 interactive
1080 (y-or-n-p "[eglot] Live process found, reconnect instead? "))
1081 (eglot-reconnect current-server interactive)
1082 (when live-p (ignore-errors (eglot-shutdown current-server)))
1083 (eglot--connect managed-major-mode project class contact language-id))))
1084
1085(defun eglot-reconnect (server &optional interactive)
1086 "Reconnect to SERVER.
1087INTERACTIVE is t if called interactively."
1088 (interactive (list (eglot--current-server-or-lose) t))
1089 (when (jsonrpc-running-p server)
1090 (ignore-errors (eglot-shutdown server interactive nil 'preserve-buffers)))
1091 (eglot--connect (eglot--major-modes server)
1092 (eglot--project server)
1093 (eieio-object-class-name server)
1094 (eglot--saved-initargs server)
1095 (eglot--language-id server))
1096 (eglot--message "Reconnected!"))
1097
1098(defvar eglot--managed-mode) ; forward decl
1099
1100;;;###autoload
1101(defun eglot-ensure ()
1102 "Start Eglot session for current buffer if there isn't one."
1103 (let ((buffer (current-buffer)))
1104 (cl-labels
1105 ((maybe-connect
1106 ()
1107 (remove-hook 'post-command-hook #'maybe-connect nil)
1108 (eglot--when-live-buffer buffer
1109 (unless eglot--managed-mode
1110 (apply #'eglot--connect (eglot--guess-contact))))))
1111 (when buffer-file-name
1112 (add-hook 'post-command-hook #'maybe-connect 'append nil)))))
1113
1114(defun eglot-events-buffer (server)
1115 "Display events buffer for SERVER.
1116Use current server's or first available Eglot events buffer."
1117 (interactive (list (eglot-current-server)))
1118 (let ((buffer (if server (jsonrpc-events-buffer server)
1119 (cl-find "\\*EGLOT.*events\\*"
1120 (buffer-list)
1121 :key #'buffer-name :test #'string-match))))
1122 (if buffer (display-buffer buffer)
1123 (eglot--error "Can't find an Eglot events buffer!"))))
1124
1125(defun eglot-stderr-buffer (server)
1126 "Display stderr buffer for SERVER."
1127 (interactive (list (eglot--current-server-or-lose)))
1128 (display-buffer (jsonrpc-stderr-buffer server)))
1129
1130(defun eglot-forget-pending-continuations (server)
1131 "Forget pending requests for SERVER."
1132 (interactive (list (eglot--current-server-or-lose)))
1133 (jsonrpc-forget-pending-continuations server))
1134
1135(defvar eglot-connect-hook
1136 '(eglot-signal-didChangeConfiguration)
1137 "Hook run after connecting in `eglot--connect'.")
1138
1139(defvar eglot-server-initialized-hook
1140 '()
1141 "Hook run after a `eglot-lsp-server' instance is created.
1142
1143That is before a connection was established. Use
1144`eglot-connect-hook' to hook into when a connection was
1145successfully established and the server on the other side has
1146received the initializing configuration.
1147
1148Each function is passed the server as an argument")
1149
1150(defun eglot--cmd (contact)
1151 "Helper for `eglot--connect'."
1152 (if (file-remote-p default-directory)
1153 ;; TODO: this seems like a bug, although it’s everywhere. For
1154 ;; some reason, for remote connections only, over a pipe, we
1155 ;; need to turn off line buffering on the tty.
1156 ;;
1157 ;; Not only does this seem like there should be a better way,
1158 ;; but it almost certainly doesn’t work on non-unix systems.
1159 (list "sh" "-c"
1160 (string-join (cons "stty raw > /dev/null;"
1161 (mapcar #'shell-quote-argument contact))
1162 " "))
1163 contact))
1164
1165(defvar-local eglot--cached-server nil
1166 "A cached reference to the current Eglot server.")
1167
1168(defun eglot--connect (managed-modes project class contact language-id)
1169 "Connect to MANAGED-MODES, LANGUAGE-ID, PROJECT, CLASS and CONTACT.
1170This docstring appeases checkdoc, that's all."
1171 (let* ((default-directory (project-root project))
1172 (nickname (file-name-base (directory-file-name default-directory)))
1173 (readable-name (format "EGLOT (%s/%s)" nickname managed-modes))
1174 autostart-inferior-process
1175 server-info
1176 (contact (if (functionp contact) (funcall contact) contact))
1177 (initargs
1178 (cond ((keywordp (car contact)) contact)
1179 ((integerp (cadr contact))
1180 (setq server-info (list (format "%s:%s" (car contact)
1181 (cadr contact))))
1182 `(:process ,(lambda ()
1183 (apply #'open-network-stream
1184 readable-name nil
1185 (car contact) (cadr contact)
1186 (cddr contact)))))
1187 ((and (stringp (car contact)) (memq :autoport contact))
1188 (setq server-info (list "<inferior process>"))
1189 `(:process ,(lambda ()
1190 (pcase-let ((`(,connection . ,inferior)
1191 (eglot--inferior-bootstrap
1192 readable-name
1193 contact)))
1194 (setq autostart-inferior-process inferior)
1195 connection))))
1196 ((stringp (car contact))
1197 (let* ((probe (cl-position-if #'keywordp contact))
1198 (more-initargs (and probe (cl-subseq contact probe)))
1199 (contact (cl-subseq contact 0 probe)))
1200 `(:process
1201 ,(lambda ()
1202 (let ((default-directory default-directory))
1203 (make-process
1204 :name readable-name
1205 :command (setq server-info (eglot--cmd contact))
1206 :connection-type 'pipe
1207 :coding 'utf-8-emacs-unix
1208 :noquery t
1209 :stderr (get-buffer-create
1210 (format "*%s stderr*" readable-name))
1211 :file-handler t)))
1212 ,@more-initargs)))))
1213 (spread (lambda (fn) (lambda (server method params)
1214 (let ((eglot--cached-server server))
1215 (apply fn server method (append params nil))))))
1216 (server
1217 (apply
1218 #'make-instance class
1219 :name readable-name
1220 :events-buffer-scrollback-size eglot-events-buffer-size
1221 :notification-dispatcher (funcall spread #'eglot-handle-notification)
1222 :request-dispatcher (funcall spread #'eglot-handle-request)
1223 :on-shutdown #'eglot--on-shutdown
1224 initargs))
1225 (cancelled nil)
1226 (tag (make-symbol "connected-catch-tag")))
1227 (when server-info
1228 (jsonrpc--debug server "Running language server: %s"
1229 (string-join server-info " ")))
1230 (setf (eglot--saved-initargs server) initargs)
1231 (setf (eglot--project server) project)
1232 (setf (eglot--project-nickname server) nickname)
1233 (setf (eglot--major-modes server) (eglot--ensure-list managed-modes))
1234 (setf (eglot--language-id server) language-id)
1235 (setf (eglot--inferior-process server) autostart-inferior-process)
1236 (run-hook-with-args 'eglot-server-initialized-hook server)
1237 ;; Now start the handshake. To honour `eglot-sync-connect'
1238 ;; maybe-sync-maybe-async semantics we use `jsonrpc-async-request'
1239 ;; and mimic most of `jsonrpc-request'.
1240 (unwind-protect
1241 (condition-case _quit
1242 (let ((retval
1243 (catch tag
1244 (jsonrpc-async-request
1245 server
1246 :initialize
1247 (list :processId
1248 (unless (or eglot-withhold-process-id
1249 (file-remote-p default-directory)
1250 (eq (jsonrpc-process-type server)
1251 'network))
1252 (emacs-pid))
1253 ;; Maybe turn trampy `/ssh:foo@bar:/path/to/baz.py'
1254 ;; into `/path/to/baz.py', so LSP groks it.
1255 :rootPath (file-local-name
1256 (expand-file-name default-directory))
1257 :rootUri (eglot--path-to-uri default-directory)
1258 :initializationOptions (eglot-initialization-options
1259 server)
1260 :capabilities (eglot-client-capabilities server)
1261 :workspaceFolders (eglot-workspace-folders server))
1262 :success-fn
1263 (eglot--lambda ((InitializeResult) capabilities serverInfo)
1264 (unless cancelled
1265 (push server
1266 (gethash project eglot--servers-by-project))
1267 (setf (eglot--capabilities server) capabilities)
1268 (setf (eglot--server-info server) serverInfo)
1269 (jsonrpc-notify server :initialized eglot--{})
1270 (dolist (buffer (buffer-list))
1271 (with-current-buffer buffer
1272 ;; No need to pass SERVER as an argument: it has
1273 ;; been registered in `eglot--servers-by-project',
1274 ;; so that it can be found (and cached) from
1275 ;; `eglot--maybe-activate-editing-mode' in any
1276 ;; managed buffer.
1277 (eglot--maybe-activate-editing-mode)))
1278 (setf (eglot--inhibit-autoreconnect server)
1279 (cond
1280 ((booleanp eglot-autoreconnect)
1281 (not eglot-autoreconnect))
1282 ((cl-plusp eglot-autoreconnect)
1283 (run-with-timer
1284 eglot-autoreconnect nil
1285 (lambda ()
1286 (setf (eglot--inhibit-autoreconnect server)
1287 (null eglot-autoreconnect)))))))
1288 (let ((default-directory (project-root project))
1289 (major-mode (car managed-modes)))
1290 (hack-dir-local-variables-non-file-buffer)
1291 (run-hook-with-args 'eglot-connect-hook server))
1292 (eglot--message
1293 "Connected! Server `%s' now managing `%s' buffers \
1294in project `%s'."
1295 (or (plist-get serverInfo :name)
1296 (jsonrpc-name server))
1297 managed-modes
1298 (eglot-project-nickname server))
1299 (when tag (throw tag t))))
1300 :timeout eglot-connect-timeout
1301 :error-fn (eglot--lambda ((ResponseError) code message)
1302 (unless cancelled
1303 (jsonrpc-shutdown server)
1304 (let ((msg (format "%s: %s" code message)))
1305 (if tag (throw tag `(error . ,msg))
1306 (eglot--error msg)))))
1307 :timeout-fn (lambda ()
1308 (unless cancelled
1309 (jsonrpc-shutdown server)
1310 (let ((msg (format "Timed out after %s seconds"
1311 eglot-connect-timeout)))
1312 (if tag (throw tag `(error . ,msg))
1313 (eglot--error msg))))))
1314 (cond ((numberp eglot-sync-connect)
1315 (accept-process-output nil eglot-sync-connect))
1316 (eglot-sync-connect
1317 (while t (accept-process-output
1318 nil eglot-connect-timeout)))))))
1319 (pcase retval
1320 (`(error . ,msg) (eglot--error msg))
1321 (`nil (eglot--message "Waiting in background for server `%s'"
1322 (jsonrpc-name server))
1323 nil)
1324 (_ server)))
1325 (quit (jsonrpc-shutdown server) (setq cancelled 'quit)))
1326 (setq tag nil))))
1327
1328(defun eglot--inferior-bootstrap (name contact &optional connect-args)
1329 "Use CONTACT to start a server, then connect to it.
1330Return a cons of two process objects (CONNECTION . INFERIOR).
1331Name both based on NAME.
1332CONNECT-ARGS are passed as additional arguments to
1333`open-network-stream'."
1334 (let* ((port-probe (make-network-process :name "eglot-port-probe-dummy"
1335 :server t
1336 :host "localhost"
1337 :service 0))
1338 (port-number (unwind-protect
1339 (process-contact port-probe :service)
1340 (delete-process port-probe)))
1341 inferior connection)
1342 (unwind-protect
1343 (progn
1344 (setq inferior
1345 (make-process
1346 :name (format "autostart-inferior-%s" name)
1347 :stderr (format "*%s stderr*" name)
1348 :noquery t
1349 :command (cl-subst
1350 (format "%s" port-number) :autoport contact)))
1351 (setq connection
1352 (cl-loop
1353 repeat 10 for i from 1
1354 do (accept-process-output nil 0.5)
1355 while (process-live-p inferior)
1356 do (eglot--message
1357 "Trying to connect to localhost and port %s (attempt %s)"
1358 port-number i)
1359 thereis (ignore-errors
1360 (apply #'open-network-stream
1361 (format "autoconnect-%s" name)
1362 nil
1363 "localhost" port-number connect-args))))
1364 (cons connection inferior))
1365 (cond ((and (process-live-p connection)
1366 (process-live-p inferior))
1367 (eglot--message "Done, connected to %s!" port-number))
1368 (t
1369 (when inferior (delete-process inferior))
1370 (when connection (delete-process connection))
1371 (eglot--error "Could not start and connect to server%s"
1372 (if inferior
1373 (format " started with %s"
1374 (process-command inferior))
1375 "!")))))))
1376
1377
1378;;; Helpers (move these to API?)
1379;;;
1380(defun eglot--error (format &rest args)
1381 "Error out with FORMAT with ARGS."
1382 (error "[eglot] %s" (apply #'format format args)))
1383
1384(defun eglot--message (format &rest args)
1385 "Message out with FORMAT with ARGS."
1386 (message "[eglot] %s" (apply #'format format args)))
1387
1388(defun eglot--warn (format &rest args)
1389 "Warning message with FORMAT and ARGS."
1390 (apply #'eglot--message (concat "(warning) " format) args)
1391 (let ((warning-minimum-level :error))
1392 (display-warning 'eglot (apply #'format format args) :warning)))
1393
1394(defun eglot-current-column () (- (point) (line-beginning-position)))
1395
1396(defvar eglot-current-column-function #'eglot-lsp-abiding-column
1397 "Function to calculate the current column.
1398
1399This is the inverse operation of
1400`eglot-move-to-column-function' (which see). It is a function of
1401no arguments returning a column number. For buffers managed by
1402fully LSP-compliant servers, this should be set to
1403`eglot-lsp-abiding-column' (the default), and
1404`eglot-current-column' for all others.")
1405
1406(defun eglot-lsp-abiding-column (&optional lbp)
1407 "Calculate current COLUMN as defined by the LSP spec.
1408LBP defaults to `line-beginning-position'."
1409 (/ (- (length (encode-coding-region (or lbp (line-beginning-position))
1410 ;; Fix github#860
1411 (min (point) (point-max)) 'utf-16 t))
1412 2)
1413 2))
1414
1415(defun eglot--pos-to-lsp-position (&optional pos)
1416 "Convert point POS to LSP position."
1417 (eglot--widening
1418 (list :line (1- (line-number-at-pos pos t)) ; F!@&#$CKING OFF-BY-ONE
1419 :character (progn (when pos (goto-char pos))
1420 (funcall eglot-current-column-function)))))
1421
1422(defvar eglot-move-to-column-function #'eglot-move-to-lsp-abiding-column
1423 "Function to move to a column reported by the LSP server.
1424
1425According to the standard, LSP column/character offsets are based
1426on a count of UTF-16 code units, not actual visual columns. So
1427when LSP says position 3 of a line containing just \"aXbc\",
1428where X is a multi-byte character, it actually means `b', not
1429`c'. However, many servers don't follow the spec this closely.
1430
1431For buffers managed by fully LSP-compliant servers, this should
1432be set to `eglot-move-to-lsp-abiding-column' (the default), and
1433`eglot-move-to-column' for all others.")
1434
1435(defun eglot-move-to-column (column)
1436 "Move to COLUMN without closely following the LSP spec."
1437 ;; We cannot use `move-to-column' here, because it moves to *visual*
1438 ;; columns, which can be different from LSP columns in case of
1439 ;; `whitespace-mode', `prettify-symbols-mode', etc. (github#296,
1440 ;; github#297)
1441 (goto-char (min (+ (line-beginning-position) column)
1442 (line-end-position))))
1443
1444(defun eglot-move-to-lsp-abiding-column (column)
1445 "Move to COLUMN abiding by the LSP spec."
1446 (save-restriction
1447 (cl-loop
1448 with lbp = (line-beginning-position)
1449 initially
1450 (narrow-to-region lbp (line-end-position))
1451 (move-to-column column)
1452 for diff = (- column
1453 (eglot-lsp-abiding-column lbp))
1454 until (zerop diff)
1455 do (condition-case eob-err
1456 (forward-char (/ (if (> diff 0) (1+ diff) (1- diff)) 2))
1457 (end-of-buffer (cl-return eob-err))))))
1458
1459(defun eglot--lsp-position-to-point (pos-plist &optional marker)
1460 "Convert LSP position POS-PLIST to Emacs point.
1461If optional MARKER, return a marker instead"
1462 (save-excursion
1463 (save-restriction
1464 (widen)
1465 (goto-char (point-min))
1466 (forward-line (min most-positive-fixnum
1467 (plist-get pos-plist :line)))
1468 (unless (eobp) ;; if line was excessive leave point at eob
1469 (let ((tab-width 1)
1470 (col (plist-get pos-plist :character)))
1471 (unless (wholenump col)
1472 (eglot--warn
1473 "Caution: LSP server sent invalid character position %s. Using 0 instead."
1474 col)
1475 (setq col 0))
1476 (funcall eglot-move-to-column-function col)))
1477 (if marker (copy-marker (point-marker)) (point)))))
1478
1479(defconst eglot--uri-path-allowed-chars
1480 (let ((vec (copy-sequence url-path-allowed-chars)))
1481 (aset vec ?: nil) ;; see github#639
1482 vec)
1483 "Like `url-path-allows-chars' but more restrictive.")
1484
1485(defun eglot--path-to-uri (path)
1486 "URIfy PATH."
1487 (let ((truepath (file-truename path)))
1488 (concat "file://"
1489 ;; Add a leading "/" for local MS Windows-style paths.
1490 (if (and (eq system-type 'windows-nt)
1491 (not (file-remote-p truepath)))
1492 "/")
1493 (url-hexify-string
1494 ;; Again watch out for trampy paths.
1495 (directory-file-name (file-local-name truepath))
1496 eglot--uri-path-allowed-chars))))
1497
1498(defun eglot--uri-to-path (uri)
1499 "Convert URI to file path, helped by `eglot--current-server'."
1500 (when (keywordp uri) (setq uri (substring (symbol-name uri) 1)))
1501 (let* ((server (eglot-current-server))
1502 (remote-prefix (and server (eglot--trampish-p server)))
1503 (retval (url-unhex-string (url-filename (url-generic-parse-url uri))))
1504 ;; Remove the leading "/" for local MS Windows-style paths.
1505 (normalized (if (and (not remote-prefix)
1506 (eq system-type 'windows-nt)
1507 (cl-plusp (length retval)))
1508 (substring retval 1)
1509 retval)))
1510 (concat remote-prefix normalized)))
1511
1512(defun eglot--snippet-expansion-fn ()
1513 "Compute a function to expand snippets.
1514Doubles as an indicator of snippet support."
1515 (and (boundp 'yas-minor-mode)
1516 (symbol-value 'yas-minor-mode)
1517 'yas-expand-snippet))
1518
1519(defun eglot--format-markup (markup)
1520 "Format MARKUP according to LSP's spec."
1521 (pcase-let ((`(,string ,mode)
1522 (if (stringp markup) (list markup 'gfm-view-mode)
1523 (list (plist-get markup :value)
1524 (pcase (plist-get markup :kind)
1525 ("markdown" 'gfm-view-mode)
1526 ("plaintext" 'text-mode)
1527 (_ major-mode))))))
1528 (with-temp-buffer
1529 (setq-local markdown-fontify-code-blocks-natively t)
1530 (insert string)
1531 (let ((inhibit-message t)
1532 (message-log-max nil))
1533 (ignore-errors (delay-mode-hooks (funcall mode))))
1534 (font-lock-ensure)
1535 (string-trim (buffer-string)))))
1536
1537(define-obsolete-variable-alias 'eglot-ignored-server-capabilites
1538 'eglot-ignored-server-capabilities "1.8")
1539
1540(defcustom eglot-ignored-server-capabilities (list)
1541 "LSP server capabilities that Eglot could use, but won't.
1542You could add, for instance, the symbol
1543`:documentHighlightProvider' to prevent automatic highlighting
1544under cursor."
1545 :type '(set
1546 :tag "Tick the ones you're not interested in"
1547 (const :tag "Documentation on hover" :hoverProvider)
1548 (const :tag "Code completion" :completionProvider)
1549 (const :tag "Function signature help" :signatureHelpProvider)
1550 (const :tag "Go to definition" :definitionProvider)
1551 (const :tag "Go to type definition" :typeDefinitionProvider)
1552 (const :tag "Go to implementation" :implementationProvider)
1553 (const :tag "Go to declaration" :implementationProvider)
1554 (const :tag "Find references" :referencesProvider)
1555 (const :tag "Highlight symbols automatically" :documentHighlightProvider)
1556 (const :tag "List symbols in buffer" :documentSymbolProvider)
1557 (const :tag "List symbols in workspace" :workspaceSymbolProvider)
1558 (const :tag "Execute code actions" :codeActionProvider)
1559 (const :tag "Code lens" :codeLensProvider)
1560 (const :tag "Format buffer" :documentFormattingProvider)
1561 (const :tag "Format portion of buffer" :documentRangeFormattingProvider)
1562 (const :tag "On-type formatting" :documentOnTypeFormattingProvider)
1563 (const :tag "Rename symbol" :renameProvider)
1564 (const :tag "Highlight links in document" :documentLinkProvider)
1565 (const :tag "Decorate color references" :colorProvider)
1566 (const :tag "Fold regions of buffer" :foldingRangeProvider)
1567 (const :tag "Execute custom commands" :executeCommandProvider)))
1568
1569(defun eglot--server-capable (&rest feats)
1570 "Determine if current server is capable of FEATS."
1571 (unless (cl-some (lambda (feat)
1572 (memq feat eglot-ignored-server-capabilities))
1573 feats)
1574 (cl-loop for caps = (eglot--capabilities (eglot--current-server-or-lose))
1575 then (cadr probe)
1576 for (feat . more) on feats
1577 for probe = (plist-member caps feat)
1578 if (not probe) do (cl-return nil)
1579 if (eq (cadr probe) :json-false) do (cl-return nil)
1580 if (not (listp (cadr probe))) do (cl-return (if more nil (cadr probe)))
1581 finally (cl-return (or (cadr probe) t)))))
1582
1583(defun eglot--range-region (range &optional markers)
1584 "Return region (BEG . END) that represents LSP RANGE.
1585If optional MARKERS, make markers."
1586 (let* ((st (plist-get range :start))
1587 (beg (eglot--lsp-position-to-point st markers))
1588 (end (eglot--lsp-position-to-point (plist-get range :end) markers)))
1589 (cons beg end)))
1590
1591(defun eglot--read-server (prompt &optional dont-if-just-the-one)
1592 "Read a running Eglot server from minibuffer using PROMPT.
1593If DONT-IF-JUST-THE-ONE and there's only one server, don't prompt
1594and just return it. PROMPT shouldn't end with a question mark."
1595 (let ((servers (cl-loop for servers
1596 being hash-values of eglot--servers-by-project
1597 append servers))
1598 (name (lambda (srv)
1599 (format "%s %s" (eglot-project-nickname srv)
1600 (eglot--major-modes srv)))))
1601 (cond ((null servers)
1602 (eglot--error "No servers!"))
1603 ((or (cdr servers) (not dont-if-just-the-one))
1604 (let* ((default (when-let ((current (eglot-current-server)))
1605 (funcall name current)))
1606 (read (completing-read
1607 (if default
1608 (format "%s (default %s)? " prompt default)
1609 (concat prompt "? "))
1610 (mapcar name servers)
1611 nil t
1612 nil nil
1613 default)))
1614 (cl-find read servers :key name :test #'equal)))
1615 (t (car servers)))))
1616
1617(defun eglot--trampish-p (server)
1618 "Tell if SERVER's project root is `file-remote-p'."
1619 (file-remote-p (project-root (eglot--project server))))
1620
1621(defun eglot--plist-keys (plist) "Get keys of a plist."
1622 (cl-loop for (k _v) on plist by #'cddr collect k))
1623
1624(defun eglot--ensure-list (x) (if (listp x) x (list x)))
1625
1626
1627;;; Minor modes
1628;;;
1629(defvar eglot-mode-map
1630 (let ((map (make-sparse-keymap)))
1631 (define-key map [remap display-local-help] #'eldoc-doc-buffer)
1632 map))
1633
1634(defvar-local eglot--current-flymake-report-fn nil
1635 "Current flymake report function for this buffer.")
1636
1637(defvar-local eglot--saved-bindings nil
1638 "Bindings saved by `eglot--setq-saving'.")
1639
1640(defvar eglot-stay-out-of '()
1641 "List of Emacs things that Eglot should try to stay of.
1642Each element is a string, a symbol, or a regexp which is matched
1643against a variable's name. Examples include the string
1644\"company\" or the symbol `xref'.
1645
1646Before Eglot starts \"managing\" a particular buffer, it
1647opinionatedly sets some peripheral Emacs facilities, such as
1648Flymake, Xref and Company. These overriding settings help ensure
1649consistent Eglot behaviour and only stay in place until
1650\"managing\" stops (usually via `eglot-shutdown'), whereupon the
1651previous settings are restored.
1652
1653However, if you wish for Eglot to stay out of a particular Emacs
1654facility that you'd like to keep control of add an element to
1655this list and Eglot will refrain from setting it.
1656
1657For example, to keep your Company customization, add the symbol
1658`company' to this variable.")
1659
1660(defun eglot--stay-out-of-p (symbol)
1661 "Tell if Eglot should stay of of SYMBOL."
1662 (cl-find (symbol-name symbol) eglot-stay-out-of
1663 :test (lambda (s thing)
1664 (let ((re (if (symbolp thing) (symbol-name thing) thing)))
1665 (string-match re s)))))
1666
1667(defmacro eglot--setq-saving (symbol binding)
1668 `(unless (or (not (boundp ',symbol)) (eglot--stay-out-of-p ',symbol))
1669 (push (cons ',symbol (symbol-value ',symbol)) eglot--saved-bindings)
1670 (setq-local ,symbol ,binding)))
1671
1672(defun eglot-managed-p ()
1673 "Tell if current buffer is managed by Eglot."
1674 eglot--managed-mode)
1675
1676(defvar eglot-managed-mode-hook nil
1677 "A hook run by Eglot after it started/stopped managing a buffer.
1678Use `eglot-managed-p' to determine if current buffer is managed.")
1679
1680(define-minor-mode eglot--managed-mode
1681 "Mode for source buffers managed by some Eglot project."
1682 :init-value nil :lighter nil :keymap eglot-mode-map
1683 (cond
1684 (eglot--managed-mode
1685 (add-hook 'after-change-functions 'eglot--after-change nil t)
1686 (add-hook 'before-change-functions 'eglot--before-change nil t)
1687 (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t)
1688 ;; Prepend "didClose" to the hook after the "nonoff", so it will run first
1689 (add-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose nil t)
1690 (add-hook 'before-revert-hook 'eglot--signal-textDocument/didClose nil t)
1691 (add-hook 'after-revert-hook 'eglot--after-revert-hook nil t)
1692 (add-hook 'before-save-hook 'eglot--signal-textDocument/willSave nil t)
1693 (add-hook 'after-save-hook 'eglot--signal-textDocument/didSave nil t)
1694 (unless (eglot--stay-out-of-p 'xref)
1695 (add-hook 'xref-backend-functions 'eglot-xref-backend nil t))
1696 (add-hook 'completion-at-point-functions #'eglot-completion-at-point nil t)
1697 (add-hook 'change-major-mode-hook #'eglot--managed-mode-off nil t)
1698 (add-hook 'post-self-insert-hook 'eglot--post-self-insert-hook nil t)
1699 (add-hook 'pre-command-hook 'eglot--pre-command-hook nil t)
1700 (eglot--setq-saving eldoc-documentation-functions
1701 '(eglot-signature-eldoc-function
1702 eglot-hover-eldoc-function))
1703 (eglot--setq-saving eldoc-documentation-strategy
1704 #'eldoc-documentation-enthusiast)
1705 (eglot--setq-saving xref-prompt-for-identifier nil)
1706 (eglot--setq-saving flymake-diagnostic-functions '(eglot-flymake-backend))
1707 (eglot--setq-saving company-backends '(company-capf))
1708 (eglot--setq-saving company-tooltip-align-annotations t)
1709 (unless (eglot--stay-out-of-p 'imenu)
1710 (add-function :before-until (local 'imenu-create-index-function)
1711 #'eglot-imenu))
1712 (unless (eglot--stay-out-of-p 'flymake) (flymake-mode 1))
1713 (unless (eglot--stay-out-of-p 'eldoc) (eldoc-mode 1))
1714 (cl-pushnew (current-buffer) (eglot--managed-buffers (eglot-current-server))))
1715 (t
1716 (remove-hook 'after-change-functions 'eglot--after-change t)
1717 (remove-hook 'before-change-functions 'eglot--before-change t)
1718 (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t)
1719 (remove-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose t)
1720 (remove-hook 'before-revert-hook 'eglot--signal-textDocument/didClose t)
1721 (remove-hook 'after-revert-hook 'eglot--after-revert-hook t)
1722 (remove-hook 'before-save-hook 'eglot--signal-textDocument/willSave t)
1723 (remove-hook 'after-save-hook 'eglot--signal-textDocument/didSave t)
1724 (remove-hook 'xref-backend-functions 'eglot-xref-backend t)
1725 (remove-hook 'completion-at-point-functions #'eglot-completion-at-point t)
1726 (remove-hook 'change-major-mode-hook #'eglot--managed-mode-off t)
1727 (remove-hook 'post-self-insert-hook 'eglot--post-self-insert-hook t)
1728 (remove-hook 'pre-command-hook 'eglot--pre-command-hook t)
1729 (cl-loop for (var . saved-binding) in eglot--saved-bindings
1730 do (set (make-local-variable var) saved-binding))
1731 (remove-function (local 'imenu-create-index-function) #'eglot-imenu)
1732 (when eglot--current-flymake-report-fn
1733 (eglot--report-to-flymake nil)
1734 (setq eglot--current-flymake-report-fn nil))
1735 (let ((server eglot--cached-server))
1736 (setq eglot--cached-server nil)
1737 (when server
1738 (setf (eglot--managed-buffers server)
1739 (delq (current-buffer) (eglot--managed-buffers server)))
1740 (when (and eglot-autoshutdown
1741 (null (eglot--managed-buffers server)))
1742 (eglot-shutdown server))))))
1743 ;; Note: the public hook runs before the internal eglot--managed-mode-hook.
1744 (run-hooks 'eglot-managed-mode-hook))
1745
1746(defun eglot--managed-mode-off ()
1747 "Turn off `eglot--managed-mode' unconditionally."
1748 (eglot--managed-mode -1))
1749
1750(defun eglot-current-server ()
1751 "Return logical Eglot server for current buffer, nil if none."
1752 (setq eglot--cached-server
1753 (or eglot--cached-server
1754 (cl-find major-mode
1755 (gethash (eglot--current-project) eglot--servers-by-project)
1756 :key #'eglot--major-modes
1757 :test #'memq)
1758 (and eglot-extend-to-xref
1759 buffer-file-name
1760 (gethash (expand-file-name buffer-file-name)
1761 eglot--servers-by-xrefed-file)))))
1762
1763(defun eglot--current-server-or-lose ()
1764 "Return current logical Eglot server connection or error."
1765 (or (eglot-current-server)
1766 (jsonrpc-error "No current JSON-RPC connection")))
1767
1768(defvar-local eglot--diagnostics nil
1769 "Flymake diagnostics for this buffer.")
1770
1771(defvar revert-buffer-preserve-modes)
1772(defun eglot--after-revert-hook ()
1773 "Eglot's `after-revert-hook'."
1774 (when revert-buffer-preserve-modes (eglot--signal-textDocument/didOpen)))
1775
1776(defun eglot--maybe-activate-editing-mode ()
1777 "Maybe activate `eglot--managed-mode'.
1778
1779If it is activated, also signal textDocument/didOpen."
1780 (unless eglot--managed-mode
1781 ;; Called when `revert-buffer-in-progress-p' is t but
1782 ;; `revert-buffer-preserve-modes' is nil.
1783 (when (and buffer-file-name (eglot-current-server))
1784 (setq eglot--diagnostics nil)
1785 (eglot--managed-mode)
1786 (eglot--signal-textDocument/didOpen))))
1787
1788(add-hook 'find-file-hook 'eglot--maybe-activate-editing-mode)
1789(add-hook 'after-change-major-mode-hook 'eglot--maybe-activate-editing-mode)
1790
1791(defun eglot-clear-status (server)
1792 "Clear the last JSONRPC error for SERVER."
1793 (interactive (list (eglot--current-server-or-lose)))
1794 (setf (jsonrpc-last-error server) nil))
1795
1796
1797;;; Mode-line, menu and other sugar
1798;;;
1799(defvar eglot--mode-line-format `(:eval (eglot--mode-line-format)))
1800
1801(put 'eglot--mode-line-format 'risky-local-variable t)
1802
1803(defun eglot--mouse-call (what)
1804 "Make an interactive lambda for calling WHAT from mode-line."
1805 (lambda (event)
1806 (interactive "e")
1807 (let ((start (event-start event))) (with-selected-window (posn-window start)
1808 (save-excursion
1809 (goto-char (or (posn-point start)
1810 (point)))
1811 (call-interactively what)
1812 (force-mode-line-update t))))))
1813
1814(defun eglot-manual () "Open on-line documentation."
1815 (interactive) (browse-url "https://github.com/joaotavora/eglot#readme"))
1816
1817(easy-menu-define eglot-menu nil "Eglot"
1818 `("Eglot"
1819 ;; Commands for getting information and customization.
1820 ["Read manual" eglot-manual]
1821 ["Customize Eglot" (lambda () (interactive) (customize-group "eglot"))]
1822 "--"
1823 ;; xref like commands.
1824 ["Find definitions" xref-find-definitions
1825 :help "Find definitions of identifier at point"
1826 :active (eglot--server-capable :definitionProvider)]
1827 ["Find references" xref-find-references
1828 :help "Find references to identifier at point"
1829 :active (eglot--server-capable :referencesProvider)]
1830 ["Find symbols in workspace (apropos)" xref-find-apropos
1831 :help "Find symbols matching a query"
1832 :active (eglot--server-capable :workspaceSymbolProvider)]
1833 ["Find declaration" eglot-find-declaration
1834 :help "Find declaration for identifier at point"
1835 :active (eglot--server-capable :declarationProvider)]
1836 ["Find implementation" eglot-find-implementation
1837 :help "Find implementation for identifier at point"
1838 :active (eglot--server-capable :implementationProvider)]
1839 ["Find type definition" eglot-find-typeDefinition
1840 :help "Find type definition for identifier at point"
1841 :active (eglot--server-capable :typeDefinitionProvider)]
1842 "--"
1843 ;; LSP-related commands (mostly Eglot's own commands).
1844 ["Rename symbol" eglot-rename
1845 :active (eglot--server-capable :renameProvider)]
1846 ["Format buffer" eglot-format-buffer
1847 :active (eglot--server-capable :documentFormattingProvider)]
1848 ["Format active region" eglot-format
1849 :active (and (region-active-p)
1850 (eglot--server-capable :documentRangeFormattingProvider))]
1851 ["Show Flymake diagnostics for buffer" flymake-show-buffer-diagnostics]
1852 ["Show Flymake diagnostics for project" flymake-show-project-diagnostics]
1853 ["Show Eldoc documentation at point" eldoc-doc-buffer]
1854 "--"
1855 ["All possible code actions" eglot-code-actions
1856 :active (eglot--server-capable :codeActionProvider)]
1857 ["Organize imports" eglot-code-action-organize-imports
1858 :visible (eglot--server-capable :codeActionProvider)]
1859 ["Extract" eglot-code-action-extract
1860 :visible (eglot--server-capable :codeActionProvider)]
1861 ["Inline" eglot-code-action-inline
1862 :visible (eglot--server-capable :codeActionProvider)]
1863 ["Rewrite" eglot-code-action-rewrite
1864 :visible (eglot--server-capable :codeActionProvider)]
1865 ["Quickfix" eglot-code-action-quickfix
1866 :visible (eglot--server-capable :codeActionProvider)]))
1867
1868(easy-menu-define eglot-server-menu nil "Monitor server communication"
1869 '("Debugging the server communication"
1870 ["Reconnect to server" eglot-reconnect]
1871 ["Quit server" eglot-shutdown]
1872 "--"
1873 ["LSP events buffer" eglot-events-buffer]
1874 ["Server stderr buffer" eglot-stderr-buffer]
1875 ["Customize event buffer size"
1876 (lambda ()
1877 (interactive)
1878 (customize-variable 'eglot-events-buffer-size))]))
1879
1880(defun eglot--mode-line-props (thing face defs &optional prepend)
1881 "Helper for function `eglot--mode-line-format'.
1882Uses THING, FACE, DEFS and PREPEND."
1883 (cl-loop with map = (make-sparse-keymap)
1884 for (elem . rest) on defs
1885 for (key def help) = elem
1886 do (define-key map `[mode-line ,key] (eglot--mouse-call def))
1887 concat (format "%s: %s" key help) into blurb
1888 when rest concat "\n" into blurb
1889 finally (return `(:propertize ,thing
1890 face ,face
1891 keymap ,map help-echo ,(concat prepend blurb)
1892 mouse-face mode-line-highlight))))
1893
1894(defun eglot--mode-line-format ()
1895 "Compose the Eglot's mode-line."
1896 (pcase-let* ((server (eglot-current-server))
1897 (nick (and server (eglot-project-nickname server)))
1898 (pending (and server (hash-table-count
1899 (jsonrpc--request-continuations server))))
1900 (`(,_id ,doing ,done-p ,_detail) (and server (eglot--spinner server)))
1901 (last-error (and server (jsonrpc-last-error server))))
1902 (append
1903 `(,(propertize
1904 eglot-menu-string
1905 'face 'eglot-mode-line
1906 'mouse-face 'mode-line-highlight
1907 'help-echo "Eglot: Emacs LSP client\nmouse-1: Display minor mode menu"
1908 'keymap (let ((map (make-sparse-keymap)))
1909 (define-key map [mode-line down-mouse-1] eglot-menu)
1910 map)))
1911 (when nick
1912 `(":"
1913 ,(propertize
1914 nick
1915 'face 'eglot-mode-line
1916 'mouse-face 'mode-line-highlight
1917 'help-echo (format "Project '%s'\nmouse-1: LSP server control menu" nick)
1918 'keymap (let ((map (make-sparse-keymap)))
1919 (define-key map [mode-line down-mouse-1] eglot-server-menu)
1920 map))
1921 ,@(when last-error
1922 `("/" ,(eglot--mode-line-props
1923 "error" 'compilation-mode-line-fail
1924 '((mouse-3 eglot-clear-status "Clear this status"))
1925 (format "An error occurred: %s\n" (plist-get last-error
1926 :message)))))
1927 ,@(when (and doing (not done-p))
1928 `("/" ,(eglot--mode-line-props doing
1929 'compilation-mode-line-run '())))
1930 ,@(when (cl-plusp pending)
1931 `("/" ,(eglot--mode-line-props
1932 (format "%d" pending) 'warning
1933 '((mouse-3 eglot-forget-pending-continuations
1934 "Forget pending continuations"))
1935 "Number of outgoing, \
1936still unanswered LSP requests to the server\n"))))))))
1937
1938(add-to-list 'mode-line-misc-info
1939 `(eglot--managed-mode (" [" eglot--mode-line-format "] ")))
1940
1941
1942;;; Flymake customization
1943;;;
1944(put 'eglot-note 'flymake-category 'flymake-note)
1945(put 'eglot-warning 'flymake-category 'flymake-warning)
1946(put 'eglot-error 'flymake-category 'flymake-error)
1947
1948(defalias 'eglot--make-diag 'flymake-make-diagnostic)
1949(defalias 'eglot--diag-data 'flymake-diagnostic-data)
1950
1951(cl-loop for i from 1
1952 for type in '(eglot-note eglot-warning eglot-error )
1953 do (put type 'flymake-overlay-control
1954 `((mouse-face . highlight)
1955 (priority . ,(+ 50 i))
1956 (keymap . ,(let ((map (make-sparse-keymap)))
1957 (define-key map [mouse-1]
1958 (eglot--mouse-call 'eglot-code-actions))
1959 map)))))
1960
1961
1962;;; Protocol implementation (Requests, notifications, etc)
1963;;;
1964(cl-defmethod eglot-handle-notification
1965 (_server method &key &allow-other-keys)
1966 "Handle unknown notification."
1967 (unless (or (string-prefix-p "$" (format "%s" method))
1968 (not (memq 'disallow-unknown-methods eglot-strict-mode)))
1969 (eglot--warn "Server sent unknown notification method `%s'" method)))
1970
1971(cl-defmethod eglot-handle-request
1972 (_server method &key &allow-other-keys)
1973 "Handle unknown request."
1974 (when (memq 'disallow-unknown-methods eglot-strict-mode)
1975 (jsonrpc-error "Unknown request method `%s'" method)))
1976
1977(cl-defmethod eglot-execute-command
1978 (server command arguments)
1979 "Execute COMMAND on SERVER with `:workspace/executeCommand'.
1980COMMAND is a symbol naming the command."
1981 (jsonrpc-request server :workspace/executeCommand
1982 `(:command ,(format "%s" command) :arguments ,arguments)))
1983
1984(cl-defmethod eglot-handle-notification
1985 (_server (_method (eql window/showMessage)) &key type message)
1986 "Handle notification window/showMessage."
1987 (eglot--message (propertize "Server reports (type=%s): %s"
1988 'face (if (<= type 1) 'error))
1989 type message))
1990
1991(cl-defmethod eglot-handle-request
1992 (_server (_method (eql window/showMessageRequest)) &key type message actions)
1993 "Handle server request window/showMessageRequest."
1994 (let* ((actions (append actions nil)) ;; gh#627
1995 (label (completing-read
1996 (concat
1997 (format (propertize "[eglot] Server reports (type=%s): %s"
1998 'face (if (<= type 1) 'error))
1999 type message)
2000 "\nChoose an option: ")
2001 (or (mapcar (lambda (obj) (plist-get obj :title)) actions)
2002 '("OK"))
2003 nil t (plist-get (elt actions 0) :title))))
2004 (if label `(:title ,label) :null)))
2005
2006(cl-defmethod eglot-handle-notification
2007 (_server (_method (eql window/logMessage)) &key _type _message)
2008 "Handle notification window/logMessage.") ;; noop, use events buffer
2009
2010(cl-defmethod eglot-handle-notification
2011 (_server (_method (eql telemetry/event)) &rest _any)
2012 "Handle notification telemetry/event.") ;; noop, use events buffer
2013
2014(cl-defmethod eglot-handle-notification
2015 (_server (_method (eql textDocument/publishDiagnostics)) &key uri diagnostics
2016 &allow-other-keys) ; FIXME: doesn't respect `eglot-strict-mode'
2017 "Handle notification publishDiagnostics."
2018 (cl-flet ((eglot--diag-type (sev)
2019 (cond ((null sev) 'eglot-error)
2020 ((<= sev 1) 'eglot-error)
2021 ((= sev 2) 'eglot-warning)
2022 (t 'eglot-note)))
2023 (mess (source code message)
2024 (concat source (and code (format " [%s]" code)) ": " message)))
2025 (if-let ((buffer (find-buffer-visiting (eglot--uri-to-path uri))))
2026 (with-current-buffer buffer
2027 (cl-loop
2028 for diag-spec across diagnostics
2029 collect (eglot--dbind ((Diagnostic) range code message severity source tags)
2030 diag-spec
2031 (setq message (mess source code message))
2032 (pcase-let
2033 ((`(,beg . ,end) (eglot--range-region range)))
2034 ;; Fallback to `flymake-diag-region' if server
2035 ;; botched the range
2036 (when (= beg end)
2037 (if-let* ((st (plist-get range :start))
2038 (diag-region
2039 (flymake-diag-region
2040 (current-buffer) (1+ (plist-get st :line))
2041 (plist-get st :character))))
2042 (setq beg (car diag-region) end (cdr diag-region))
2043 (eglot--widening
2044 (goto-char (point-min))
2045 (setq beg
2046 (line-beginning-position
2047 (1+ (plist-get (plist-get range :start) :line))))
2048 (setq end
2049 (line-end-position
2050 (1+ (plist-get (plist-get range :end) :line)))))))
2051 (eglot--make-diag
2052 (current-buffer) beg end
2053 (eglot--diag-type severity)
2054 message `((eglot-lsp-diag . ,diag-spec))
2055 (when-let ((faces
2056 (cl-loop for tag across tags
2057 when (alist-get tag eglot--tag-faces)
2058 collect it)))
2059 `((face . ,faces))))))
2060 into diags
2061 finally (cond ((and
2062 ;; only add to current report if Flymake
2063 ;; starts on idle-timer (github#958)
2064 (not (null flymake-no-changes-timeout))
2065 eglot--current-flymake-report-fn)
2066 (eglot--report-to-flymake diags))
2067 (t
2068 (setq eglot--diagnostics diags)))))
2069 (cl-loop
2070 with path = (expand-file-name (eglot--uri-to-path uri))
2071 for diag-spec across diagnostics
2072 collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec
2073 (setq message (mess source code message))
2074 (let* ((start (plist-get range :start))
2075 (line (1+ (plist-get start :line)))
2076 (char (1+ (plist-get start :character))))
2077 (eglot--make-diag
2078 path (cons line char) nil (eglot--diag-type severity) message)))
2079 into diags
2080 finally
2081 (setq flymake-list-only-diagnostics
2082 (assoc-delete-all path flymake-list-only-diagnostics #'string=))
2083 (push (cons path diags) flymake-list-only-diagnostics)))))
2084
2085(cl-defun eglot--register-unregister (server things how)
2086 "Helper for `registerCapability'.
2087THINGS are either registrations or unregisterations (sic)."
2088 (cl-loop
2089 for thing in (cl-coerce things 'list)
2090 do (eglot--dbind ((Registration) id method registerOptions) thing
2091 (apply (cl-ecase how
2092 (register 'eglot-register-capability)
2093 (unregister 'eglot-unregister-capability))
2094 server (intern method) id registerOptions))))
2095
2096(cl-defmethod eglot-handle-request
2097 (server (_method (eql client/registerCapability)) &key registrations)
2098 "Handle server request client/registerCapability."
2099 (eglot--register-unregister server registrations 'register))
2100
2101(cl-defmethod eglot-handle-request
2102 (server (_method (eql client/unregisterCapability))
2103 &key unregisterations) ;; XXX: "unregisterations" (sic)
2104 "Handle server request client/unregisterCapability."
2105 (eglot--register-unregister server unregisterations 'unregister))
2106
2107(cl-defmethod eglot-handle-request
2108 (_server (_method (eql workspace/applyEdit)) &key _label edit)
2109 "Handle server request workspace/applyEdit."
2110 (eglot--apply-workspace-edit edit eglot-confirm-server-initiated-edits)
2111 `(:applied t))
2112
2113(cl-defmethod eglot-handle-request
2114 (server (_method (eql workspace/workspaceFolders)))
2115 "Handle server request workspace/workspaceFolders."
2116 (eglot-workspace-folders server))
2117
2118(defun eglot--TextDocumentIdentifier ()
2119 "Compute TextDocumentIdentifier object for current buffer."
2120 `(:uri ,(eglot--path-to-uri (or buffer-file-name
2121 (ignore-errors
2122 (buffer-file-name
2123 (buffer-base-buffer)))))))
2124
2125(defvar-local eglot--versioned-identifier 0)
2126
2127(defun eglot--VersionedTextDocumentIdentifier ()
2128 "Compute VersionedTextDocumentIdentifier object for current buffer."
2129 (append (eglot--TextDocumentIdentifier)
2130 `(:version ,eglot--versioned-identifier)))
2131
2132(defun eglot--TextDocumentItem ()
2133 "Compute TextDocumentItem object for current buffer."
2134 (append
2135 (eglot--VersionedTextDocumentIdentifier)
2136 (list :languageId
2137 (eglot--language-id (eglot--current-server-or-lose))
2138 :text
2139 (eglot--widening
2140 (buffer-substring-no-properties (point-min) (point-max))))))
2141
2142(defun eglot--TextDocumentPositionParams ()
2143 "Compute TextDocumentPositionParams."
2144 (list :textDocument (eglot--TextDocumentIdentifier)
2145 :position (eglot--pos-to-lsp-position)))
2146
2147(defvar-local eglot--last-inserted-char nil
2148 "If non-nil, value of the last inserted character in buffer.")
2149
2150(defun eglot--post-self-insert-hook ()
2151 "Set `eglot--last-inserted-char', maybe call on-type-formatting."
2152 (setq eglot--last-inserted-char last-input-event)
2153 (let ((ot-provider (eglot--server-capable :documentOnTypeFormattingProvider)))
2154 (when (and ot-provider
2155 (ignore-errors ; github#906, some LS's send empty strings
2156 (or (eq last-input-event
2157 (seq-first (plist-get ot-provider :firstTriggerCharacter)))
2158 (cl-find last-input-event
2159 (plist-get ot-provider :moreTriggerCharacter)
2160 :key #'seq-first))))
2161 (eglot-format (point) nil last-input-event))))
2162
2163(defvar eglot--workspace-symbols-cache (make-hash-table :test #'equal)
2164 "Cache of `workspace/Symbol' results used by `xref-find-definitions'.")
2165
2166(defun eglot--pre-command-hook ()
2167 "Reset some temporary variables."
2168 (clrhash eglot--workspace-symbols-cache)
2169 (setq eglot--last-inserted-char nil))
2170
2171(defun eglot--CompletionParams ()
2172 (append
2173 (eglot--TextDocumentPositionParams)
2174 `(:context
2175 ,(if-let (trigger (and (characterp eglot--last-inserted-char)
2176 (cl-find eglot--last-inserted-char
2177 (eglot--server-capable :completionProvider
2178 :triggerCharacters)
2179 :key (lambda (str) (aref str 0))
2180 :test #'char-equal)))
2181 `(:triggerKind 2 :triggerCharacter ,trigger) `(:triggerKind 1)))))
2182
2183(defvar-local eglot--recent-changes nil
2184 "Recent buffer changes as collected by `eglot--before-change'.")
2185
2186(cl-defmethod jsonrpc-connection-ready-p ((_server eglot-lsp-server) _what)
2187 "Tell if SERVER is ready for WHAT in current buffer."
2188 (and (cl-call-next-method) (not eglot--recent-changes)))
2189
2190(defvar-local eglot--change-idle-timer nil "Idle timer for didChange signals.")
2191
2192(defun eglot--before-change (beg end)
2193 "Hook onto `before-change-functions' with BEG and END."
2194 (when (listp eglot--recent-changes)
2195 ;; Records BEG and END, crucially convert them into LSP
2196 ;; (line/char) positions before that information is lost (because
2197 ;; the after-change thingy doesn't know if newlines were
2198 ;; deleted/added). Also record markers of BEG and END
2199 ;; (github#259)
2200 (push `(,(eglot--pos-to-lsp-position beg)
2201 ,(eglot--pos-to-lsp-position end)
2202 (,beg . ,(copy-marker beg nil))
2203 (,end . ,(copy-marker end t)))
2204 eglot--recent-changes)))
2205
2206(defun eglot--after-change (beg end pre-change-length)
2207 "Hook onto `after-change-functions'.
2208Records BEG, END and PRE-CHANGE-LENGTH locally."
2209 (cl-incf eglot--versioned-identifier)
2210 (pcase (and (listp eglot--recent-changes)
2211 (car eglot--recent-changes))
2212 (`(,lsp-beg ,lsp-end
2213 (,b-beg . ,b-beg-marker)
2214 (,b-end . ,b-end-marker))
2215 ;; github#259 and github#367: With `capitalize-word' or somesuch,
2216 ;; `before-change-functions' always records the whole word's
2217 ;; `b-beg' and `b-end'. Similarly, when coalescing two lines
2218 ;; into one, `fill-paragraph' they mark the end of the first line
2219 ;; up to the end of the second line. In both situations, args
2220 ;; received here contradict that information: `beg' and `end'
2221 ;; will differ by 1 and will likely only encompass the letter
2222 ;; that was capitalized or, in the sentence-joining situation,
2223 ;; the replacement of the newline with a space. That's we keep
2224 ;; markers _and_ positions so we're able to detect and correct
2225 ;; this. We ignore `beg', `len' and `pre-change-len' and send
2226 ;; "fuller" information about the region from the markers. I've
2227 ;; also experimented with doing this unconditionally but it seems
2228 ;; to break when newlines are added.
2229 (if (and (= b-end b-end-marker) (= b-beg b-beg-marker)
2230 (or (/= beg b-beg) (/= end b-end)))
2231 (setcar eglot--recent-changes
2232 `(,lsp-beg ,lsp-end ,(- b-end-marker b-beg-marker)
2233 ,(buffer-substring-no-properties b-beg-marker
2234 b-end-marker)))
2235 (setcar eglot--recent-changes
2236 `(,lsp-beg ,lsp-end ,pre-change-length
2237 ,(buffer-substring-no-properties beg end)))))
2238 (_ (setf eglot--recent-changes :emacs-messup)))
2239 (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer))
2240 (let ((buf (current-buffer)))
2241 (setq eglot--change-idle-timer
2242 (run-with-idle-timer
2243 eglot-send-changes-idle-time
2244 nil (lambda () (eglot--when-live-buffer buf
2245 (when eglot--managed-mode
2246 (eglot--signal-textDocument/didChange)
2247 (setq eglot--change-idle-timer nil))))))))
2248
2249;; HACK! Launching a deferred sync request with outstanding changes is a
2250;; bad idea, since that might lead to the request never having a
2251;; chance to run, because `jsonrpc-connection-ready-p'.
2252(advice-add #'jsonrpc-request :before
2253 (cl-function (lambda (_proc _method _params &key
2254 deferred &allow-other-keys)
2255 (when (and eglot--managed-mode deferred)
2256 (eglot--signal-textDocument/didChange))))
2257 '((name . eglot--signal-textDocument/didChange)))
2258
2259(defvar-local eglot-workspace-configuration ()
2260 "Configure LSP servers specifically for a given project.
2261
2262This variable's value should be a plist (SECTION VALUE ...).
2263SECTION is a keyword naming a parameter section relevant to a
2264particular server. VALUE is a plist or a primitive type
2265converted to JSON also understood by that server.
2266
2267Instead of a plist, an alist ((SECTION . VALUE) ...) can be used
2268instead, but this variant is less reliable and not recommended.
2269
2270This variable should be set as a directory-local variable. See
2271See info node `(emacs)Directory Variables' for various ways to to
2272that.
2273
2274Here's an example value that establishes two sections relevant to
2275the Pylsp and Gopls LSP servers:
2276
2277 (:pylsp (:plugins (:jedi_completion (:include_params t
2278 :fuzzy t)
2279 :pylint (:enabled :json-false)))
2280 :gopls (:usePlaceholders t))
2281
2282The value of this variable can also be a unary function of a
2283single argument, which will be a connected `eglot-lsp-server'
2284instance. The function runs with `default-directory' set to the
2285root of the current project. It should return an object of the
2286format described above.")
2287
2288;;;###autoload
2289(put 'eglot-workspace-configuration 'safe-local-variable 'listp)
2290
2291(defun eglot-show-workspace-configuration (&optional server)
2292 "Dump `eglot-workspace-configuration' as JSON for debugging."
2293 (interactive (list (and (eglot-current-server)
2294 (eglot--read-server "Server configuration"
2295 (eglot-current-server)))))
2296 (let ((conf (eglot--workspace-configuration-plist server)))
2297 (with-current-buffer (get-buffer-create "*EGLOT workspace configuration*")
2298 (erase-buffer)
2299 (insert (jsonrpc--json-encode conf))
2300 (with-no-warnings
2301 (require 'json)
2302 (when (require 'json-mode nil t) (json-mode))
2303 (json-pretty-print-buffer))
2304 (pop-to-buffer (current-buffer)))))
2305
2306(defun eglot--workspace-configuration (server)
2307 (if (functionp eglot-workspace-configuration)
2308 (funcall eglot-workspace-configuration server)
2309 eglot-workspace-configuration))
2310
2311(defun eglot--workspace-configuration-plist (server)
2312 "Returns `eglot-workspace-configuration' suitable for serialization."
2313 (let ((val (eglot--workspace-configuration server)))
2314 (or (and (consp (car val))
2315 (cl-loop for (section . v) in val
2316 collect (if (keywordp section) section
2317 (intern (format ":%s" section)))
2318 collect v))
2319 val)))
2320
2321(defun eglot-signal-didChangeConfiguration (server)
2322 "Send a `:workspace/didChangeConfiguration' signal to SERVER.
2323When called interactively, use the currently active server"
2324 (interactive (list (eglot--current-server-or-lose)))
2325 (jsonrpc-notify
2326 server :workspace/didChangeConfiguration
2327 (list
2328 :settings
2329 (or (eglot--workspace-configuration-plist server)
2330 eglot--{}))))
2331
2332(cl-defmethod eglot-handle-request
2333 (server (_method (eql workspace/configuration)) &key items)
2334 "Handle server request workspace/configuration."
2335 (apply #'vector
2336 (mapcar
2337 (eglot--lambda ((ConfigurationItem) scopeUri section)
2338 (with-temp-buffer
2339 (let* ((uri-path (eglot--uri-to-path scopeUri))
2340 (default-directory
2341 (if (and (not (string-empty-p uri-path))
2342 (file-directory-p uri-path))
2343 (file-name-as-directory uri-path)
2344 (project-root (eglot--project server)))))
2345 (setq-local major-mode (car (eglot--major-modes server)))
2346 (hack-dir-local-variables-non-file-buffer)
2347 (cl-loop for (wsection o)
2348 on (eglot--workspace-configuration-plist server)
2349 by #'cddr
2350 when (string=
2351 (if (keywordp wsection)
2352 (substring (symbol-name wsection) 1)
2353 wsection)
2354 section)
2355 return o))))
2356 items)))
2357
2358(defun eglot--signal-textDocument/didChange ()
2359 "Send textDocument/didChange to server."
2360 (when eglot--recent-changes
2361 (let* ((server (eglot--current-server-or-lose))
2362 (sync-capability (eglot--server-capable :textDocumentSync))
2363 (sync-kind (if (numberp sync-capability) sync-capability
2364 (plist-get sync-capability :change)))
2365 (full-sync-p (or (eq sync-kind 1)
2366 (eq :emacs-messup eglot--recent-changes))))
2367 (jsonrpc-notify
2368 server :textDocument/didChange
2369 (list
2370 :textDocument (eglot--VersionedTextDocumentIdentifier)
2371 :contentChanges
2372 (if full-sync-p
2373 (vector `(:text ,(eglot--widening
2374 (buffer-substring-no-properties (point-min)
2375 (point-max)))))
2376 (cl-loop for (beg end len text) in (reverse eglot--recent-changes)
2377 ;; github#259: `capitalize-word' and commands based
2378 ;; on `casify_region' will cause multiple duplicate
2379 ;; empty entries in `eglot--before-change' calls
2380 ;; without an `eglot--after-change' reciprocal.
2381 ;; Weed them out here.
2382 when (numberp len)
2383 vconcat `[,(list :range `(:start ,beg :end ,end)
2384 :rangeLength len :text text)]))))
2385 (setq eglot--recent-changes nil)
2386 (setf (eglot--spinner server) (list nil :textDocument/didChange t))
2387 (jsonrpc--call-deferred server))))
2388
2389(defun eglot--signal-textDocument/didOpen ()
2390 "Send textDocument/didOpen to server."
2391 (setq eglot--recent-changes nil eglot--versioned-identifier 0)
2392 (jsonrpc-notify
2393 (eglot--current-server-or-lose)
2394 :textDocument/didOpen `(:textDocument ,(eglot--TextDocumentItem))))
2395
2396(defun eglot--signal-textDocument/didClose ()
2397 "Send textDocument/didClose to server."
2398 (with-demoted-errors
2399 "[eglot] error sending textDocument/didClose: %s"
2400 (jsonrpc-notify
2401 (eglot--current-server-or-lose)
2402 :textDocument/didClose `(:textDocument ,(eglot--TextDocumentIdentifier)))))
2403
2404(defun eglot--signal-textDocument/willSave ()
2405 "Send textDocument/willSave to server."
2406 (let ((server (eglot--current-server-or-lose))
2407 (params `(:reason 1 :textDocument ,(eglot--TextDocumentIdentifier))))
2408 (when (eglot--server-capable :textDocumentSync :willSave)
2409 (jsonrpc-notify server :textDocument/willSave params))
2410 (when (eglot--server-capable :textDocumentSync :willSaveWaitUntil)
2411 (ignore-errors
2412 (eglot--apply-text-edits
2413 (jsonrpc-request server :textDocument/willSaveWaitUntil params
2414 :timeout 0.5))))))
2415
2416(defun eglot--signal-textDocument/didSave ()
2417 "Send textDocument/didSave to server."
2418 (eglot--signal-textDocument/didChange)
2419 (jsonrpc-notify
2420 (eglot--current-server-or-lose)
2421 :textDocument/didSave
2422 (list
2423 ;; TODO: Handle TextDocumentSaveRegistrationOptions to control this.
2424 :text (buffer-substring-no-properties (point-min) (point-max))
2425 :textDocument (eglot--TextDocumentIdentifier))))
2426
2427(defun eglot-flymake-backend (report-fn &rest _more)
2428 "A Flymake backend for Eglot.
2429Calls REPORT-FN (or arranges for it to be called) when the server
2430publishes diagnostics. Between calls to this function, REPORT-FN
2431may be called multiple times (respecting the protocol of
2432`flymake-backend-functions')."
2433 (cond (eglot--managed-mode
2434 (setq eglot--current-flymake-report-fn report-fn)
2435 (eglot--report-to-flymake eglot--diagnostics))
2436 (t
2437 (funcall report-fn nil))))
2438
2439(defun eglot--report-to-flymake (diags)
2440 "Internal helper for `eglot-flymake-backend'."
2441 (save-restriction
2442 (widen)
2443 (funcall eglot--current-flymake-report-fn diags
2444 ;; If the buffer hasn't changed since last
2445 ;; call to the report function, flymake won't
2446 ;; delete old diagnostics. Using :region
2447 ;; keyword forces flymake to delete
2448 ;; them (github#159).
2449 :region (cons (point-min) (point-max))))
2450 (setq eglot--diagnostics diags))
2451
2452(defun eglot-xref-backend () "Eglot xref backend." 'eglot)
2453
2454(defvar eglot--temp-location-buffers (make-hash-table :test #'equal)
2455 "Helper variable for `eglot--handling-xrefs'.")
2456
2457(defvar eglot-xref-lessp-function #'ignore
2458 "Compare two `xref-item' objects for sorting.")
2459
2460(cl-defmacro eglot--collecting-xrefs ((collector) &rest body)
2461 "Sort and handle xrefs collected with COLLECTOR in BODY."
2462 (declare (indent 1) (debug (sexp &rest form)))
2463 (let ((collected (cl-gensym "collected")))
2464 `(unwind-protect
2465 (let (,collected)
2466 (cl-flet ((,collector (xref) (push xref ,collected)))
2467 ,@body)
2468 (setq ,collected (nreverse ,collected))
2469 (sort ,collected eglot-xref-lessp-function))
2470 (maphash (lambda (_uri buf) (kill-buffer buf)) eglot--temp-location-buffers)
2471 (clrhash eglot--temp-location-buffers))))
2472
2473(defun eglot--xref-make-match (name uri range)
2474 "Like `xref-make-match' but with LSP's NAME, URI and RANGE.
2475Try to visit the target file for a richer summary line."
2476 (pcase-let*
2477 ((file (eglot--uri-to-path uri))
2478 (visiting (or (find-buffer-visiting file)
2479 (gethash uri eglot--temp-location-buffers)))
2480 (collect (lambda ()
2481 (eglot--widening
2482 (pcase-let* ((`(,beg . ,end) (eglot--range-region range))
2483 (bol (progn (goto-char beg) (line-beginning-position)))
2484 (substring (buffer-substring bol (line-end-position)))
2485 (hi-beg (- beg bol))
2486 (hi-end (- (min (line-end-position) end) bol)))
2487 (add-face-text-property hi-beg hi-end 'xref-match
2488 t substring)
2489 (list substring (line-number-at-pos (point) t)
2490 (eglot-current-column) (- end beg))))))
2491 (`(,summary ,line ,column ,length)
2492 (cond
2493 (visiting (with-current-buffer visiting (funcall collect)))
2494 ((file-readable-p file) (with-current-buffer
2495 (puthash uri (generate-new-buffer " *temp*")
2496 eglot--temp-location-buffers)
2497 (insert-file-contents file)
2498 (funcall collect)))
2499 (t ;; fall back to the "dumb strategy"
2500 (let* ((start (cl-getf range :start))
2501 (line (1+ (cl-getf start :line)))
2502 (start-pos (cl-getf start :character))
2503 (end-pos (cl-getf (cl-getf range :end) :character)))
2504 (list name line start-pos (- end-pos start-pos)))))))
2505 (setf (gethash (expand-file-name file) eglot--servers-by-xrefed-file)
2506 (eglot--current-server-or-lose))
2507 (xref-make-match summary (xref-make-file-location file line column) length)))
2508
2509(defun eglot--workspace-symbols (pat &optional buffer)
2510 "Ask for :workspace/symbol on PAT, return list of formatted strings.
2511If BUFFER, switch to it before."
2512 (with-current-buffer (or buffer (current-buffer))
2513 (unless (eglot--server-capable :workspaceSymbolProvider)
2514 (eglot--error "This LSP server isn't a :workspaceSymbolProvider"))
2515 (mapcar
2516 (lambda (wss)
2517 (eglot--dbind ((WorkspaceSymbol) name containerName kind) wss
2518 (propertize
2519 (format "%s%s %s"
2520 (if (zerop (length containerName)) ""
2521 (concat (propertize containerName 'face 'shadow) " "))
2522 name
2523 (propertize (alist-get kind eglot--symbol-kind-names "Unknown")
2524 'face 'shadow))
2525 'eglot--lsp-workspaceSymbol wss)))
2526 (jsonrpc-request (eglot--current-server-or-lose) :workspace/symbol
2527 `(:query ,pat)))))
2528
2529(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql eglot)))
2530 "Yet another tricky connection between LSP and Elisp completion semantics."
2531 (let ((buf (current-buffer)) (cache eglot--workspace-symbols-cache))
2532 (cl-labels ((refresh (pat) (eglot--workspace-symbols pat buf))
2533 (lookup-1 (pat) ;; check cache, else refresh
2534 (let ((probe (gethash pat cache :missing)))
2535 (if (eq probe :missing) (puthash pat (refresh pat) cache)
2536 probe)))
2537 (lookup (pat)
2538 (let ((res (lookup-1 pat))
2539 (def (and (string= pat "") (gethash :default cache))))
2540 (append def res nil)))
2541 (score (c)
2542 (cl-getf (get-text-property
2543 0 'eglot--lsp-workspaceSymbol c)
2544 :score 0)))
2545 (lambda (string _pred action)
2546 (pcase action
2547 (`metadata `(metadata
2548 (cycle-sort-function
2549 . ,(lambda (completions)
2550 (cl-sort completions #'> :key #'score)))
2551 (category . eglot-indirection-joy)))
2552 (`(eglot--lsp-tryc . ,point) `(eglot--lsp-tryc . (,string . ,point)))
2553 (`(eglot--lsp-allc . ,_point) `(eglot--lsp-allc . ,(lookup string)))
2554 (_ nil))))))
2555
2556(defun eglot--recover-workspace-symbol-meta (string)
2557 "Search `eglot--workspace-symbols-cache' for rich entry of STRING."
2558 (catch 'found
2559 (maphash (lambda (_k v)
2560 (while (consp v)
2561 ;; Like mess? Ask minibuffer.el about improper lists.
2562 (when (equal (car v) string) (throw 'found (car v)))
2563 (setq v (cdr v))))
2564 eglot--workspace-symbols-cache)))
2565
2566(add-to-list 'completion-category-overrides
2567 '(eglot-indirection-joy (styles . (eglot--lsp-backend-style))))
2568
2569(cl-defmethod xref-backend-identifier-at-point ((_backend (eql eglot)))
2570 (let ((attempt
2571 (and (xref--prompt-p this-command)
2572 (puthash :default
2573 (ignore-errors
2574 (eglot--workspace-symbols (symbol-name (symbol-at-point))))
2575 eglot--workspace-symbols-cache))))
2576 (if attempt (car attempt) "LSP identifier at point")))
2577
2578(defvar eglot--lsp-xref-refs nil
2579 "`xref' objects for overriding `xref-backend-references''s.")
2580
2581(cl-defun eglot--lsp-xrefs-for-method (method &key extra-params capability)
2582 "Make `xref''s for METHOD, EXTRA-PARAMS, check CAPABILITY."
2583 (unless (eglot--server-capable
2584 (or capability
2585 (intern
2586 (format ":%sProvider"
2587 (cadr (split-string (symbol-name method)
2588 "/"))))))
2589 (eglot--error "Sorry, this server doesn't do %s" method))
2590 (let ((response
2591 (jsonrpc-request
2592 (eglot--current-server-or-lose)
2593 method (append (eglot--TextDocumentPositionParams) extra-params))))
2594 (eglot--collecting-xrefs (collect)
2595 (mapc
2596 (lambda (loc-or-loc-link)
2597 (let ((sym-name (symbol-name (symbol-at-point))))
2598 (eglot--dcase loc-or-loc-link
2599 (((LocationLink) targetUri targetSelectionRange)
2600 (collect (eglot--xref-make-match sym-name
2601 targetUri targetSelectionRange)))
2602 (((Location) uri range)
2603 (collect (eglot--xref-make-match sym-name
2604 uri range))))))
2605 (if (vectorp response) response (and response (list response)))))))
2606
2607(cl-defun eglot--lsp-xref-helper (method &key extra-params capability )
2608 "Helper for `eglot-find-declaration' & friends."
2609 (let ((eglot--lsp-xref-refs (eglot--lsp-xrefs-for-method
2610 method
2611 :extra-params extra-params
2612 :capability capability)))
2613 (if eglot--lsp-xref-refs
2614 (xref-find-references "LSP identifier at point.")
2615 (eglot--message "%s returned no references" method))))
2616
2617(defun eglot-find-declaration ()
2618 "Find declaration for SYM, the identifier at point."
2619 (interactive)
2620 (eglot--lsp-xref-helper :textDocument/declaration))
2621
2622(defun eglot-find-implementation ()
2623 "Find implementation for SYM, the identifier at point."
2624 (interactive)
2625 (eglot--lsp-xref-helper :textDocument/implementation))
2626
2627(defun eglot-find-typeDefinition ()
2628 "Find type definition for SYM, the identifier at point."
2629 (interactive)
2630 (eglot--lsp-xref-helper :textDocument/typeDefinition))
2631
2632(cl-defmethod xref-backend-definitions ((_backend (eql eglot)) id)
2633 (let ((probe (eglot--recover-workspace-symbol-meta id)))
2634 (if probe
2635 (eglot--dbind ((WorkspaceSymbol) name location)
2636 (get-text-property 0 'eglot--lsp-workspaceSymbol probe)
2637 (eglot--dbind ((Location) uri range) location
2638 (list (eglot--xref-make-match name uri range))))
2639 (eglot--lsp-xrefs-for-method :textDocument/definition))))
2640
2641(cl-defmethod xref-backend-references ((_backend (eql eglot)) _identifier)
2642 (or
2643 eglot--lsp-xref-refs
2644 (eglot--lsp-xrefs-for-method
2645 :textDocument/references :extra-params `(:context (:includeDeclaration t)))))
2646
2647(cl-defmethod xref-backend-apropos ((_backend (eql eglot)) pattern)
2648 (when (eglot--server-capable :workspaceSymbolProvider)
2649 (eglot--collecting-xrefs (collect)
2650 (mapc
2651 (eglot--lambda ((SymbolInformation) name location)
2652 (eglot--dbind ((Location) uri range) location
2653 (collect (eglot--xref-make-match name uri range))))
2654 (jsonrpc-request (eglot--current-server-or-lose)
2655 :workspace/symbol
2656 `(:query ,pattern))))))
2657
2658(defun eglot-format-buffer ()
2659 "Format contents of current buffer."
2660 (interactive)
2661 (eglot-format nil nil))
2662
2663(defun eglot-format (&optional beg end on-type-format)
2664 "Format region BEG END.
2665If either BEG or END is nil, format entire buffer.
2666Interactively, format active region, or entire buffer if region
2667is not active.
2668
2669If non-nil, ON-TYPE-FORMAT is a character just inserted at BEG
2670for which LSP on-type-formatting should be requested."
2671 (interactive (and (region-active-p) (list (region-beginning) (region-end))))
2672 (pcase-let ((`(,method ,cap ,args)
2673 (cond
2674 ((and beg on-type-format)
2675 `(:textDocument/onTypeFormatting
2676 :documentOnTypeFormattingProvider
2677 ,`(:position ,(eglot--pos-to-lsp-position beg)
2678 :ch ,(string on-type-format))))
2679 ((and beg end)
2680 `(:textDocument/rangeFormatting
2681 :documentRangeFormattingProvider
2682 (:range ,(list :start (eglot--pos-to-lsp-position beg)
2683 :end (eglot--pos-to-lsp-position end)))))
2684 (t
2685 '(:textDocument/formatting :documentFormattingProvider nil)))))
2686 (unless (eglot--server-capable cap)
2687 (eglot--error "Server can't format!"))
2688 (eglot--apply-text-edits
2689 (jsonrpc-request
2690 (eglot--current-server-or-lose)
2691 method
2692 (cl-list*
2693 :textDocument (eglot--TextDocumentIdentifier)
2694 :options (list :tabSize tab-width
2695 :insertSpaces (if indent-tabs-mode :json-false t)
2696 :insertFinalNewline (if require-final-newline t :json-false)
2697 :trimFinalNewlines (if delete-trailing-lines t :json-false))
2698 args)
2699 :deferred method))))
2700
2701(defun eglot-completion-at-point ()
2702 "Eglot's `completion-at-point' function."
2703 ;; Commit logs for this function help understand what's going on.
2704 (when-let (completion-capability (eglot--server-capable :completionProvider))
2705 (let* ((server (eglot--current-server-or-lose))
2706 (sort-completions
2707 (lambda (completions)
2708 (cl-sort completions
2709 #'string-lessp
2710 :key (lambda (c)
2711 (or (plist-get
2712 (get-text-property 0 'eglot--lsp-item c)
2713 :sortText)
2714 "")))))
2715 (metadata `(metadata (category . eglot)
2716 (display-sort-function . ,sort-completions)))
2717 resp items (cached-proxies :none)
2718 (proxies
2719 (lambda ()
2720 (if (listp cached-proxies) cached-proxies
2721 (setq resp
2722 (jsonrpc-request server
2723 :textDocument/completion
2724 (eglot--CompletionParams)
2725 :deferred :textDocument/completion
2726 :cancel-on-input t))
2727 (setq items (append
2728 (if (vectorp resp) resp (plist-get resp :items))
2729 nil))
2730 (setq cached-proxies
2731 (mapcar
2732 (jsonrpc-lambda
2733 (&rest item &key label insertText insertTextFormat
2734 &allow-other-keys)
2735 (let ((proxy
2736 (cond ((and (eql insertTextFormat 2)
2737 (eglot--snippet-expansion-fn))
2738 (string-trim-left label))
2739 ((and insertText
2740 (not (string-empty-p insertText)))
2741 insertText)
2742 (t
2743 (string-trim-left label)))))
2744 (unless (zerop (length proxy))
2745 (put-text-property 0 1 'eglot--lsp-item item proxy))
2746 proxy))
2747 items)))))
2748 (resolved (make-hash-table))
2749 (resolve-maybe
2750 ;; Maybe completion/resolve JSON object `lsp-comp' into
2751 ;; another JSON object, if at all possible. Otherwise,
2752 ;; just return lsp-comp.
2753 (lambda (lsp-comp)
2754 (or (gethash lsp-comp resolved)
2755 (setf (gethash lsp-comp resolved)
2756 (if (and (eglot--server-capable :completionProvider
2757 :resolveProvider)
2758 (plist-get lsp-comp :data))
2759 (jsonrpc-request server :completionItem/resolve
2760 lsp-comp :cancel-on-input t)
2761 lsp-comp)))))
2762 (bounds (bounds-of-thing-at-point 'symbol)))
2763 (list
2764 (or (car bounds) (point))
2765 (or (cdr bounds) (point))
2766 (lambda (probe pred action)
2767 (cond
2768 ((eq action 'metadata) metadata) ; metadata
2769 ((eq action 'lambda) ; test-completion
2770 (test-completion probe (funcall proxies)))
2771 ((eq (car-safe action) 'boundaries) nil) ; boundaries
2772 ((null action) ; try-completion
2773 (try-completion probe (funcall proxies)))
2774 ((eq action t) ; all-completions
2775 (all-completions
2776 ""
2777 (funcall proxies)
2778 (lambda (proxy)
2779 (let* ((item (get-text-property 0 'eglot--lsp-item proxy))
2780 (filterText (plist-get item :filterText)))
2781 (and (or (null pred) (funcall pred proxy))
2782 (string-prefix-p
2783 probe (or filterText proxy) completion-ignore-case))))))))
2784 :annotation-function
2785 (lambda (proxy)
2786 (eglot--dbind ((CompletionItem) detail kind)
2787 (get-text-property 0 'eglot--lsp-item proxy)
2788 (let* ((detail (and (stringp detail)
2789 (not (string= detail ""))
2790 detail))
2791 (annotation
2792 (or detail
2793 (cdr (assoc kind eglot--kind-names)))))
2794 (when annotation
2795 (concat " "
2796 (propertize annotation
2797 'face 'font-lock-function-name-face))))))
2798 :company-kind
2799 ;; Associate each lsp-item with a lsp-kind symbol.
2800 (lambda (proxy)
2801 (when-let* ((lsp-item (get-text-property 0 'eglot--lsp-item proxy))
2802 (kind (alist-get (plist-get lsp-item :kind)
2803 eglot--kind-names)))
2804 (intern (downcase kind))))
2805 :company-deprecated
2806 (lambda (proxy)
2807 (when-let ((lsp-item (get-text-property 0 'eglot--lsp-item proxy)))
2808 (or (seq-contains-p (plist-get lsp-item :tags)
2809 1)
2810 (eq t (plist-get lsp-item :deprecated)))))
2811 :company-docsig
2812 ;; FIXME: autoImportText is specific to the pyright language server
2813 (lambda (proxy)
2814 (when-let* ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy))
2815 (data (plist-get (funcall resolve-maybe lsp-comp) :data))
2816 (import-text (plist-get data :autoImportText)))
2817 import-text))
2818 :company-doc-buffer
2819 (lambda (proxy)
2820 (let* ((documentation
2821 (let ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy)))
2822 (plist-get (funcall resolve-maybe lsp-comp) :documentation)))
2823 (formatted (and documentation
2824 (eglot--format-markup documentation))))
2825 (when formatted
2826 (with-current-buffer (get-buffer-create " *eglot doc*")
2827 (erase-buffer)
2828 (insert formatted)
2829 (current-buffer)))))
2830 :company-require-match 'never
2831 :company-prefix-length
2832 (save-excursion
2833 (when (car bounds) (goto-char (car bounds)))
2834 (when (listp completion-capability)
2835 (looking-back
2836 (regexp-opt
2837 (cl-coerce (cl-getf completion-capability :triggerCharacters) 'list))
2838 (line-beginning-position))))
2839 :exit-function
2840 (lambda (proxy status)
2841 (when (memq status '(finished exact))
2842 ;; To assist in using this whole `completion-at-point'
2843 ;; function inside `completion-in-region', ensure the exit
2844 ;; function runs in the buffer where the completion was
2845 ;; triggered from. This should probably be in Emacs itself.
2846 ;; (github#505)
2847 (with-current-buffer (if (minibufferp)
2848 (window-buffer (minibuffer-selected-window))
2849 (current-buffer))
2850 (eglot--dbind ((CompletionItem) insertTextFormat
2851 insertText textEdit additionalTextEdits label)
2852 (funcall
2853 resolve-maybe
2854 (or (get-text-property 0 'eglot--lsp-item proxy)
2855 ;; When selecting from the *Completions*
2856 ;; buffer, `proxy' won't have any properties.
2857 ;; A lookup should fix that (github#148)
2858 (get-text-property
2859 0 'eglot--lsp-item
2860 (cl-find proxy (funcall proxies) :test #'string=))))
2861 (let ((snippet-fn (and (eql insertTextFormat 2)
2862 (eglot--snippet-expansion-fn))))
2863 (cond (textEdit
2864 ;; Undo (yes, undo) the newly inserted completion.
2865 ;; If before completion the buffer was "foo.b" and
2866 ;; now is "foo.bar", `proxy' will be "bar". We
2867 ;; want to delete only "ar" (`proxy' minus the
2868 ;; symbol whose bounds we've calculated before)
2869 ;; (github#160).
2870 (delete-region (+ (- (point) (length proxy))
2871 (if bounds
2872 (- (cdr bounds) (car bounds))
2873 0))
2874 (point))
2875 (eglot--dbind ((TextEdit) range newText) textEdit
2876 (pcase-let ((`(,beg . ,end)
2877 (eglot--range-region range)))
2878 (delete-region beg end)
2879 (goto-char beg)
2880 (funcall (or snippet-fn #'insert) newText))))
2881 (snippet-fn
2882 ;; A snippet should be inserted, but using plain
2883 ;; `insertText'. This requires us to delete the
2884 ;; whole completion, since `insertText' is the full
2885 ;; completion's text.
2886 (delete-region (- (point) (length proxy)) (point))
2887 (funcall snippet-fn (or insertText label))))
2888 (when (cl-plusp (length additionalTextEdits))
2889 (eglot--apply-text-edits additionalTextEdits)))
2890 (eglot--signal-textDocument/didChange)
2891 (eldoc)))))))))
2892
2893(defun eglot--hover-info (contents &optional _range)
2894 (mapconcat #'eglot--format-markup
2895 (if (vectorp contents) contents (list contents)) "\n"))
2896
2897(defun eglot--sig-info (sigs active-sig sig-help-active-param)
2898 (cl-loop
2899 for (sig . moresigs) on (append sigs nil) for i from 0
2900 concat
2901 (eglot--dbind ((SignatureInformation) label documentation parameters activeParameter) sig
2902 (with-temp-buffer
2903 (save-excursion (insert label))
2904 (let ((active-param (or activeParameter sig-help-active-param))
2905 params-start params-end)
2906 ;; Ad-hoc attempt to parse label as <name>(<params>)
2907 (when (looking-at "\\([^(]+\\)(\\([^)]+\\))")
2908 (setq params-start (match-beginning 2) params-end (match-end 2))
2909 (add-face-text-property (match-beginning 1) (match-end 1)
2910 'font-lock-function-name-face))
2911 (when (eql i active-sig)
2912 ;; Decide whether to add one-line-summary to signature line
2913 (when (and (stringp documentation)
2914 (string-match "[[:space:]]*\\([^.\r\n]+[.]?\\)"
2915 documentation))
2916 (setq documentation (match-string 1 documentation))
2917 (unless (string-prefix-p (string-trim documentation) label)
2918 (goto-char (point-max))
2919 (insert ": " (eglot--format-markup documentation))))
2920 ;; Decide what to do with the active parameter...
2921 (when (and (eql i active-sig) active-param
2922 (< -1 active-param (length parameters)))
2923 (eglot--dbind ((ParameterInformation) label documentation)
2924 (aref parameters active-param)
2925 ;; ...perhaps highlight it in the formals list
2926 (when params-start
2927 (goto-char params-start)
2928 (pcase-let
2929 ((`(,beg ,end)
2930 (if (stringp label)
2931 (let ((case-fold-search nil))
2932 (and (re-search-forward
2933 (concat "\\<" (regexp-quote label) "\\>")
2934 params-end t)
2935 (list (match-beginning 0) (match-end 0))))
2936 (mapcar #'1+ (append label nil)))))
2937 (if (and beg end)
2938 (add-face-text-property
2939 beg end
2940 'eldoc-highlight-function-argument))))
2941 ;; ...and/or maybe add its doc on a line by its own.
2942 (when documentation
2943 (goto-char (point-max))
2944 (insert "\n"
2945 (propertize
2946 (if (stringp label)
2947 label
2948 (apply #'buffer-substring (mapcar #'1+ label)))
2949 'face 'eldoc-highlight-function-argument)
2950 ": " (eglot--format-markup documentation))))))
2951 (buffer-string))))
2952 when moresigs concat "\n"))
2953
2954(defun eglot-signature-eldoc-function (cb)
2955 "A member of `eldoc-documentation-functions', for signatures."
2956 (when (eglot--server-capable :signatureHelpProvider)
2957 (let ((buf (current-buffer)))
2958 (jsonrpc-async-request
2959 (eglot--current-server-or-lose)
2960 :textDocument/signatureHelp (eglot--TextDocumentPositionParams)
2961 :success-fn
2962 (eglot--lambda ((SignatureHelp)
2963 signatures activeSignature activeParameter)
2964 (eglot--when-buffer-window buf
2965 (funcall cb
2966 (unless (seq-empty-p signatures)
2967 (eglot--sig-info signatures
2968 activeSignature
2969 activeParameter)))))
2970 :deferred :textDocument/signatureHelp))
2971 t))
2972
2973(defun eglot-hover-eldoc-function (cb)
2974 "A member of `eldoc-documentation-functions', for hover."
2975 (when (eglot--server-capable :hoverProvider)
2976 (let ((buf (current-buffer)))
2977 (jsonrpc-async-request
2978 (eglot--current-server-or-lose)
2979 :textDocument/hover (eglot--TextDocumentPositionParams)
2980 :success-fn (eglot--lambda ((Hover) contents range)
2981 (eglot--when-buffer-window buf
2982 (let ((info (unless (seq-empty-p contents)
2983 (eglot--hover-info contents range))))
2984 (funcall cb info :buffer t))))
2985 :deferred :textDocument/hover))
2986 (eglot--highlight-piggyback cb)
2987 t))
2988
2989(defvar eglot--highlights nil "Overlays for textDocument/documentHighlight.")
2990
2991(defun eglot--highlight-piggyback (_cb)
2992 "Request and handle `:textDocument/documentHighlight'."
2993 ;; FIXME: Obviously, this is just piggy backing on eldoc's calls for
2994 ;; convenience, as shown by the fact that we just ignore cb.
2995 (let ((buf (current-buffer)))
2996 (when (eglot--server-capable :documentHighlightProvider)
2997 (jsonrpc-async-request
2998 (eglot--current-server-or-lose)
2999 :textDocument/documentHighlight (eglot--TextDocumentPositionParams)
3000 :success-fn
3001 (lambda (highlights)
3002 (mapc #'delete-overlay eglot--highlights)
3003 (setq eglot--highlights
3004 (eglot--when-buffer-window buf
3005 (mapcar
3006 (eglot--lambda ((DocumentHighlight) range)
3007 (pcase-let ((`(,beg . ,end)
3008 (eglot--range-region range)))
3009 (let ((ov (make-overlay beg end)))
3010 (overlay-put ov 'face 'eglot-highlight-symbol-face)
3011 (overlay-put ov 'modification-hooks
3012 `(,(lambda (o &rest _) (delete-overlay o))))
3013 ov)))
3014 highlights))))
3015 :deferred :textDocument/documentHighlight)
3016 nil)))
3017
3018(defun eglot-imenu ()
3019 "Eglot's `imenu-create-index-function'.
3020Returns a list as described in docstring of `imenu--index-alist'."
3021 (cl-labels
3022 ((unfurl (obj)
3023 (eglot--dcase obj
3024 (((SymbolInformation)) (list obj))
3025 (((DocumentSymbol) name children)
3026 (cons obj
3027 (mapcar
3028 (lambda (c)
3029 (plist-put
3030 c :containerName
3031 (let ((existing (plist-get c :containerName)))
3032 (if existing (format "%s::%s" name existing)
3033 name))))
3034 (mapcan #'unfurl children)))))))
3035 (mapcar
3036 (pcase-lambda (`(,kind . ,objs))
3037 (cons
3038 (alist-get kind eglot--symbol-kind-names "Unknown")
3039 (mapcan (pcase-lambda (`(,container . ,objs))
3040 (let ((elems (mapcar
3041 (lambda (obj)
3042 (cons (plist-get obj :name)
3043 (car (eglot--range-region
3044 (eglot--dcase obj
3045 (((SymbolInformation) location)
3046 (plist-get location :range))
3047 (((DocumentSymbol) selectionRange)
3048 selectionRange))))))
3049 objs)))
3050 (if container (list (cons container elems)) elems)))
3051 (seq-group-by
3052 (lambda (e) (plist-get e :containerName)) objs))))
3053 (seq-group-by
3054 (lambda (obj) (plist-get obj :kind))
3055 (mapcan #'unfurl
3056 (jsonrpc-request (eglot--current-server-or-lose)
3057 :textDocument/documentSymbol
3058 `(:textDocument
3059 ,(eglot--TextDocumentIdentifier))
3060 :cancel-on-input non-essential))))))
3061
3062(defun eglot--apply-text-edits (edits &optional version)
3063 "Apply EDITS for current buffer if at VERSION, or if it's nil."
3064 (unless (or (not version) (equal version eglot--versioned-identifier))
3065 (jsonrpc-error "Edits on `%s' require version %d, you have %d"
3066 (current-buffer) version eglot--versioned-identifier))
3067 (atomic-change-group
3068 (let* ((change-group (prepare-change-group))
3069 (howmany (length edits))
3070 (reporter (make-progress-reporter
3071 (format "[eglot] applying %s edits to `%s'..."
3072 howmany (current-buffer))
3073 0 howmany))
3074 (done 0))
3075 (mapc (pcase-lambda (`(,newText ,beg . ,end))
3076 (let ((source (current-buffer)))
3077 (with-temp-buffer
3078 (insert newText)
3079 (let ((temp (current-buffer)))
3080 (with-current-buffer source
3081 (save-excursion
3082 (save-restriction
3083 (narrow-to-region beg end)
3084
3085 ;; On emacs versions < 26.2,
3086 ;; `replace-buffer-contents' is buggy - it calls
3087 ;; change functions with invalid arguments - so we
3088 ;; manually call the change functions here.
3089 ;;
3090 ;; See emacs bugs #32237, #32278:
3091 ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32237
3092 ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32278
3093 (let ((inhibit-modification-hooks t)
3094 (length (- end beg))
3095 (beg (marker-position beg))
3096 (end (marker-position end)))
3097 (run-hook-with-args 'before-change-functions
3098 beg end)
3099 (replace-buffer-contents temp)
3100 (run-hook-with-args 'after-change-functions
3101 beg (+ beg (length newText))
3102 length))))
3103 (progress-reporter-update reporter (cl-incf done)))))))
3104 (mapcar (eglot--lambda ((TextEdit) range newText)
3105 (cons newText (eglot--range-region range 'markers)))
3106 (reverse edits)))
3107 (undo-amalgamate-change-group change-group)
3108 (progress-reporter-done reporter))))
3109
3110(defun eglot--apply-workspace-edit (wedit &optional confirm)
3111 "Apply the workspace edit WEDIT. If CONFIRM, ask user first."
3112 (eglot--dbind ((WorkspaceEdit) changes documentChanges) wedit
3113 (let ((prepared
3114 (mapcar (eglot--lambda ((TextDocumentEdit) textDocument edits)
3115 (eglot--dbind ((VersionedTextDocumentIdentifier) uri version)
3116 textDocument
3117 (list (eglot--uri-to-path uri) edits version)))
3118 documentChanges)))
3119 (unless (and changes documentChanges)
3120 ;; We don't want double edits, and some servers send both
3121 ;; changes and documentChanges. This unless ensures that we
3122 ;; prefer documentChanges over changes.
3123 (cl-loop for (uri edits) on changes by #'cddr
3124 do (push (list (eglot--uri-to-path uri) edits) prepared)))
3125 (if (or confirm
3126 (cl-notevery #'find-buffer-visiting
3127 (mapcar #'car prepared)))
3128 (unless (y-or-n-p
3129 (format "[eglot] Server wants to edit:\n %s\n Proceed? "
3130 (mapconcat #'identity (mapcar #'car prepared) "\n ")))
3131 (jsonrpc-error "User cancelled server edit")))
3132 (cl-loop for edit in prepared
3133 for (path edits version) = edit
3134 do (with-current-buffer (find-file-noselect path)
3135 (eglot--apply-text-edits edits version))
3136 finally (eldoc) (eglot--message "Edit successful!")))))
3137
3138(defun eglot-rename (newname)
3139 "Rename the current symbol to NEWNAME."
3140 (interactive
3141 (list (read-from-minibuffer
3142 (format "Rename `%s' to: " (or (thing-at-point 'symbol t)
3143 "unknown symbol"))
3144 nil nil nil nil
3145 (symbol-name (symbol-at-point)))))
3146 (unless (eglot--server-capable :renameProvider)
3147 (eglot--error "Server can't rename!"))
3148 (eglot--apply-workspace-edit
3149 (jsonrpc-request (eglot--current-server-or-lose)
3150 :textDocument/rename `(,@(eglot--TextDocumentPositionParams)
3151 :newName ,newname))
3152 current-prefix-arg))
3153
3154(defun eglot--region-bounds ()
3155 "Region bounds if active, else bounds of things at point."
3156 (if (use-region-p) `(,(region-beginning) ,(region-end))
3157 (let ((boftap (bounds-of-thing-at-point 'sexp)))
3158 (list (car boftap) (cdr boftap)))))
3159
3160(defun eglot-code-actions (beg &optional end action-kind interactive)
3161 "Find LSP code actions of type ACTION-KIND between BEG and END.
3162Interactively, offer to execute them.
3163If ACTION-KIND is nil, consider all kinds of actions.
3164Interactively, default BEG and END to region's bounds else BEG is
3165point and END is nil, which results in a request for code actions
3166at point. With prefix argument, prompt for ACTION-KIND."
3167 (interactive
3168 `(,@(eglot--region-bounds)
3169 ,(and current-prefix-arg
3170 (completing-read "[eglot] Action kind: "
3171 '("quickfix" "refactor.extract" "refactor.inline"
3172 "refactor.rewrite" "source.organizeImports")))
3173 t))
3174 (unless (or (not interactive)
3175 (eglot--server-capable :codeActionProvider))
3176 (eglot--error "Server can't execute code actions!"))
3177 (let* ((server (eglot--current-server-or-lose))
3178 (actions
3179 (jsonrpc-request
3180 server
3181 :textDocument/codeAction
3182 (list :textDocument (eglot--TextDocumentIdentifier)
3183 :range (list :start (eglot--pos-to-lsp-position beg)
3184 :end (eglot--pos-to-lsp-position end))
3185 :context
3186 `(:diagnostics
3187 [,@(cl-loop for diag in (flymake-diagnostics beg end)
3188 when (cdr (assoc 'eglot-lsp-diag
3189 (eglot--diag-data diag)))
3190 collect it)]
3191 ,@(when action-kind `(:only [,action-kind]))))
3192 :deferred t))
3193 ;; Redo filtering, in case the `:only' didn't go through.
3194 (actions (cl-loop for a across actions
3195 when (or (not action-kind)
3196 (equal action-kind (plist-get a :kind)))
3197 collect a)))
3198 (if interactive
3199 (eglot--read-execute-code-action actions server action-kind)
3200 actions)))
3201
3202(defun eglot--read-execute-code-action (actions server &optional action-kind)
3203 "Helper for interactive calls to `eglot-code-actions'"
3204 (let* ((menu-items
3205 (or (cl-loop for a in actions
3206 collect (cons (plist-get a :title) a))
3207 (apply #'eglot--error
3208 (if action-kind `("No \"%s\" code actions here" ,action-kind)
3209 `("No code actions here")))))
3210 (preferred-action (cl-find-if
3211 (lambda (menu-item)
3212 (plist-get (cdr menu-item) :isPreferred))
3213 menu-items))
3214 (default-action (car (or preferred-action (car menu-items))))
3215 (chosen (if (and action-kind (null (cadr menu-items)))
3216 (cdr (car menu-items))
3217 (if (listp last-nonmenu-event)
3218 (x-popup-menu last-nonmenu-event `("Eglot code actions:"
3219 ("dummy" ,@menu-items)))
3220 (cdr (assoc (completing-read
3221 (format "[eglot] Pick an action (default %s): "
3222 default-action)
3223 menu-items nil t nil nil default-action)
3224 menu-items))))))
3225 (eglot--dcase chosen
3226 (((Command) command arguments)
3227 (eglot-execute-command server (intern command) arguments))
3228 (((CodeAction) edit command)
3229 (when edit (eglot--apply-workspace-edit edit))
3230 (when command
3231 (eglot--dbind ((Command) command arguments) command
3232 (eglot-execute-command server (intern command) arguments)))))))
3233
3234(defmacro eglot--code-action (name kind)
3235 "Define NAME to execute KIND code action."
3236 `(defun ,name (beg &optional end)
3237 ,(format "Execute `%s' code actions between BEG and END." kind)
3238 (interactive (eglot--region-bounds))
3239 (eglot-code-actions beg end ,kind)))
3240
3241(eglot--code-action eglot-code-action-organize-imports "source.organizeImports")
3242(eglot--code-action eglot-code-action-extract "refactor.extract")
3243(eglot--code-action eglot-code-action-inline "refactor.inline")
3244(eglot--code-action eglot-code-action-rewrite "refactor.rewrite")
3245(eglot--code-action eglot-code-action-quickfix "quickfix")
3246
3247
3248;;; Dynamic registration
3249;;;
3250(cl-defmethod eglot-register-capability
3251 (server (method (eql workspace/didChangeWatchedFiles)) id &key watchers)
3252 "Handle dynamic registration of workspace/didChangeWatchedFiles."
3253 (eglot-unregister-capability server method id)
3254 (let* (success
3255 (globs (mapcar
3256 (eglot--lambda ((FileSystemWatcher) globPattern)
3257 (eglot--glob-compile globPattern t t))
3258 watchers))
3259 (dirs-to-watch
3260 (delete-dups (mapcar #'file-name-directory
3261 (project-files
3262 (eglot--project server))))))
3263 (cl-labels
3264 ((handle-event
3265 (event)
3266 (pcase-let ((`(,desc ,action ,file ,file1) event))
3267 (cond
3268 ((and (memq action '(created changed deleted))
3269 (cl-find file globs :test (lambda (f g) (funcall g f))))
3270 (jsonrpc-notify
3271 server :workspace/didChangeWatchedFiles
3272 `(:changes ,(vector `(:uri ,(eglot--path-to-uri file)
3273 :type ,(cl-case action
3274 (created 1)
3275 (changed 2)
3276 (deleted 3)))))))
3277 ((eq action 'renamed)
3278 (handle-event `(,desc 'deleted ,file))
3279 (handle-event `(,desc 'created ,file1)))))))
3280 (unwind-protect
3281 (progn
3282 (dolist (dir dirs-to-watch)
3283 (push (file-notify-add-watch dir '(change) #'handle-event)
3284 (gethash id (eglot--file-watches server))))
3285 (setq
3286 success
3287 `(:message ,(format "OK, watching %s directories in %s watchers"
3288 (length dirs-to-watch) (length watchers)))))
3289 (unless success
3290 (eglot-unregister-capability server method id))))))
3291
3292(cl-defmethod eglot-unregister-capability
3293 (server (_method (eql workspace/didChangeWatchedFiles)) id)
3294 "Handle dynamic unregistration of workspace/didChangeWatchedFiles."
3295 (mapc #'file-notify-rm-watch (gethash id (eglot--file-watches server)))
3296 (remhash id (eglot--file-watches server))
3297 (list t "OK"))
3298
3299
3300;;; Glob heroics
3301;;;
3302(defun eglot--glob-parse (glob)
3303 "Compute list of (STATE-SYM EMITTER-FN PATTERN)."
3304 (with-temp-buffer
3305 (save-excursion (insert glob))
3306 (cl-loop
3307 with grammar = '((:** "\\*\\*/?" eglot--glob-emit-**)
3308 (:* "\\*" eglot--glob-emit-*)
3309 (:? "\\?" eglot--glob-emit-?)
3310 (:{} "{[^][*{}]+}" eglot--glob-emit-{})
3311 (:range "\\[\\^?[^][/,*{}]+\\]" eglot--glob-emit-range)
3312 (:literal "[^][,*?{}]+" eglot--glob-emit-self))
3313 until (eobp)
3314 collect (cl-loop
3315 for (_token regexp emitter) in grammar
3316 thereis (and (re-search-forward (concat "\\=" regexp) nil t)
3317 (list (cl-gensym "state-") emitter (match-string 0)))
3318 finally (error "Glob '%s' invalid at %s" (buffer-string) (point))))))
3319
3320(defun eglot--glob-compile (glob &optional byte-compile noerror)
3321 "Convert GLOB into Elisp function. Maybe BYTE-COMPILE it.
3322If NOERROR, return predicate, else erroring function."
3323 (let* ((states (eglot--glob-parse glob))
3324 (body `(with-current-buffer (get-buffer-create " *eglot-glob-matcher*")
3325 (erase-buffer)
3326 (save-excursion (insert string))
3327 (cl-labels ,(cl-loop for (this that) on states
3328 for (self emit text) = this
3329 for next = (or (car that) 'eobp)
3330 collect (funcall emit text self next))
3331 (or (,(caar states))
3332 (error "Glob done but more unmatched text: '%s'"
3333 (buffer-substring (point) (point-max)))))))
3334 (form `(lambda (string) ,(if noerror `(ignore-errors ,body) body))))
3335 (if byte-compile (byte-compile form) form)))
3336
3337(defun eglot--glob-emit-self (text self next)
3338 `(,self () (re-search-forward ,(concat "\\=" (regexp-quote text))) (,next)))
3339
3340(defun eglot--glob-emit-** (_ self next)
3341 `(,self () (or (ignore-errors (save-excursion (,next)))
3342 (and (re-search-forward "\\=/?[^/]+/?") (,self)))))
3343
3344(defun eglot--glob-emit-* (_ self next)
3345 `(,self () (re-search-forward "\\=[^/]")
3346 (or (ignore-errors (save-excursion (,next))) (,self))))
3347
3348(defun eglot--glob-emit-? (_ self next)
3349 `(,self () (re-search-forward "\\=[^/]") (,next)))
3350
3351(defun eglot--glob-emit-{} (arg self next)
3352 (let ((alternatives (split-string (substring arg 1 (1- (length arg))) ",")))
3353 `(,self ()
3354 (or (re-search-forward ,(concat "\\=" (regexp-opt alternatives)) nil t)
3355 (error "Failed matching any of %s" ',alternatives))
3356 (,next))))
3357
3358(defun eglot--glob-emit-range (arg self next)
3359 (when (eq ?! (aref arg 1)) (aset arg 1 ?^))
3360 `(,self () (re-search-forward ,(concat "\\=" arg)) (,next)))
3361
3362
3363;;; List connections mode
3364
3365(define-derived-mode eglot-list-connections-mode tabulated-list-mode
3366 "" "Eglot mode for listing server connections
3367\\{eglot-list-connections-mode-map}"
3368 (setq-local tabulated-list-format
3369 `[("Language server" 16) ("Project name" 16) ("Modes handled" 16)])
3370 (tabulated-list-init-header))
3371
3372(defun eglot-list-connections ()
3373 "List currently active Eglot connections."
3374 (interactive)
3375 (with-current-buffer
3376 (get-buffer-create "*EGLOT connections*")
3377 (let ((inhibit-read-only t))
3378 (erase-buffer)
3379 (eglot-list-connections-mode)
3380 (setq-local tabulated-list-entries
3381 (mapcar
3382 (lambda (server)
3383 (list server
3384 `[,(or (plist-get (eglot--server-info server) :name)
3385 (jsonrpc-name server))
3386 ,(eglot-project-nickname server)
3387 ,(mapconcat #'symbol-name
3388 (eglot--major-modes server)
3389 ", ")]))
3390 (cl-reduce #'append
3391 (hash-table-values eglot--servers-by-project))))
3392 (revert-buffer)
3393 (pop-to-buffer (current-buffer)))))
3394
3395
3396;;; Hacks
3397;;;
3398;; FIXME: Although desktop.el compatibility is Emacs bug#56407, the
3399;; optimal solution agreed to there is a bit more work than what I
3400;; have time to right now. See
3401;; e.g. https://debbugs.gnu.org/cgi/bugreport.cgi?bug=bug%2356407#68.
3402;; For now, just use `with-eval-after-load'
3403(with-eval-after-load 'desktop
3404 (add-to-list 'desktop-minor-mode-handlers '(eglot--managed-mode . ignore)))
3405
3406
3407;;; Obsolete
3408;;;
3409
3410(make-obsolete-variable 'eglot--managed-mode-hook
3411 'eglot-managed-mode-hook "1.6")
3412(provide 'eglot)
3413
3414
3415;;; Backend completion
3416
3417;; Written by Stefan Monnier circa 2016. Something to move to
3418;; minibuffer.el "ASAP" (with all the `eglot--lsp-' replaced by
3419;; something else. The very same code already in SLY and stable for a
3420;; long time.
3421
3422;; This "completion style" delegates all the work to the "programmable
3423;; completion" table which is then free to implement its own
3424;; completion style. Typically this is used to take advantage of some
3425;; external tool which already has its own completion system and
3426;; doesn't give you efficient access to the prefix completion needed
3427;; by other completion styles. The table should recognize the symbols
3428;; 'eglot--lsp-tryc and 'eglot--lsp-allc as ACTION, reply with
3429;; (eglot--lsp-tryc COMP...) or (eglot--lsp-allc . (STRING . POINT)),
3430;; accordingly. tryc/allc names made akward/recognizable on purpose.
3431
3432(add-to-list 'completion-styles-alist
3433 '(eglot--lsp-backend-style
3434 eglot--lsp-backend-style-try-completion
3435 eglot--lsp-backend-style-all-completions
3436 "Ad-hoc completion style provided by the completion table."))
3437
3438(defun eglot--lsp-backend-style-call (op string table pred point)
3439 (when (functionp table)
3440 (let ((res (funcall table string pred (cons op point))))
3441 (when (eq op (car-safe res))
3442 (cdr res)))))
3443
3444(defun eglot--lsp-backend-style-try-completion (string table pred point)
3445 (eglot--lsp-backend-style-call 'eglot--lsp-tryc string table pred point))
3446
3447(defun eglot--lsp-backend-style-all-completions (string table pred point)
3448 (eglot--lsp-backend-style-call 'eglot--lsp-allc string table pred point))
3449
3450
3451;; Local Variables:
3452;; bug-reference-bug-regexp: "\\(github#\\([0-9]+\\)\\)"
3453;; bug-reference-url-format: "https://github.com/joaotavora/eglot/issues/%s"
3454;; checkdoc-force-docstrings-flag: nil
3455;; End:
3456
3457;;; eglot.el ends here