diff options
| author | João Távora | 2022-10-20 13:50:09 +0100 |
|---|---|---|
| committer | João Távora | 2022-10-20 13:50:09 +0100 |
| commit | 83fbda715973f57dc49fe002d255ecaff8273154 (patch) | |
| tree | ab89ec338df6c9b818ba65fb2fcc1fd1e60d85b7 | |
| parent | 6f3ade1c08c6cbf56c0dc0d12e9508c261eb42bf (diff) | |
| parent | 8b3a7003274de7b184b71c4552e6c4518948bcfe (diff) | |
| download | emacs-83fbda715973f57dc49fe002d255ecaff8273154.tar.gz emacs-83fbda715973f57dc49fe002d255ecaff8273154.zip | |
Merge branch 'feature/eglot2emacs'
| -rw-r--r-- | doc/misc/Makefile.in | 2 | ||||
| -rw-r--r-- | doc/misc/eglot.texi | 1129 | ||||
| -rw-r--r-- | lisp/info-look.el | 1 | ||||
| -rw-r--r-- | lisp/progmodes/eglot.el | 3457 |
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. |
| 70 | INFO_COMMON = auth autotype bovine calc ccmode cl \ | 70 | INFO_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 | ||
| 11 | This manual is for Eglot, the Emacs LSP client. | ||
| 12 | |||
| 13 | Copyright @copyright{} 2022 Free Software Foundation, Inc. | ||
| 14 | |||
| 15 | @quotation | ||
| 16 | Permission is granted to copy, distribute and/or modify this document | ||
| 17 | under the terms of the GNU Free Documentation License, Version 1.3 or | ||
| 18 | any later version published by the Free Software Foundation; with no | ||
| 19 | Invariant Sections, with the Front-Cover Texts being ``A GNU Manual'', | ||
| 20 | and with the Back-Cover Texts as in (a) below. A copy of the license | ||
| 21 | is 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 | ||
| 24 | modify 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 | ||
| 63 | Eglot 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{ | ||
| 66 | A @dfn{polyglot} is a | ||
| 67 | person who is able to use several languages. | ||
| 68 | } Eglot provides infrastructure and a set of commands for enriching | ||
| 69 | the source code editing capabilities of Emacs via LSP. LSP is a | ||
| 70 | standardized communications protocol between source code editors (such | ||
| 71 | as Emacs) and language servers, programs external to Emacs for | ||
| 72 | analyzing source code on behalf of Emacs. The protocol allows Emacs | ||
| 73 | to receive various source code services from the server, such as | ||
| 74 | description and location of functions calls, types of variables, class | ||
| 75 | definitions, syntactic errors, etc. This way, Emacs doesn't need to | ||
| 76 | implement the language-specific parsing and analysis capabilities in | ||
| 77 | its own code, but is still capable of providing sophisticated editing | ||
| 78 | features that rely on such capabilities, such as automatic code | ||
| 79 | completion, go-to definition of function/class, documentation of | ||
| 80 | symbol at-point, refactoring, on-the-fly diagnostics, and more. | ||
| 81 | |||
| 82 | Eglot itself is completely language-agnostic, but it can support any | ||
| 83 | programming language for which there is a language server and an Emacs | ||
| 84 | major mode. | ||
| 85 | |||
| 86 | This 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 | |||
| 105 | This chapter provides concise instructions for setting up and using | ||
| 106 | Eglot with your programming project in common usage scenarios. For | ||
| 107 | more detailed instructions regarding Eglot setup, @pxref{Eglot and LSP | ||
| 108 | Servers}. @xref{Using Eglot}, for detailed description of using Eglot, | ||
| 109 | and see @ref{Customizing Eglot}, for adapting Eglot to less common use | ||
| 110 | patterns. | ||
| 111 | |||
| 112 | Here's how to start using Eglot with your programming project: | ||
| 113 | |||
| 114 | @enumerate | ||
| 115 | @item | ||
| 116 | Select and install a language server. | ||
| 117 | |||
| 118 | Eglot comes pre-configured with many popular language servers, see the | ||
| 119 | value of @code{eglot-server-programs}. If the server(s) mentioned | ||
| 120 | there satisfy your needs for the programming language(s) with which | ||
| 121 | you want to use Eglot, you just need to make sure those servers are | ||
| 122 | installed on your system. Alternatively, install one or more servers | ||
| 123 | of your choice and add them to the value of | ||
| 124 | @code{eglot-server-programs}, as described in @ref{Setting Up LSP | ||
| 125 | Servers}. | ||
| 126 | |||
| 127 | @item | ||
| 128 | Turn on Eglot for your project. | ||
| 129 | |||
| 130 | To start using Eglot for a project, type @kbd{M-x eglot @key{RET}} in | ||
| 131 | a buffer visiting any file that belongs to the project. This starts | ||
| 132 | the language server configured for the programming language of that | ||
| 133 | buffer, and causes Eglot to start managing all the files of the | ||
| 134 | project which use the same programming language. The notion of a | ||
| 135 | ``project'' used by Eglot is the same Emacs uses (@pxref{Projects,,, | ||
| 136 | emacs, GNU Emacs Manual}): in the simplest case, the ``project'' is | ||
| 137 | the single file you are editing, but it can also be all the files in a | ||
| 138 | single directory or a directory tree under some version control | ||
| 139 | system, such as Git. | ||
| 140 | |||
| 141 | Alternatively, you can start Eglot automatically from the major-mode | ||
| 142 | hook of the mode used for the programming language; see @ref{Starting | ||
| 143 | Eglot}. | ||
| 144 | |||
| 145 | @item | ||
| 146 | Use Eglot. | ||
| 147 | |||
| 148 | Most Eglot facilities are integrated into Emacs features, such as | ||
| 149 | ElDoc, Flymake, Xref, and Imenu. However, Eglot also provides | ||
| 150 | commands of its own, mainly to perform tasks by the LSP server, such | ||
| 151 | as @kbd{M-x eglot-rename} (to rename an identifier across the entire | ||
| 152 | project), @kbd{M-x eglot-format} (to reformat and reindent code), and | ||
| 153 | some others. @xref{Eglot Commands}, for the detailed list of Eglot | ||
| 154 | commands. | ||
| 155 | |||
| 156 | @item | ||
| 157 | That's it! | ||
| 158 | @end enumerate | ||
| 159 | |||
| 160 | @node Eglot and LSP Servers | ||
| 161 | @chapter Eglot and LSP Servers | ||
| 162 | |||
| 163 | This chapter describes how to set up Eglot for your needs, and how to | ||
| 164 | start 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 | |||
| 177 | For Eglot to be useful, it must first be combined with a suitable | ||
| 178 | language server. Usually, that means running the server program | ||
| 179 | locally as a child process of Emacs (@pxref{Processes,,, elisp, GNU | ||
| 180 | Emacs Lisp Reference Manual}) and communicating with it via the | ||
| 181 | standard input and output streams. | ||
| 182 | |||
| 183 | The language server program must be installed separately, and is not | ||
| 184 | further discussed in this manual; refer to the documentation of the | ||
| 185 | particular server(s) you want to install. | ||
| 186 | |||
| 187 | To use a language server, Eglot must know how to start it and which | ||
| 188 | programming languages each server supports. Eglot comes with a fairly | ||
| 189 | complete set of associations of major-modes to popular language | ||
| 190 | servers predefined. This information is provided by the | ||
| 191 | @code{eglot-server-programs} variable. | ||
| 192 | |||
| 193 | @defvar eglot-server-programs | ||
| 194 | This variable associates major modes with names and command-line | ||
| 195 | arguments of the language server programs corresponding to the | ||
| 196 | programming language of each major mode. It provides all the | ||
| 197 | information that Eglot needs to know about the programming language of | ||
| 198 | the source you are editing. | ||
| 199 | |||
| 200 | The value of the variable is an alist, whose elements are of the form | ||
| 201 | @w{@code{(@var{major-mode} . @var{server})}}. | ||
| 202 | |||
| 203 | The @var{major-mode} of the alist elements can be either a symbol of | ||
| 204 | an 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 | ||
| 206 | and @var{id} a string that identifies the language to the server. The | ||
| 207 | latter form should be used if Eglot cannot by itself convert the | ||
| 208 | major-mode to the language identifier string required by the server. | ||
| 209 | In addition, @var{major-mode} can be a list of several major modes | ||
| 210 | specified in one of the above forms -- this means a running instance | ||
| 211 | of the associated server is responsible for files of multiple major | ||
| 212 | modes or languages in the project. | ||
| 213 | |||
| 214 | The @var{server} part of the alist elements can be one of the | ||
| 215 | following: | ||
| 216 | |||
| 217 | @table @code | ||
| 218 | @item (@var{program} @var{args}@dots{}) | ||
| 219 | This says to invoke @var{program} with zero or more arguments | ||
| 220 | @var{args}; the program is expected to communicate with Emacs via the | ||
| 221 | standard input and standard output streams. | ||
| 222 | |||
| 223 | @item (@var{program} @var{args}@dots{} :initializationOptions @var{options}@dots{}) | ||
| 224 | Like above, but with @var{options} specifying the options to be | ||
| 225 | used for constructing the @samp{initializationOptions} JSON object for | ||
| 226 | the server. @var{options} can also be a function of one argument, in | ||
| 227 | which case it will be called with the server instance as the argument, | ||
| 228 | and should return the JSON object to use for initialization. | ||
| 229 | |||
| 230 | @item (@var{host} @var{port} @var{args}@dots{}) | ||
| 231 | Here @var{host} is a string and @var{port} is a positive integer | ||
| 232 | specifying a TCP connection to a remote server. The @var{args} are | ||
| 233 | passed to @code{open-network-stream}, e.g.@: if the connection needs | ||
| 234 | to use encryption or other non-default parameters (@pxref{Network,,, | ||
| 235 | elisp, 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 | ||
| 240 | arguments in @var{moreargs}; Eglot then establishes a TCP connection | ||
| 241 | with the server via that port on the local host. | ||
| 242 | |||
| 243 | @item @var{function} | ||
| 244 | This should be a function of a single argument: non-@code{nil} if the | ||
| 245 | connection was requested interactively (e.g., by the @code{eglot} | ||
| 246 | command), otherwise @code{nil}. The function should return a value of | ||
| 247 | any of the forms described above. This allows interaction with the | ||
| 248 | user for determining the program to start and its command-line | ||
| 249 | arguments. | ||
| 250 | @end table | ||
| 251 | |||
| 252 | @end defvar | ||
| 253 | |||
| 254 | If you need to add server associations to the default list, use | ||
| 255 | @code{add-to-list}. For example, if there is a hypothetical language | ||
| 256 | server program @command{fools} for the language @code{Foo} which is | ||
| 257 | supported by an Emacs major-mode @code{foo-mode}, you can add it to | ||
| 258 | the alist like this: | ||
| 259 | |||
| 260 | @lisp | ||
| 261 | (add-to-list 'eglot-server-programs | ||
| 262 | '(foo-mode . ("fools" "--stdio"))) | ||
| 263 | @end lisp | ||
| 264 | |||
| 265 | This will invoke the program @command{fools} with the command-line | ||
| 266 | argument @option{--stdio} in support of editing source files for which | ||
| 267 | Emacs turns on @code{foo-mode}, and will communicate with the program | ||
| 268 | via the standard streams. As usual with invoking programs, the | ||
| 269 | executable file @file{fools} should be in one of the directories | ||
| 270 | mentioned by the @code{exec-path} variable (@pxref{Subprocess | ||
| 271 | Creation,,, elisp, GNU Emacs Lisp Reference Manual}), for Eglot to be | ||
| 272 | able 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 | ||
| 280 | The most common way to start Eglot is to simply visit a source file of | ||
| 281 | a given language and use the command @kbd{M-x eglot}. This starts the | ||
| 282 | language server suitable for the visited file's major-mode, and | ||
| 283 | attempts to connect to it. If the connection to the language server | ||
| 284 | is successful, you will see the @code{[eglot:@var{project}]} indicator | ||
| 285 | on the mode line which reflects the server that was started. If the | ||
| 286 | server program couldn't be started or connection to it failed, you | ||
| 287 | will see an error message; in that case, try to troubleshoot the | ||
| 288 | problem as described in @ref{Troubleshooting Eglot}. Once a language | ||
| 289 | server was successfully started and Eglot connected to it, you can | ||
| 290 | immediately start using the Emacs features supported by Eglot, as | ||
| 291 | described in @ref{Eglot Features}. | ||
| 292 | |||
| 293 | A single Eglot session for a certain major-mode usually serves all the | ||
| 294 | buffers under that mode which visit files from the same project, so | ||
| 295 | you don't need to invoke @kbd{M-x eglot} again when you visit another | ||
| 296 | file from the same project which is edited using the same major-mode. | ||
| 297 | This is because Eglot uses the Emacs project infrastructure, as | ||
| 298 | described in @ref{Eglot and Buffers}, and this knows about files that | ||
| 299 | belong to the same project. Thus, after starting an Eglot session for | ||
| 300 | some buffer, that session is automatically reused when visiting files | ||
| 301 | in the same project with the same major-mode. | ||
| 302 | |||
| 303 | @findex eglot-ensure | ||
| 304 | Alternatively, you could configure Eglot to start automatically for | ||
| 305 | one or more major-modes from the respective mode hooks. Here's an | ||
| 306 | example for a hypothetical @code{foo-mode}: | ||
| 307 | |||
| 308 | @lisp | ||
| 309 | (add-hook 'foo-mode-hook 'eglot-ensure) | ||
| 310 | @end lisp | ||
| 311 | |||
| 312 | @noindent | ||
| 313 | The function @code{eglot-ensure} will start an Eglot session for each | ||
| 314 | buffer in which @code{foo-mode} is turned on, if there isn't already | ||
| 315 | an Eglot session that handles the buffer. Note that this variant of | ||
| 316 | starting an Eglot session is non-interactive, so it should be used | ||
| 317 | only when you are confident that Eglot can be started reliably for any | ||
| 318 | file which may be visited with the major-mode in question. | ||
| 319 | |||
| 320 | When Eglot connects to a language server for the first time in an | ||
| 321 | Emacs 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 | |||
| 328 | When Eglot is turned on, it arranges for turning itself off | ||
| 329 | automatically if the language server process terminates. Turning off | ||
| 330 | Eglot means that it shuts down the server connection, ceases its | ||
| 331 | management of all the buffers that use the server connection which was | ||
| 332 | terminated, deactivates its minor mode, and restores the original | ||
| 333 | values of the Emacs variables that Eglot changed when it was turned | ||
| 334 | on. @xref{Eglot and Buffers}, for more details of what Eglot | ||
| 335 | management of a buffer entails. | ||
| 336 | |||
| 337 | @findex eglot-shutdown | ||
| 338 | You can also shut down a language server manually, by using the | ||
| 339 | command @kbd{M-x eglot-shutdown}. This prompts for the server (unless | ||
| 340 | there's only one connection and it's used in the current buffer), and | ||
| 341 | then shuts it down. By default, it also kills the server's events | ||
| 342 | buffer (@pxref{Troubleshooting Eglot}), but a prefix argument prevents | ||
| 343 | that. | ||
| 344 | |||
| 345 | Alternatively, you can customize the variable | ||
| 346 | @code{eglot-autoshutdown} to a non-@code{nil} value, in which case | ||
| 347 | Eglot will automatically shut down the language server process when | ||
| 348 | the last buffer served by that language server is killed. The default | ||
| 349 | of this variable is @code{nil}, so that visiting another file would | ||
| 350 | automatically activate Eglot even when the project which started Eglot | ||
| 351 | with the server no longer has any buffer associated with it. This | ||
| 352 | default allows you to start a server only once in each Emacs session. | ||
| 353 | |||
| 354 | @node Using Eglot | ||
| 355 | @chapter Using Eglot | ||
| 356 | |||
| 357 | This chapter describes in detail the features that Eglot provides and | ||
| 358 | how it does that. It also provides reference sections for Eglot | ||
| 359 | commands 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 | |||
| 372 | Once Eglot is enabled in a buffer, it uses LSP and the language-server | ||
| 373 | capabilities to activate, enable, and enhance modern IDE features in | ||
| 374 | Emacs. The features themselves are usually provided via other Emacs | ||
| 375 | packages. These are the main features that Eglot enables and provides: | ||
| 376 | |||
| 377 | @itemize @bullet | ||
| 378 | @item | ||
| 379 | At-point documentation: when point is at or near a symbol or an | ||
| 380 | identifier, the information about the symbol/identifier, such as the | ||
| 381 | signature of a function or class method and server-generated | ||
| 382 | diagnostics, is made available via the ElDoc package (@pxref{Lisp | ||
| 383 | Doc,,, emacs, GNU Emacs Manual}). This allows major modes to provide | ||
| 384 | extensive help and documentation about the program identifiers. | ||
| 385 | |||
| 386 | @item | ||
| 387 | On-the-fly diagnostic annotations with server-suggested fixes, via the | ||
| 388 | Flymake package (@pxref{Top,,, flymake, GNU Flymake manual}). This | ||
| 389 | improves and enhances the Flymake diagnostics, replacing the other | ||
| 390 | Flymake backends. | ||
| 391 | |||
| 392 | @item | ||
| 393 | Finding definitions and uses of identifiers, via Xref (@pxref{Xref,,, | ||
| 394 | emacs, GNU Emacs Manual}). Eglot provides a backend for the Xref | ||
| 395 | capabilities which uses the language-server understanding of the | ||
| 396 | program source. In particular, it eliminates the need to generate | ||
| 397 | tags tables (@pxref{Tags tables,,, emacs, GNU Emacs Manual}) for | ||
| 398 | languages that are only supported by the @code{etags} backend. | ||
| 399 | |||
| 400 | @item | ||
| 401 | Buffer navigation by name of function, class, method, etc., via Imenu | ||
| 402 | (@pxref{Imenu,,, emacs, GNU Emacs Manual}). Eglot provides its own | ||
| 403 | variant of @code{imenu-create-index-function}, which generates the | ||
| 404 | index for the buffer based on language-server program source analysis. | ||
| 405 | |||
| 406 | @item | ||
| 407 | Enhanced completion of symbol at point by the | ||
| 408 | @code{completion-at-point} command (@pxref{Symbol Completion,,, emacs, | ||
| 409 | GNU Emacs Manual}). This uses the language-server's parser data for | ||
| 410 | the completion candidates. | ||
| 411 | |||
| 412 | @item | ||
| 413 | Automatic reformatting of source code as you type it. This is similar | ||
| 414 | to what the @code{eglot-format} command does (see below), but is | ||
| 415 | activated automatically as you type. | ||
| 416 | |||
| 417 | @item | ||
| 418 | If a completion package such as @code{company-mode}, a popular | ||
| 419 | third-party completion package, is installed, Eglot enhances it by | ||
| 420 | providing completion candidates based on the language-server analysis | ||
| 421 | of the source code. (@code{company-mode} can be installed from GNU ELPA.) | ||
| 422 | |||
| 423 | @item | ||
| 424 | If @code{yasnippet}, a popular third-party package for automatic | ||
| 425 | insertion of code templates (snippets), is installed, and the language | ||
| 426 | server supports snippet completion candidates, Eglot arranges for the | ||
| 427 | completion package to instantiate these snippets using | ||
| 428 | @code{yasnippet}. (@code{yasnippet} can be installed from GNU ELPA.) | ||
| 429 | |||
| 430 | @item | ||
| 431 | If the popular third-party package @code{markdown-mode} is installed, | ||
| 432 | and the server provides at-point documentation formatted as Markdown | ||
| 433 | in addition to plain text, Eglot arranges for the ElDoc package to | ||
| 434 | enrich this text with e.g. fontification before displaying it to the | ||
| 435 | user. | ||
| 436 | |||
| 437 | @item | ||
| 438 | In addition to enabling and enhancing other features and packages, | ||
| 439 | Eglot also provides a small number of user commands based directly on | ||
| 440 | the capabilities of language servers. These commands are: | ||
| 441 | |||
| 442 | @table @kbd | ||
| 443 | @item M-x eglot-rename | ||
| 444 | This prompts for a new name for the symbol at point, and then modifies | ||
| 445 | all the project source files to rename the symbol to the new name, | ||
| 446 | based on editing data received from the language-server. @xref{Eglot | ||
| 447 | and Buffers}, for the details of how project files are defined. | ||
| 448 | |||
| 449 | @item M-x eglot-format | ||
| 450 | This reformats and prettifies the current active region according to | ||
| 451 | source formatting rules of the language-server. If the region is not | ||
| 452 | active, it reformats the entire buffer instead. | ||
| 453 | |||
| 454 | @item M-x eglot-format-buffer | ||
| 455 | This reformats and prettifies the current buffer according to source | ||
| 456 | formatting 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 | ||
| 465 | These command allow you to invoke the so-called @dfn{code actions}: | ||
| 466 | requests for the language-server to provide editing commands for | ||
| 467 | various code fixes, typically either to fix an error diagnostic or to | ||
| 468 | beautify/refactor code. For example, | ||
| 469 | @code{eglot-code-action-organize-imports} rearranges the program | ||
| 470 | @dfn{imports}---declarations of modules whose capabilities the program | ||
| 471 | uses. These commands affect all the files that belong to the | ||
| 472 | project. The command @kbd{M-x eglot-code-actions} will pop up a menu | ||
| 473 | of code applicable actions at point. | ||
| 474 | @end table | ||
| 475 | |||
| 476 | @end itemize | ||
| 477 | |||
| 478 | Not all servers support the full set of LSP capabilities, but most of | ||
| 479 | them support enough to enable the basic set of features mentioned | ||
| 480 | above. Conversely, some servers offer capabilities for which no | ||
| 481 | equivalent Emacs package exists yet, and so Eglot cannot (yet) expose | ||
| 482 | these 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 | ||
| 490 | One of the main strong points of using a language server is that a | ||
| 491 | language server has a broad view of the program: it considers more | ||
| 492 | than just the single source file you are editing. Ideally, the | ||
| 493 | language server should know about all the source files of your program | ||
| 494 | which are written in the language supported by the server. In the | ||
| 495 | language-server parlance, the set of the source files of a program is | ||
| 496 | known as a @dfn{workspace}. The Emacs equivalent of a workspace is a | ||
| 497 | @dfn{project} (@pxref{Projects,,, emacs, GNU Emacs Manual}). Eglot | ||
| 498 | fully supports Emacs projects, and considers the file in whose buffer | ||
| 499 | Eglot is turned on as belonging to a project. In the simplest case, | ||
| 500 | that file is the entire project, i.e.@: your project consists of a | ||
| 501 | single file. But there are other more complex projects: | ||
| 502 | |||
| 503 | @itemize @bullet | ||
| 504 | @item | ||
| 505 | A single-directory project: several source files in a single common | ||
| 506 | directory. | ||
| 507 | |||
| 508 | @item | ||
| 509 | A VC project: source files in a directory hierarchy under some VCS, | ||
| 510 | e.g.@: a Git repository (@pxref{Version Control,,, emacs, GNU Emacs | ||
| 511 | Manual}). | ||
| 512 | |||
| 513 | @item | ||
| 514 | An EDE project: source files in a directory hierarchy managed via the | ||
| 515 | Emacs Development Environment (@pxref{EDE,,, emacs, GNU Emacs | ||
| 516 | Manual}). | ||
| 517 | @end itemize | ||
| 518 | |||
| 519 | Eglot uses the Emacs's project management infrastructure to figure out | ||
| 520 | which files and buffers belong to what project, so any kind of project | ||
| 521 | supported by that infrastructure is automatically supported by Eglot. | ||
| 522 | |||
| 523 | When Eglot starts a server program, it does so in the project's root | ||
| 524 | directory, which is usually the top-level directory of the project's | ||
| 525 | directory hierarchy. This ensures the language server has the same | ||
| 526 | comprehensive view of the project's files as you do. | ||
| 527 | |||
| 528 | For example, if you visit the file @file{~/projects/fooey/lib/x.foo} | ||
| 529 | and @file{x.foo} belongs to a project rooted at | ||
| 530 | @file{~/projects/fooey} (perhaps because a @file{.git} directory | ||
| 531 | exists there), then @kbd{M-x eglot} causes the server program to start | ||
| 532 | with that root as the current working directory. The server then will | ||
| 533 | analyze not only the file @file{lib/x.foo} you visited, but likely | ||
| 534 | also all the other @file{*.foo} files under the | ||
| 535 | @file{~/projects/fooey} directory. | ||
| 536 | |||
| 537 | In some cases, additional information specific to a given project will | ||
| 538 | need to be provided to the language server when starting it. The | ||
| 539 | variable @code{eglot-workspace-configuration} (@pxref{Customizing | ||
| 540 | Eglot}) exists for that purpose. It specifies the parameters and | ||
| 541 | their values to communicate to each language server which needs that. | ||
| 542 | |||
| 543 | When Eglot is active for a project, it performs several background | ||
| 544 | activities 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 | ||
| 551 | All of the project's file-visiting buffers under the same major-mode | ||
| 552 | are served by a single language-server connection. (If the project | ||
| 553 | uses several programming languages, there will usually be a separate | ||
| 554 | server connection for each group of files written in the same language | ||
| 555 | and using the same Emacs major-mode.) Eglot adds the | ||
| 556 | @samp{[eglot:@var{project}]} indication to the mode line of | ||
| 557 | each such buffer, where @var{server} is the name of the server and | ||
| 558 | @var{project} identifies the project by its root directory. Clicking | ||
| 559 | the mouse on the Eglot mode-line indication activates a menu with | ||
| 560 | server-specific items. | ||
| 561 | |||
| 562 | @item | ||
| 563 | For each buffer in which Eglot is active, it notifies the language | ||
| 564 | server that Eglot is @dfn{managing} the file visited by that buffer. | ||
| 565 | This tells the language server that the file's contents on disk may no | ||
| 566 | longer be up-to-date due to unsaved edits. Eglot reports to the | ||
| 567 | server any changes in the text of each managed buffer, to make the | ||
| 568 | server aware of unsaved changes. This includes your editing of the | ||
| 569 | buffer and also changes done automatically by other Emacs features and | ||
| 570 | commands. Killing a buffer relinquishes its management by Eglot and | ||
| 571 | notifies 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 | ||
| 576 | Eglot turns on a special minor mode in each buffer it manages. This | ||
| 577 | minor mode ensures the server is notified about files Eglot manages, | ||
| 578 | and also arranges for other Emacs features supported by Eglot | ||
| 579 | (@pxref{Eglot Features}) to receive information from the language | ||
| 580 | server, by changing the settings of these features. Unlike other | ||
| 581 | minor-modes, this special minor mode is not activated manually by the | ||
| 582 | user, but automatically as result of starting an Eglot session for the | ||
| 583 | buffer. However, this minor mode provides a hook variable | ||
| 584 | @code{eglot-managed-mode-hook} that can be used to customize the Eglot | ||
| 585 | management of the buffer. This hook is run both when the minor mode | ||
| 586 | is turned on and when it's turned off; use the variable | ||
| 587 | @code{eglot-managed-p} to tell if current buffer is still being | ||
| 588 | managed or not. When Eglot stops managing the buffer, this minor mode | ||
| 589 | is turned off, and all the settings that Eglot changed are restored to | ||
| 590 | their original values. | ||
| 591 | |||
| 592 | @item | ||
| 593 | When you visit a file under the same project, whether an existing or a | ||
| 594 | new file, its buffer is automatically added to the set of buffers | ||
| 595 | managed by Eglot, and the server which supports the buffer's | ||
| 596 | major-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 | ||
| 598 | notify the server of the @file{*.foo} files' language that a new file | ||
| 599 | was added to the project, even before the file appears on disk. The | ||
| 600 | special Eglot minor mode is also turned on automatically in the buffer | ||
| 601 | visiting the file. | ||
| 602 | @end itemize | ||
| 603 | |||
| 604 | @node Eglot Commands | ||
| 605 | @section Eglot Commands | ||
| 606 | @cindex commands, Eglot | ||
| 607 | |||
| 608 | This section provides a reference of the most commonly used Eglot | ||
| 609 | commands: | ||
| 610 | |||
| 611 | @ftable @code | ||
| 612 | @item M-x eglot | ||
| 613 | This command adds the current buffer and the file it visits to the | ||
| 614 | group of buffers and files managed by Eglot on behalf of a suitable | ||
| 615 | language server. If a language server for the buffer's | ||
| 616 | @code{major-mode} (@pxref{Major Modes,,, emacs, GNU Emacs Manual}) is | ||
| 617 | not yet running, it will be started; otherwise the buffer and its file | ||
| 618 | will be added to those managed by an existing server session. | ||
| 619 | |||
| 620 | The command attempts to figure out the buffer's major mode and the | ||
| 621 | suitable language server; in case it fails, it might prompt for the | ||
| 622 | major mode to use and for the server program to start. If invoked | ||
| 623 | with a prefix argument @kbd{C-u}, it always prompts for the server | ||
| 624 | program, and if invoked with @kbd{C-u C-u}, also prompt for the major | ||
| 625 | mode. | ||
| 626 | |||
| 627 | If the language server is successfully started and contacted, this | ||
| 628 | command arranges for any other buffers belonging to the same project | ||
| 629 | and using the same major mode to use the same language-server session. | ||
| 630 | That includes any buffers created by visiting files after this command | ||
| 631 | succeeds to connect to a language server. | ||
| 632 | |||
| 633 | All the Emacs features that are capable of using Eglot services | ||
| 634 | (@pxref{Eglot Features}) are automatically configured by this command | ||
| 635 | to start using the language server via Eglot. To customize which | ||
| 636 | Emacs 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 | ||
| 640 | Shuts down an the current connection to the language server and | ||
| 641 | immediately restarts it using the same options used originally. This | ||
| 642 | can sometimes be useful to unclog a partially malfunctioning server | ||
| 643 | connection. | ||
| 644 | |||
| 645 | @item M-x eglot-shutdown | ||
| 646 | Shuts down a language server. This commands prompts for a language | ||
| 647 | server to shut down (unless there's only one server session, and it | ||
| 648 | manages the current buffer). Then the command shuts down the server | ||
| 649 | and stops managing the buffers the server was used for. Emacs | ||
| 650 | features (@pxref{Eglot Features}) that Eglot configured to work with | ||
| 651 | the language server are restored back to their original configuration. | ||
| 652 | |||
| 653 | Normally, this command kills the buffers used for communicating with | ||
| 654 | the language server, but if invoked with a prefix argument @kbd{C-u}, | ||
| 655 | the command doesn't kill those buffers, allowing them to be used for | ||
| 656 | diagnostics and problem reporting (@pxref{Troubleshooting Eglot}). | ||
| 657 | |||
| 658 | @item M-x eglot-shutdown-all | ||
| 659 | This command shuts down all the language servers active in the current | ||
| 660 | Emacs session. As with @code{eglot-shutdown}, invoking this command | ||
| 661 | with a prefix argument avoids killing the buffers used for | ||
| 662 | communications with the language servers. | ||
| 663 | |||
| 664 | @item M-x eglot-rename | ||
| 665 | This command renames the program symbol (a.k.a.@: @dfn{identifier}) at | ||
| 666 | point to another name. It prompts for the new name of the symbol, and | ||
| 667 | then modifies all the files in the project which arte managed by the | ||
| 668 | language server of the current buffer to implement the renaming. | ||
| 669 | |||
| 670 | @item M-x eglot-format | ||
| 671 | This command reformats the active region according to the | ||
| 672 | language-server rules. If no region is active, it reformats the | ||
| 673 | entire current buffer. | ||
| 674 | |||
| 675 | @item M-x eglot-format-buffer | ||
| 676 | This 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 | ||
| 681 | This command asks the server for any @dfn{code actions} applicable at | ||
| 682 | point. It can also be invoked by @kbd{mouse-1} clicking on | ||
| 683 | diagnostics 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 | ||
| 690 | These commands invoke specific code actions supported by the language | ||
| 691 | server. | ||
| 692 | @c FIXME: Need more detailed description of each action. | ||
| 693 | @end ftable | ||
| 694 | |||
| 695 | The following Eglot commands are used less commonly, mostly for | ||
| 696 | diagnostic and troubleshooting purposes: | ||
| 697 | |||
| 698 | @ftable @code | ||
| 699 | @item M-x eglot-events-buffer | ||
| 700 | This command pops up the events buffer used for communication with the | ||
| 701 | language server of the current buffer. | ||
| 702 | |||
| 703 | @item M-x eglot-stderr-buffer | ||
| 704 | This command pops up the buffer with the debug info printed by the | ||
| 705 | language server to its standard error stream. | ||
| 706 | |||
| 707 | @item M-x eglot-forget-pending-continuations | ||
| 708 | Forget 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 | ||
| 712 | This command updates the language server configuration according to | ||
| 713 | the current value of the variable @code{eglot-workspace-configuration} | ||
| 714 | (@pxref{Customizing Eglot}). | ||
| 715 | |||
| 716 | @item M-x eglot-clear-status | ||
| 717 | Clear the last JSONRPC error for the server of the current buffer. | ||
| 718 | Eglot keeps track of erroneous situations encountered by the server in | ||
| 719 | its mode-line indication so that the user may inspect the | ||
| 720 | communication leading up to it (@pxref{Troubleshooting Eglot}). If | ||
| 721 | the situation is deemed uninteresting or temporary, this command can | ||
| 722 | be used to ``forget'' the error. Note that the command @code{M-x | ||
| 723 | eglot-reconnect} can sometimes be used to unclog a temporarily | ||
| 724 | malfunctioning server. | ||
| 725 | @end ftable | ||
| 726 | |||
| 727 | As described in @ref{Eglot Features} most features associated with | ||
| 728 | Eglot are actually provided by other Emacs packages and features, and | ||
| 729 | Eglot only enhances them by allowing them to use the information | ||
| 730 | coming from the language servers. For completeness, here's the list | ||
| 731 | of commands of those other packages that are very commonly used in | ||
| 732 | Eglot-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 | ||
| 739 | Ask 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 | ||
| 744 | Ask Flymake system to display diagnostics for the current buffer. | ||
| 745 | |||
| 746 | @item M-x flymake-show-project-diagnostics | ||
| 747 | Ask Flymake to list diagnostics for all the files in the current | ||
| 748 | project. | ||
| 749 | |||
| 750 | @cindex xref, and Eglot | ||
| 751 | @cindex finding definitions of identifiers using Eglot | ||
| 752 | @item M-x xref-find-definitions | ||
| 753 | Ask Xref to go the definition of the identifier at point. | ||
| 754 | |||
| 755 | @cindex imenu navigation using Eglot | ||
| 756 | @item M-x imenu | ||
| 757 | Let the user navigate the program source code using buffer index, | ||
| 758 | categorizing program elements by syntactic class (class, method, | ||
| 759 | variable, etc.) and offering completion. | ||
| 760 | |||
| 761 | @cindex symbol completion using Eglot | ||
| 762 | @item M-x completion-at-point | ||
| 763 | Request completion of the symbol at point. | ||
| 764 | @end table | ||
| 765 | |||
| 766 | @node Eglot Variables | ||
| 767 | @section Eglot Variables | ||
| 768 | @cindex variables, Eglot | ||
| 769 | |||
| 770 | This section provides a reference of the Eglot' user options. | ||
| 771 | |||
| 772 | @vtable @code | ||
| 773 | @item eglot-autoreconnect | ||
| 774 | This option controls the ability to reconnect automatically to the | ||
| 775 | language server when Eglot detects that the server process terminated | ||
| 776 | unexpectedly. The default value 3 means to attempt reconnection only | ||
| 777 | if the previous successful connection lasted for more than that number | ||
| 778 | of seconds; a different positive value changes the minimal length of | ||
| 779 | the connection to trigger reconnection. A value of @code{t} means | ||
| 780 | always reconnect automatically, and @code{nil} means never reconnect. | ||
| 781 | |||
| 782 | @item eglot-connect-timeout | ||
| 783 | This specifies the number of seconds before connection attempt to a | ||
| 784 | language server times out. The value of @code{nil} means never time | ||
| 785 | out. The default is 30 seconds. | ||
| 786 | |||
| 787 | @item eglot-sync-connect | ||
| 788 | This setting is mainly important for connections which are slow to | ||
| 789 | establish. Whereas the variable @code{eglot-connect-timeout} controls | ||
| 790 | how long to wait for, this variable controls whether to block Emacs's | ||
| 791 | user interface while waiting. The default value is 3; a positive | ||
| 792 | value means block for that many seconds, then wait for the connection | ||
| 793 | in the background. The value of @code{t} means block during the whole | ||
| 794 | waiting period. The value of @code{nil} or zero means don't block at | ||
| 795 | all during the waiting period. | ||
| 796 | |||
| 797 | @item eglot-events-buffer-size | ||
| 798 | This determines the size of the Eglot events buffer. @xref{Eglot | ||
| 799 | Commands, eglot-events-buffer}, for how to display that buffer. If | ||
| 800 | the value is changed, for it to take effect the connection should be | ||
| 801 | restarted 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 | ||
| 808 | If this is non-@code{nil}, Eglot shuts down a language server when the | ||
| 809 | last buffer managed by it is killed. @xref{Shutting Down LSP Servers}. | ||
| 810 | The 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 | ||
| 814 | Various Eglot commands and code actions result in the language server | ||
| 815 | sending editing commands to Emacs. If this option's value is | ||
| 816 | non-@code{nil} (the default), Eglot will ask for confirmation before | ||
| 817 | performing edits initiated by the server or edits whose scope affects | ||
| 818 | buffers other than the one where the user initiated the request. | ||
| 819 | |||
| 820 | @item eglot-ignored-server-capabilities | ||
| 821 | This variable's value is a list of language server capabilities that | ||
| 822 | Eglot should not use. The default is @code{nil}: Eglot uses all of | ||
| 823 | the capabilities supported by each server. | ||
| 824 | |||
| 825 | @item eglot-extend-to-xref | ||
| 826 | If this is non-@code{nil}, and @kbd{M-.} | ||
| 827 | (@code{xref-find-definitions}) lands you in a file outside of your | ||
| 828 | project, such as a system-installed library or header file, | ||
| 829 | transiently consider that file as managed by the same language server. | ||
| 830 | That file is still outside your project (i.e. @code{project-find-file} | ||
| 831 | won't find it), but Eglot and the server will consider it to be part | ||
| 832 | of the workspace. The default is @code{nil}. | ||
| 833 | |||
| 834 | @item eglot-mode-map | ||
| 835 | This variable is the keymap for binding Eglot-related command. It is | ||
| 836 | in effect only as long as the buffer is managed by Eglot. By default, | ||
| 837 | it is empty, with the single exception: @kbd{C-h .} is remapped to | ||
| 838 | invoke @code{eldoc-doc-buffer}. You can bind additional commands in | ||
| 839 | this 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 | |||
| 850 | Additional variables, which are relevant for customizing the server | ||
| 851 | connections, are documented in @ref{Customizing Eglot}. | ||
| 852 | |||
| 853 | @node Customizing Eglot | ||
| 854 | @chapter Customizing Eglot | ||
| 855 | @cindex customizing Eglot | ||
| 856 | |||
| 857 | A large part of customizing Eglot to your needs and preferences should | ||
| 858 | actually be done via options of the Emacs packages and features which | ||
| 859 | Eglot supports and enhances (@pxref{Eglot Features}). For example: | ||
| 860 | |||
| 861 | @itemize @bullet | ||
| 862 | @item | ||
| 863 | To configure the face used for server-derived errors and warnings, | ||
| 864 | customize the Flymake faces @code{flymake-error} and | ||
| 865 | @code{flymake-error}. | ||
| 866 | |||
| 867 | @item | ||
| 868 | To configure the amount of space taken up by documentation in the | ||
| 869 | echo area, customize the ElDoc variable | ||
| 870 | @code{eldoc-echo-area-use-multiline-p}. | ||
| 871 | |||
| 872 | @item | ||
| 873 | To completely change how ElDoc displays the at-point documentation | ||
| 874 | destination, customize the ElDoc variable | ||
| 875 | @code{eldoc-display-functions}. | ||
| 876 | @end itemize | ||
| 877 | |||
| 878 | For this reason, this manual describes only how to customize the | ||
| 879 | Eglot's own operation, which mainly has to do with the server | ||
| 880 | connections 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 | ||
| 886 | This variable determines which language server to start for each | ||
| 887 | supported 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 | ||
| 892 | This is @code{nil} by default, meaning that Eglot is generally lenient | ||
| 893 | about non-conforming servers. If you need to debug a server, set this | ||
| 894 | to @w{@code{(disallow-non-standard-keys enforce-required-keys)}}. | ||
| 895 | |||
| 896 | @vindex eglot-server-initialized-hook | ||
| 897 | @item eglot-server-initialized-hook | ||
| 898 | A hook run after the server object is successfully initialized. | ||
| 899 | |||
| 900 | @vindex eglot-connect-hook | ||
| 901 | @item eglot-connect-hook | ||
| 902 | A hook run after connection to the server is successfully | ||
| 903 | established. @xref{Starting Eglot}. | ||
| 904 | |||
| 905 | @item eglot-managed-mode-hook | ||
| 906 | A 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 | ||
| 911 | This variable's value lists Emacs features that Eglot shouldn't | ||
| 912 | automatically try to manage on user's behalf. It is useful, for | ||
| 913 | example, when you need to use non-LSP Flymake or Company back-ends. | ||
| 914 | To have Eglot stay away of some Emacs feature, add that feature's | ||
| 915 | symbol or a regexp that will match a symbol's name to the list: for | ||
| 916 | example, the symbol @code{xref} to leave Xref alone, or the string | ||
| 917 | @samp{company} to stay away of your Company customizations. Here's an | ||
| 918 | example: | ||
| 919 | |||
| 920 | @lisp | ||
| 921 | (add-to-list 'eglot-stay-out-of 'flymake) | ||
| 922 | @end lisp | ||
| 923 | |||
| 924 | Note that you can still configure the excluded Emacs features manually | ||
| 925 | to use Eglot in your @code{eglot-managed-mode-hook} or via some other | ||
| 926 | mechanism. | ||
| 927 | |||
| 928 | @vindex eglot-workspace-configuration | ||
| 929 | @cindex server workspace configuration | ||
| 930 | @item eglot-workspace-configuration | ||
| 931 | This variable is meant to be set in the @file{.dir-locals.el} file, to | ||
| 932 | provide per-project settings, as described below in more detail. | ||
| 933 | @end table | ||
| 934 | |||
| 935 | Some language servers need to know project-specific settings, which | ||
| 936 | the LSP calls @dfn{workspace configuration}. Eglot allows such fine | ||
| 937 | tuning of per-project settings via the variable | ||
| 938 | @code{eglot-workspace-configuration}. Eglot sends the portion of the | ||
| 939 | settings contained in this variable to each server for which such | ||
| 940 | settings were defined in the variable. These settings are | ||
| 941 | communicated to the server initially (upon establishing the | ||
| 942 | connection) or when the settings are changed, or in response to the | ||
| 943 | configuration request from the server. | ||
| 944 | |||
| 945 | In many cases, servers can be configured globally using a | ||
| 946 | configuration file in the user's home directory or in the project | ||
| 947 | directory, 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 | ||
| 950 | file @file{.clangd} anywhere in the current project's directory tree. | ||
| 951 | If possible, we recommend to use these configuration files that are | ||
| 952 | independent of Eglot and Emacs; they have the advantage that they will | ||
| 953 | work with other LSP clients as well. | ||
| 954 | |||
| 955 | If you do need to provide Emacs-specific configuration for a language | ||
| 956 | server, we recommend to define the appropriate value in the | ||
| 957 | @file{.dir-locals.el} file in the project's directory. The value of | ||
| 958 | this 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 | ||
| 965 | Here @code{:@var{server}} identifies a particular language server and | ||
| 966 | @var{plist} is the corresponding keyword-value property list of one or | ||
| 967 | more parameter settings for that server, serialized by Eglot as a JSON | ||
| 968 | object. @var{plist} may be arbitrarity complex, generally containing | ||
| 969 | other keywork-value property sublists corresponding to JSON subobjects. | ||
| 970 | The JSON values @code{true}, @code{false}, @code{null} and @code{@{@}} | ||
| 971 | are represented by the Lisp values @code{t}, @code{:json-false}, | ||
| 972 | @code{nil}, and @code{eglot-@{@}}, respectively. | ||
| 973 | |||
| 974 | @findex eglot-show-workspace-configuration | ||
| 975 | When experimenting with workspace settings, you can use the command | ||
| 976 | @kbd{M-x eglot-show-workspace-configuration} to inspect and debug the | ||
| 977 | JSON value to be sent to the server. This helper command works even | ||
| 978 | before actually connecting to the server. | ||
| 979 | |||
| 980 | Here's an example of defining the workspace-configuration settings for | ||
| 981 | a project that uses two different language servers, one for Python, | ||
| 982 | whose server is @command{pylsp}, the other one for Go, with | ||
| 983 | @command{gopls} as its server (presumably, the project is written in a | ||
| 984 | combination 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 | ||
| 998 | This should go into the @file{.dir-locals.el} file in the project's | ||
| 999 | root directory. It sets up the value of | ||
| 1000 | @code{eglot-workspace-configuration} separately for each major mode. | ||
| 1001 | |||
| 1002 | Alternatively, 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 | |||
| 1013 | This is an equivalent setup which sets the value for all the | ||
| 1014 | major-modes inside the project; Eglot will use for each server only | ||
| 1015 | the section of the parameters intended for that server. | ||
| 1016 | |||
| 1017 | As 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 | ||
| 1020 | Variables,,, elisp, GNU Emacs Lisp Reference Manual}. | ||
| 1021 | |||
| 1022 | Finally, if one needs to determine the workspace configuration based | ||
| 1023 | on some dynamic context, @code{eglot-workspace-configuration} can be | ||
| 1024 | set to a function. The function is called with the | ||
| 1025 | @code{eglot-lsp-server} instance of the connected server (if any) and | ||
| 1026 | with @code{default-directory} set to the root of the project. The | ||
| 1027 | function should return a value of the form described above. | ||
| 1028 | |||
| 1029 | Some servers need special hand-holding to operate correctly. If your | ||
| 1030 | server has some quirks or non-conformity, it's possible to extend | ||
| 1031 | Eglot 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 | |||
| 1035 | Here'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 | ||
| 1053 | See the doc string of @code{eglot-initialization-options} for more | ||
| 1054 | details. | ||
| 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 | |||
| 1062 | This section documents commands and variables that can be used to | ||
| 1063 | troubleshoot Eglot problems. It also provides guidelines for | ||
| 1064 | reporting Eglot bugs in a way that facilitates their resolution. | ||
| 1065 | |||
| 1066 | When 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 | ||
| 1068 | pop up special buffers that can be used to inspect the communications | ||
| 1069 | between the Eglot and language server. In many cases, this will | ||
| 1070 | indicate the problems or at least provide a hint. | ||
| 1071 | |||
| 1072 | A common and easy-to-fix cause of performance problems is the length | ||
| 1073 | of these two buffers. If Eglot is operating correctly but slowly, | ||
| 1074 | customize the variable @code{eglot-events-buffer-size} (@pxref{Eglot | ||
| 1075 | Variables}) to limit logging, and thus speed things up. | ||
| 1076 | |||
| 1077 | If you need to report an Eglot bug, please keep in mind that, because | ||
| 1078 | there are so many variables involved, it is generally both very | ||
| 1079 | @emph{difficult} and @emph{absolutely essential} to reproduce bugs | ||
| 1080 | exactly as they happened to you, the user. Therefore, every bug | ||
| 1081 | report should include: | ||
| 1082 | |||
| 1083 | @enumerate | ||
| 1084 | @item | ||
| 1085 | The transcript of events obtained from the buffer popped up by | ||
| 1086 | @kbd{M-x eglot-events-buffer}. If the transcript can be narrowed down | ||
| 1087 | to show the problematic exchange, so much the better. This is | ||
| 1088 | invaluable for the investigation and reproduction of the problem. | ||
| 1089 | |||
| 1090 | @item | ||
| 1091 | If Emacs signaled an error (an error message was seen or heard), make | ||
| 1092 | sure to repeat the process after toggling @code{debug-on-error} on | ||
| 1093 | (via @kbd{M-x toggle-debug-on-error}). This normally produces a | ||
| 1094 | backtrace of the error that should also be attached to the bug report. | ||
| 1095 | |||
| 1096 | @item | ||
| 1097 | An explanation how to obtain and install the language server you used. | ||
| 1098 | If possible, try to replicate the problem with the C/C@t{++} or Python | ||
| 1099 | servers, as these are very easy to install. | ||
| 1100 | |||
| 1101 | @item | ||
| 1102 | A description of how to setup the @emph{minimal} project (one or two | ||
| 1103 | files and their contents) where the problem happens. | ||
| 1104 | |||
| 1105 | @item | ||
| 1106 | A recipe to replicate the problem with @emph{a clean Emacs run}. This | ||
| 1107 | means @kbd{emacs -Q} invocation or a very minimal (no more that 10 | ||
| 1108 | lines) @file{.emacs} initialization file. @code{eglot-ensure} and | ||
| 1109 | @code{use-package} calls are generally @emph{not} needed. | ||
| 1110 | |||
| 1111 | @item | ||
| 1112 | Make sure to double check all the above elements and re-run the | ||
| 1113 | recipe to see that the problem is reproducible. | ||
| 1114 | @end enumerate | ||
| 1115 | |||
| 1116 | Please keep in mind that some problems reported against Eglot may | ||
| 1117 | actually be bugs in the language server or the Emacs feature/package | ||
| 1118 | that 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'. | ||
| 141 | Each element of ALTERNATIVES is a string PROGRAM or a list of | ||
| 142 | strings (PROGRAM ARGS...) where program names an LSP server | ||
| 143 | program to start with ARGS. Returns a function of one argument. | ||
| 144 | When invoked, that function will return a list (ABSPATH ARGS), | ||
| 145 | where ABSPATH is the absolute path of the PROGRAM that was | ||
| 146 | chosen (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/\ | ||
| 194 | language-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. | ||
| 238 | An association list of (MAJOR-MODE . CONTACT) pairs. MAJOR-MODE | ||
| 239 | identifies the buffers that are to be managed by a specific | ||
| 240 | language server. The associated CONTACT specifies how to connect | ||
| 241 | to a server for those buffers. | ||
| 242 | |||
| 243 | MAJOR-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 | |||
| 256 | CONTACT 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. | ||
| 319 | If t, always reconnect automatically (not recommended). If nil, | ||
| 320 | never reconnect automatically after unexpected server shutdowns, | ||
| 321 | crashes or network failures. A positive integer number says to | ||
| 322 | only autoreconnect if the previous successful connection attempt | ||
| 323 | lasted 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. | ||
| 329 | If nil, never time out." | ||
| 330 | :type 'number) | ||
| 331 | |||
| 332 | (defcustom eglot-sync-connect 3 | ||
| 333 | "Control blocking of LSP connection attempts. | ||
| 334 | If t, block for `eglot-connect-timeout' seconds. A positive | ||
| 335 | integer number means block for that many seconds, and then wait | ||
| 336 | for the connection in the background. nil has the same meaning | ||
| 337 | as 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. | ||
| 351 | If a number, don't let the buffer grow larger than that many | ||
| 352 | characters. If 0, don't use an event's buffer at all. If nil, | ||
| 353 | let the buffer grow forever. | ||
| 354 | |||
| 355 | For changes on this variable to take effect on a connection | ||
| 356 | already started, you need to restart the connection. That can be | ||
| 357 | done 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. | ||
| 376 | This 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 | |||
| 462 | INTERFACE-NAME is a symbol designated by the spec as | ||
| 463 | \"interface\". INTERFACE is a list (REQUIRED OPTIONAL) where | ||
| 464 | REQUIRED and OPTIONAL are lists of KEYWORD designating field | ||
| 465 | names that must be, or may be, respectively, present in a message | ||
| 466 | adhering to that interface. KEY can be a keyword or a cons (SYM | ||
| 467 | TYPE), where type is used by `cl-typep' to check types at | ||
| 468 | runtime. | ||
| 469 | |||
| 470 | Here'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 | |||
| 483 | Value is a list of symbols (if the list is empty, no checks are | ||
| 484 | performed). | ||
| 485 | |||
| 486 | If the symbol `disallow-non-standard-keys' is present, an error | ||
| 487 | is raised if any extraneous fields are sent by the server. At | ||
| 488 | compile-time, a warning is raised if a destructuring spec | ||
| 489 | includes such a field. | ||
| 490 | |||
| 491 | If the symbol `enforce-required-keys' is present, an error is | ||
| 492 | raised if any required fields are missing from the message sent | ||
| 493 | from the server. At compile-time, a warning is raised if a | ||
| 494 | destructuring spec doesn't use such a field. | ||
| 495 | |||
| 496 | If the symbol `enforce-optional-keys' is present, nothing special | ||
| 497 | happens at run-time. At compile-time, a warning is raised if a | ||
| 498 | destructuring spec doesn't use all optional fields. | ||
| 499 | |||
| 500 | If the symbol `disallow-unknown-methods' is present, Eglot warns | ||
| 501 | on 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. | ||
| 580 | VARS is ([(INTERFACE)] SYMS...) | ||
| 581 | Honour `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. | ||
| 608 | Honour `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. | ||
| 615 | CLAUSES is a list (DESTRUCTURE FORMS...) where DESTRUCTURE is | ||
| 616 | treated 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. | ||
| 847 | Interactively, read SERVER from the minibuffer unless there is | ||
| 848 | only one and it's managing the current buffer. | ||
| 849 | |||
| 850 | Forcefully quit it if it doesn't respond within TIMEOUT seconds. | ||
| 851 | TIMEOUT defaults to 1.5 seconds. Don't leave this function with | ||
| 852 | the server still running. | ||
| 853 | |||
| 854 | If PRESERVE-BUFFERS is non-nil (interactively, when called with a | ||
| 855 | prefix argument), do not kill events and output buffers of | ||
| 856 | SERVER." | ||
| 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. | ||
| 872 | PRESERVE-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. | ||
| 916 | Return (MANAGED-MODES LANGUAGE-ID CONTACT-PROXY). | ||
| 917 | |||
| 918 | MANAGED-MODES is a list with MODE as its first elements. | ||
| 919 | Subsequent elements are other major modes also potentially | ||
| 920 | managed by the server that is to manage MODE. | ||
| 921 | |||
| 922 | If not specified in `eglot-server-programs' (which see), | ||
| 923 | LANGUAGE-ID is determined from MODE's name. | ||
| 924 | |||
| 925 | CONTACT-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'. | ||
| 951 | Return (MANAGED-MODE PROJECT CLASS CONTACT LANG-ID). If INTERACTIVE is | ||
| 952 | non-nil, maybe prompt user, else error as soon as something can't | ||
| 953 | be 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. | ||
| 1031 | This relies on `project-current' and thus on | ||
| 1032 | `project-find-functions'. Functions in the latter | ||
| 1033 | variable (which see) can query the value `eglot-lsp-context' to | ||
| 1034 | decide whether a given directory is a project containing a | ||
| 1035 | suitable 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 | |||
| 1044 | The LSP server of CLASS is started (or contacted) via CONTACT. | ||
| 1045 | If this operation is successful, current *and future* file | ||
| 1046 | buffers of MANAGED-MAJOR-MODE inside PROJECT become \"managed\" | ||
| 1047 | by the LSP server, meaning information about their contents is | ||
| 1048 | exchanged periodically to provide enhanced code-analysis via | ||
| 1049 | `xref-find-definitions', `flymake-mode', `eldoc-mode', | ||
| 1050 | `completion-at-point', among others. | ||
| 1051 | |||
| 1052 | Interactively, the command attempts to guess MANAGED-MAJOR-MODE | ||
| 1053 | from current buffer, CLASS and CONTACT from | ||
| 1054 | `eglot-server-programs' and PROJECT from | ||
| 1055 | `project-find-functions'. The search for active projects in this | ||
| 1056 | context binds `eglot-lsp-context' (which see). | ||
| 1057 | |||
| 1058 | If it can't guess, the user is prompted. With a single | ||
| 1059 | \\[universal-argument] prefix arg, it always prompt for COMMAND. | ||
| 1060 | With two \\[universal-argument] prefix args, also prompts for | ||
| 1061 | MANAGED-MAJOR-MODE. | ||
| 1062 | |||
| 1063 | PROJECT is a project object as returned by `project-current'. | ||
| 1064 | |||
| 1065 | CLASS is a subclass of `eglot-lsp-server'. | ||
| 1066 | |||
| 1067 | CONTACT specifies how to contact the server. It is a | ||
| 1068 | keyword-value plist used to initialize CLASS or a plain list as | ||
| 1069 | described in `eglot-server-programs', which see. | ||
| 1070 | |||
| 1071 | LANGUAGE-ID is the language ID string to send to the server for | ||
| 1072 | MANAGED-MAJOR-MODE, which matters to a minority of servers. | ||
| 1073 | |||
| 1074 | INTERACTIVE 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. | ||
| 1087 | INTERACTIVE 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. | ||
| 1116 | Use 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 | |||
| 1143 | That is before a connection was established. Use | ||
| 1144 | `eglot-connect-hook' to hook into when a connection was | ||
| 1145 | successfully established and the server on the other side has | ||
| 1146 | received the initializing configuration. | ||
| 1147 | |||
| 1148 | Each 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. | ||
| 1170 | This 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 \ | ||
| 1294 | in 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. | ||
| 1330 | Return a cons of two process objects (CONNECTION . INFERIOR). | ||
| 1331 | Name both based on NAME. | ||
| 1332 | CONNECT-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 | |||
| 1399 | This is the inverse operation of | ||
| 1400 | `eglot-move-to-column-function' (which see). It is a function of | ||
| 1401 | no arguments returning a column number. For buffers managed by | ||
| 1402 | fully 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. | ||
| 1408 | LBP 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 | |||
| 1425 | According to the standard, LSP column/character offsets are based | ||
| 1426 | on a count of UTF-16 code units, not actual visual columns. So | ||
| 1427 | when LSP says position 3 of a line containing just \"aXbc\", | ||
| 1428 | where X is a multi-byte character, it actually means `b', not | ||
| 1429 | `c'. However, many servers don't follow the spec this closely. | ||
| 1430 | |||
| 1431 | For buffers managed by fully LSP-compliant servers, this should | ||
| 1432 | be 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. | ||
| 1461 | If 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. | ||
| 1514 | Doubles 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. | ||
| 1542 | You could add, for instance, the symbol | ||
| 1543 | `:documentHighlightProvider' to prevent automatic highlighting | ||
| 1544 | under 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. | ||
| 1585 | If 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. | ||
| 1593 | If DONT-IF-JUST-THE-ONE and there's only one server, don't prompt | ||
| 1594 | and 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. | ||
| 1642 | Each element is a string, a symbol, or a regexp which is matched | ||
| 1643 | against a variable's name. Examples include the string | ||
| 1644 | \"company\" or the symbol `xref'. | ||
| 1645 | |||
| 1646 | Before Eglot starts \"managing\" a particular buffer, it | ||
| 1647 | opinionatedly sets some peripheral Emacs facilities, such as | ||
| 1648 | Flymake, Xref and Company. These overriding settings help ensure | ||
| 1649 | consistent Eglot behaviour and only stay in place until | ||
| 1650 | \"managing\" stops (usually via `eglot-shutdown'), whereupon the | ||
| 1651 | previous settings are restored. | ||
| 1652 | |||
| 1653 | However, if you wish for Eglot to stay out of a particular Emacs | ||
| 1654 | facility that you'd like to keep control of add an element to | ||
| 1655 | this list and Eglot will refrain from setting it. | ||
| 1656 | |||
| 1657 | For 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. | ||
| 1678 | Use `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 | |||
| 1779 | If 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'. | ||
| 1882 | Uses 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, \ | ||
| 1936 | still 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'. | ||
| 1980 | COMMAND 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'. | ||
| 2087 | THINGS 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'. | ||
| 2208 | Records 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 | |||
| 2262 | This variable's value should be a plist (SECTION VALUE ...). | ||
| 2263 | SECTION is a keyword naming a parameter section relevant to a | ||
| 2264 | particular server. VALUE is a plist or a primitive type | ||
| 2265 | converted to JSON also understood by that server. | ||
| 2266 | |||
| 2267 | Instead of a plist, an alist ((SECTION . VALUE) ...) can be used | ||
| 2268 | instead, but this variant is less reliable and not recommended. | ||
| 2269 | |||
| 2270 | This variable should be set as a directory-local variable. See | ||
| 2271 | See info node `(emacs)Directory Variables' for various ways to to | ||
| 2272 | that. | ||
| 2273 | |||
| 2274 | Here's an example value that establishes two sections relevant to | ||
| 2275 | the 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 | |||
| 2282 | The value of this variable can also be a unary function of a | ||
| 2283 | single argument, which will be a connected `eglot-lsp-server' | ||
| 2284 | instance. The function runs with `default-directory' set to the | ||
| 2285 | root of the current project. It should return an object of the | ||
| 2286 | format 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. | ||
| 2323 | When 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. | ||
| 2429 | Calls REPORT-FN (or arranges for it to be called) when the server | ||
| 2430 | publishes diagnostics. Between calls to this function, REPORT-FN | ||
| 2431 | may 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. | ||
| 2475 | Try 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. | ||
| 2511 | If 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. | ||
| 2665 | If either BEG or END is nil, format entire buffer. | ||
| 2666 | Interactively, format active region, or entire buffer if region | ||
| 2667 | is not active. | ||
| 2668 | |||
| 2669 | If non-nil, ON-TYPE-FORMAT is a character just inserted at BEG | ||
| 2670 | for 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'. | ||
| 3020 | Returns 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. | ||
| 3162 | Interactively, offer to execute them. | ||
| 3163 | If ACTION-KIND is nil, consider all kinds of actions. | ||
| 3164 | Interactively, default BEG and END to region's bounds else BEG is | ||
| 3165 | point and END is nil, which results in a request for code actions | ||
| 3166 | at 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. | ||
| 3322 | If 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 | ||