From fb8111d8222b09641a6aaab02d846ceac3ae97ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Mon, 13 Mar 2023 11:09:55 +0000 Subject: [PATCH] Update eglot.el from Emacs's upstream lisp/progmodes/eglot.el MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This brings in many changes: commit ba22a2c346b4a8cc992333037f6a30303a928a56 Author: João Távora Date: Fri Mar 10 11:30:58 2023 +0000 Bump Eglot version to 1.12 * etc/EGLOT-NEWS: New file. * etc/NEWS: Briefly mention etc/EGLOT-NEWS. * lisp/progmodes/eglot.el: Bump versions. commit 4a603c98499e6ffc0ea4b061a2895b4f7f8cb1c3 Author: João Távora Date: Sun Mar 12 19:30:53 2023 +0000 Amend last Eglot commit (bug#62065) The fix contained a spurious check for this-command that shouldn't have made it in.. * lisp/progmodes/eglot.el (eglot-ensure): Don't check this-command. commit b916ec88b2ffe22a49128f17cdfb78f0ab1bc713 Author: João Távora Date: Sun Mar 12 18:19:40 2023 +0000 Make eglot-ensure's post-command-hook run a bit later (bug#62065) 'eglot-ensure', typically used in the major-mode-hook, use 'post-command-hook' to schedule an automated, non-interactive connection attempt to a server. The goal is to connect when the buffer is ready, i.e. after the user command that found the file. However, if there are dir-local or buffer-local variables to confirm, finding the file will cause a minibuffer prompt to appear. In that case, 'eglot-ensure's addition to the global post-command-hook runs before it was intended too and a connection is started prematurely. In turn, this means that a call to 'hack-dir-local-variables' -- which is part of the connection process -- which also needs a minibuffer prompt, collides with the previous one. This generates an error and confuses the user, who doesn't know if the directory-local variables have been applied or not. This commit fixes the clash by having 'eglot-ensure' set 'post-command-hook' buffer-locally. This causes the automated connection to take place, as intended, after the user's original file-finding command has ended. However, the problem reported in bug#62065 is not completely fixed. If the user answers "no" to the first "confirm local variables" "prompt, she will be prompted again in the second one. A subsequent commit will address this separate problem. * lisp/progmodes/eglot.el (eglot-ensure): Use buffer-local post-command-hook. commit 01b65d442ad681e6fef0db76451e668125a17038 Author: João Távora Date: Thu Mar 9 13:07:02 2023 +0000 Autoload Eglot helper funtion eglot--debbugs-or-github-bug-uri This isn't a typical autoload: the progn block is plced in the autoloads file, but the eglot.el file itself isn't loaded as a result when the function inside the progn block is called. * lisp/progmodes/eglot.el (eglot--debbugs-or-github-bug-uri): autoload, but in progn. commit 6c66dbd02c7773a4347583b35b8caa219df30807 Author: João Távora Date: Sat Mar 4 15:05:15 2023 +0000 Turn on Eglot inlay hints by default This is like any other server-provided feature, and may be turned off client-side by setting eglot-ignored-server-capabilities like (add-to-list 'eglot-ignored-server-capabilities :inlayHintProvider) * lisp/progmodes/eglot.el (eglot--maybe-activate-editing-mode): Activate eglot-inlay-hints-mode. (eglot-inlay-hints-mode): Instead of warning about missing :inlayHintProvider, turn off eglot-inlay-hints-mode. commit 0bfba49ca7cf90e841c076d6db9bb3826e7b6a57 Author: João Távora Date: Sat Mar 4 12:49:00 2023 +0000 Robustify Eglot for "transient" projects When Eglot needs to synthesize a "transient" project for default-directory sometimes the value of that variable is set to an unexpanded value, sometimes not. This can cause simple invocations like. Emacs -Q ~/path/to-some-python-file.py -f eglot to fail, because eglot--current-server will be looking for a project in the registry called (transient . "~/path") where in reality it is stored there as (transient . "/home/someuser/path") The fix is to always expand default-directory in eglot--current-project. * lisp/progmodes/eglot.el (eglot--current-project): Use expand-file-name. commit bd5115e13479b1d81d6aa09efe362ad14d53c3c6 Author: João Távora Date: Sat Mar 4 00:09:32 2023 +0000 Remove Eglot activation check from find-file-hook Adding eglot--maybe-activate-editing-mode to find-file-hook isn't really necessary, since it is already in 'after-change-major-mode-hook', and that also runs every time we find a file. This reduces the number of project.el logic that runs every time the user visits a file. * lisp/progmodes/eglot.el (find-file-hook): No need to add eglot--maybe-activate-editing-mode here. commit 3a651773d29afb48ac4229cd19e532bd57a4ee2d Author: João Távora Date: Fri Mar 3 13:13:35 2023 +0000 Eglot: pay better attention to hints' paddingLeft/Right (bug#61924) * lisp/progmodes/eglot.el (eglot--update-hints-1): Consider :json-false, which is a non-nil value. commit 67befc1f5a5492fee5fb3891083df77831ec830e Author: João Távora Date: Thu Mar 2 13:27:14 2023 +0000 Eglot: use shell-file-name in eglot--cmd (bug#61748) * lisp/progmodes/eglot.el (eglot--cmd): Use shell-file-name. commit 0a4b1c0102d4062d24e19340f863b9df25e07ab3 Author: João Távora Date: Wed Mar 1 13:24:07 2023 +0000 ; Eglot: improve bug-reference-url-format/bug-reference-url-regexp * lisp/progmodes/eglot.el (eglot--debbugs-or-github-bug-uri): New helper. commit 40c9fc8e3b3f55c9122b95e21660b5709109bd1a Author: João Távora Date: Wed Mar 1 11:12:51 2023 +0000 Eglot: work around Tramp instability bug#61350 Unconditionally disable ControlMaster for the Eglot's Tramp connection. * lisp/progmodes/eglot.el (tramp-ssh-controlmaster-options) (use-tramp-ssh-controlmaster-options): Forward declare (eglot--connect): Set variables to unconditionally disable ControlMaster. commit 97a83ff31fa9e3e2455d3168a789649dcd20a635 Author: João Távora Date: Wed Mar 1 01:22:15 2023 +0000 Eglot: fix M-x eglot-show-workspace-configuration (bug#61866) Now consult .dir-locals.el every time the workspace configuration is needed: - workspace/configuration server request - workspace/didChangeConfiguration signal - M-x eglot-show-workspace-configuration The major-mode/hack-dir-local-variables-non-file-buffer trick is used. When there is more than one, the server connection's "main" major mode is used to find the relevant .dir-locals.el section. * lisp/progmodes/eglot.el (eglot--lookup-mode): Fix docstring. (eglot--connect): Simplify. (eglot-show-workspace-configuration): Fix. (eglot--workspace-configuration): Remove. (eglot--workspace-configuration-plist): Rework. (eglot-handle-request): Simplify. commit f601e9666d8b861712c09025711dc3a4261cb0ea Author: João Távora Date: Mon Feb 27 14:54:53 2023 +0000 Eglot: support multiple labels in same inlay hint Mainly the rust-analyzer LSP server uses this. There are still more things we could support, like tooltips and stuff. * lisp/progmodes/eglot.el (lsp-interface-alist): Add InlayHintLabelPart. (eglot--update-hints-1): Support multiple labels for same hint. commit 4a5eda7ed2ae7567d0d54871cc51e0c2c27d73a9 Author: João Távora Date: Mon Feb 27 14:23:35 2023 +0000 Eglot: don't paint hints outside requested region (bug#61812) * lisp/progmodes/eglot.el (eglot--lambda): Add cl-block. (eglot--update-hints-1): Return early if hint is outside the requested inlay hint range. commit 3d0a6c9baa613d69d75c3b2644e033a21da5f096 Author: João Távora Date: Mon Feb 27 11:29:32 2023 +0000 Eglot: protect against unintended field text motion (bug#61726) Suggested-by: Augusto Stoffel * lisp/progmodes/eglot.el (eglot--bol): New helper. (eglot-utf-8-linepos, eglot-utf-16-linepos) (eglot-utf-32-linepos, eglot-move-to-utf-8-linepos) (eglot-move-to-utf-16-linepos, eglot-move-to-utf-32-linepos) (eglot-handle-notification, eglot--xref-make-match) (eglot-completion-at-point): Use it. commit 647e40f4a0cf2c653d6ff6fc32116cbd2104d6ff Author: João Távora Date: Mon Feb 27 11:04:44 2023 +0000 ; And yet another fix to eglot-current-linepos-function's docstring bug#61726 * lisp/progmodes/eglot.el (eglot-current-linepos-function): Another fix. commit 7c552be89da02270993f0866035abedb8c44f890 Author: Eli Zaretskii Date: Sun Feb 26 16:48:07 2023 +0200 ; Another doc fix in eglot.el * lisp/progmodes/eglot.el (eglot-current-linepos-function): Another doc fix. commit 75c65fcc98ef5bda8fe91aeb8ba25c03e3182b8e Author: João Távora Date: Sun Feb 26 14:05:07 2023 +0000 ; Fix last change bug#61726 * lisp/progmodes/eglot.el (eglot-current-linepos-function): Fix docstring. commit a3d15c1f74988b488fc4f924b303f8de620645a9 Author: Eli Zaretskii Date: Sun Feb 26 15:24:11 2023 +0200 ; Fix last change * lisp/progmodes/eglot.el (eglot-current-linepos-function) (eglot-utf-8-linepos, eglot-utf-16-linepos) (eglot-utf-32-linepos, eglot-move-to-linepos-function) (eglot-move-to-utf-8-linepos, eglot-move-to-utf-32-linepos): Doc fixes. (Bug#61726) commit ca79b138d424766e3bfe8e4ca5d2b315b9ea4408 Author: João Távora Date: Sun Feb 26 12:50:42 2023 +0000 Eglot: rename and redocument encoding-related functions (bug#61726) * lisp/progmodes/eglot.el (eglot-current-column): Obsolete. (eglot-lsp-abiding-column): Obsolete. (eglot-current-column-function): Obsolete. (eglot-current-linepos-function): Rename from eglot-current-column-function. (eglot-utf-8-linepos): Rename from eglot-bytewise-column. (eglot-utf-16-linepos): Rename from eglot-lsp-abiding-column. (eglot-utf-32-linepos): Rename from eglot-current-column. (eglot-move-to-current-column): Obsolete. (eglot-move-to-lsp-abiding-column): Obsolete. (eglot-move-to-column-function): Obsolete. (eglot-move-to-linepos-function): Rename from eglot-move-to-column-function. (eglot-move-to-utf-8-linepos): Rename from eglot-move-to-bytewise-column. (eglot-move-to-utf-16-linepos): Rename from eglot-move-to-lsp-abiding-column. (eglot-move-to-utf-32-linepos): Rename from eglot-move-to-current-column. (eglot--managed-mode): Adjust. (eglot-client-capabilities): Trim whitespace. * test/lisp/progmodes/eglot-tests.el (eglot-test-lsp-abiding-column) (eglot-test-lsp-abiding-column-1): Use new function/variable names. commit 3e3e6d71be76b80beca946f9dd433d12aa450695 Author: Augusto Stoffel Date: Sun Feb 26 11:47:32 2023 +0000 Eglot: support positionEncoding LSP capability (bug#61726) * lisp/progmodes/eglot.el(eglot-client-capabilities): Announce the new capability. (eglot-bytewise-column, eglot-move-to-bytewise-column): New functions. (eglot--managed-mode): Set 'eglot-current-column-function' and 'eglot-move-to-bytewise-column' appropriately. commit b0e87e930e86d2e48659aed9cf85099c1fd2795c Author: Eli Zaretskii Date: Sun Feb 26 10:27:18 2023 +0000 Eglot: use faster strategy for moving to LSP positions (bug#61726) Turns out we don't need encode-coding-region after all. * lisp/progmodes/eglot.el (eglot-move-to-lsp-abiding-column): Rewrite. Co-authored-by: Augusto Stoffel commit 55d29c9bacb6227bc8b3a6c0dd52c7085fe63aaf Author: João Távora Date: Fri Feb 24 14:48:01 2023 +0000 Eglot: fix jit-lock inlay hint bugs One of the bugs was straightforward. The timer function of eglot--update-hints must set the correct buffer. The other is much more odd. When using Eglot on Emacs's own src/coding.c, the jit-lock code starts calling its jit-functions over and over again with the same sequence of arguments, like so: ====================================================================== 1 -> (eglot--update-hints 63551 65051) 1 <- eglot--update-hints: [nil 25592 52026 4 ====================================================================== 1 -> (eglot--update-hints 65051 66551) 1 <- eglot--update-hints: [nil 25592 52026 4 ====================================================================== 1 -> (eglot--update-hints-1 63551 66551) 1 <- eglot--update-hints-1: nil ====================================================================== 1 -> (eglot--update-hints 63551 65051) 1 <- eglot--update-hints: [nil 25592 52026 4 ====================================================================== 1 -> (eglot--update-hints 65051 66551) 1 <- eglot--update-hints: [nil 25592 52026 5 ====================================================================== 1 -> (eglot--update-hints-1 63551 66551) 1 <- eglot--update-hints-1: nil This continues forever at a very fast rate and saturates the LSP channel. At first I thought that it was because eglot--update-hints-1 is actually causing the buffer to be modified with overlays sometime in the future, but it is not so! It seems that merely calling (goto-char (eglot--lsp-position-to-point position)) (from the LSP request handler in eglot--update-hints-1) will cause this bug. * lisp/progmodes/eglot.el (eglot--update-hints): Fix bugs. commit 5db75ec7d30d5cf5dc610382ca25bd5a5c4f8fb6 Author: João Távora Date: Fri Feb 24 10:46:20 2023 +0000 Eglot: fix inlay hint with label collection instead of string Reported by Chinmay Dalal * lisp/progmodes/eglot.el (eglot--update-hints-1): Fix bug when inlay hint contains collection of labels. commit b0cbd5590b238fa9001e3f07b7035704ef976722 Author: João Távora Date: Thu Feb 23 23:51:09 2023 +0000 Eglot: simplify inlay hints implementation with jit-lock This implementation is much simpler than the one based on windows-scroll-functions. It's also supposedly safer, as long as jit-lock guarantees refontification of affected regions. It's not _trivially_ simple though, as simply adding 'eglot--update-hints-1' to jit-lock-functions, while possible, is going to request inlay hints from the LSP server for many small regions of the buffer, depending on what jit-lock thinks is best. So we keep coalescing these into a larger region until the time is suitable for a more bandwidth-efficient request. To do this, we use a jit-lock implementation detail, jit-lock-context-unfontify-pos, which is a proxy for knowing that the jit-lock-context-timer has run. Not sure how brittle it is, but it seems to work reasonably. We also get rid of the previous "get hints for entire buffer" implementation. * doc/misc/eglot.texi (Eglot Variables): Remove mention to deleted eglot-lazy-inlay-hints. * lisp/progmodes/eglot.el (eglot-lazy-inlay-hints) (eglot--inlay-hints-after-scroll) (eglot--inlay-hints-fully) (eglot--inlay-hints-lazily): Remove. (eglot--update-hints): Add function. (eglot-inlay-hints-mode): Simplify. commit 91e24c5b5a69495bcd706a5287c05bb5fd282700 Author: João Távora Date: Thu Feb 23 19:18:41 2023 +0000 Eglot: update inlay hints on window configuration changes * lisp/progmodes/eglot.el (eglot--inlay-hints-after-window-config-change): New helper. (eglot-inlay-hints-mode): Use it. commit 5c2be6a2632052b39b49899d1b19df2942ac6453 Author: Eli Zaretskii Date: Thu Feb 23 17:18:28 2023 +0200 ; Fix recently-added doc strings in eglot.el * lisp/progmodes/eglot.el (eglot-lazy-inlay-hints) (eglot-inlay-hints-mode): Doc fixes. commit e3be0dbf85c729447776d361ba56ada6b92f0149 Author: João Távora Date: Thu Feb 23 13:58:38 2023 +0000 Eglot: display completion label when safe Originally reported in https://github.com/joaotavora/eglot/discussions/1141 by "Mintsoup". Eglot doesn't always show the LSP :label property of a CompletionItem in the completion candidates. That is because label is sometimes not what should be inserted in the buffer in the end, the :insertText property supercedes it. But the label is usually more suitable for display nevertheless and if the LSP CompletionItem contains either a snippet or a textEdit, it's safe to display the label, since :exit-function will guarantee that a suitable buffer insertion is performed. This change reflects that awareness that when a textEdit is available, it's acceptable to show the label. * lisp/progmodes/eglot.el (eglot-completion-at-point): Adjust. commit 1841299a11dfcd875bdbdb75d1fc56d996a727f7 Author: João Távora Date: Tue Feb 21 14:14:05 2023 +0000 Eglot: implement inlay hints (bug#61412, bug#61066) Inlay hints are small text annotations to specific parts of the whole buffer, not unlike diagnostics, but designed to help readability instead of indicating problems. For example, a C++ LSP server can serve hints about positional parameter names in function calls and a variable's automatically deduced type. Emacs can display these hints in many little 0-length overlays with an 'before-string property, thus helping the user remember those types and parameter names. Since inlay hints are potentially a large amount of data to request from the LSP server, the implementation strives to be as parsimonious as possible with these requests. So, by default, inlay hints are only requested for the visible portions of the buffer across windows showing this buffer. This is done by leveraging the 'window-scroll-functions' variable, making for a reasonably complex implementation involving per-window timers. When scrolling a window, it may take a short amount of time for inlay hints to "pop in". The new user variable 'eglot-lazy-inlay-hints' can be used to exert some control over this. Specifically, if the variable's value is set to 'nil', then inlay hints are greedily fetched for the whole buffer every time a change occurs. This is a much simpler mode of operation which may avoid problems, but is also likely much slower in large buffers. Also, because the inlay feature is probably visually suprising to some, it is turned OFF by default, which is not the usual practice of Eglot (at least not when the necessary infrastructure is present). This decision may be changed soon. Here's a good one-liner for enabling it by default in every Eglot-managed buffer: (add-hook 'eglot-managed-mode-hook #'eglot-inlay-hints-mode) I haven't tested inlay hints extensively across many LSP servers, so I would appreciate any testing, both for functional edge cases and regarding performance. There are possibly more optimization oportunities in the "lazy" mode of operation, like more aggressively deleting buffer overlays that are not in visible parts of the buffer. Though I ended up writing this one from scratch, I want to thank Dimitry Bolopopsky and Chinmay Dala for suggestions and early patches. * lisp/progmodes/eglot.el (eglot--lsp-interface-alist): Define InlayHint. (eglot-client-capabilities): Announce 'inlayHint' capability. (eglot-ignored-server-capabilities): Add :inlayHintProvider. (eglot--document-changed-hook): New helper hook. (eglot--after-change): Use it. (eglot-inlay-hint-face, eglot-type-hint-face) (eglot-parameter-hint-face): New faces. (eglot--update-hints-1, eglot--inlay-hints-after-scroll) (eglot--inlay-hints-fully, eglot--inlay-hints-lazily): New helpers. (eglot-lazy-inlay-hints): New user variable. (eglot-inlay-hints-mode): New minor mode. (eglot--maybe-activate-editing-mode): Try to activate eglot-inlay-hints-mode. (eglot--before-change): Remove overlays immediately in the area being changed. (eglot--managed-mode-off): Remove overlays. * doc/misc/eglot.texi (Eglot Features): Mention inlay hints. (Eglot Variables): Mention eglot-lazy-inlay-hints. commit 28ed0d1840f94ba52b8b60bfbf222493fee2a3ea Author: João Távora Date: Wed Feb 22 18:44:39 2023 +0000 Eglot: run eglot-managed-mode-hook after LSP didOpen This allows using the hook for interacting with the LSP server using the current buffer as the subject of that interaction ("document" in LSP parlance). * lisp/progmodes/eglot.el (eglot--maybe-activate-editing-mode): Run eglot-managed-mode-hook here. (eglot--managed-mode): Not here. commit 7ad5d9babed68ddb8cc4bdf7571fdf10e44e1bae Author: João Távora Date: Wed Feb 22 18:50:46 2023 +0000 Eglot: restore eldoc-documentation-functions on shutdown * lisp/progmodes/eglot.el (eglot--managed-mode): Restore eldoc-documentation-functions when shutting down eglot. commit 711a775ba761e2838a6f73bf4b3119f0fe412841 Author: João Távora Date: Tue Feb 21 13:59:04 2023 +0000 Eglot: simplify capability-checking code * lisp/progmodes/eglot.el (eglot--server-capable-or-lose): New helper. (eglot--signal-textDocument/willSave) (eglot--signal-textDocument/didSave): Tweak docstring. (eglot--workspace-symbols, xref-backend-identifier-at-point) (eglot-format, eglot-completion-at-point, eglot-rename) (eglot-code-actions): Use new eglot--server-capable-or-lose. commit ea7251ad6dfe3cfbdcea221a67c7b7d4fcbfebfa Author: João Távora Date: Wed Feb 22 18:05:00 2023 +0000 Eglot: go back to setting eldoc-documentation-strategy again This commits reverts part of commit e83c78b8c7784254c2c6f043530ab325c2fa7f16 Author: João Távora Date: Mon Feb 20 22:43:50 2023 +0000 Eglot: respect user's Eldoc configuration by default In that commit, I did what many longstanding issues and users were suggesting and removed Eglot's override of two Eldoc user configuration varibles. I verified that Eglot's behaviour would stay mostly unaltered but my tests were very incomplete. In short there is no way that Eglot can work acceptably with the default setting of 'eldoc-documentation-strategy', which is 'eldoc-documentation-default'. So it must be changed, either globally or locally in Eglot's minor mode. This is true for any situation where both synchronous and asynchronous documentation sources are present. In Eglot's case there are two asynchronous sources which have more importance than the synchronous source. So any other strategy except the 'eldoc-documentation-default' makes sense. * lisp/progmodes/eglot.el (eglot--managed-mode): Set eldoc-documentation-strategy to eldoc-documentation-compose. commit e83c78b8c7784254c2c6f043530ab325c2fa7f16 Author: João Távora Date: Mon Feb 20 22:43:50 2023 +0000 Eglot: respect user's Eldoc configuration by default This change addresses the problems reported in many Elglot reports dating back to early 2021 at least: https://github.com/joaotavora/eglot/issues/648 https://github.com/joaotavora/eglot/issues/894 https://github.com/joaotavora/eglot/issues/920 https://github.com/joaotavora/eglot/issues/1031 https://github.com/joaotavora/eglot/issues/1171 In one form or another, the reports point out that the multiple pieces of information about the "thing at point" made available by the LSP server are not all being considered by the ElDoc system. The reason for this is Eglot setting/trampling the variables 'eldoc-documentation-strategy' and 'eldoc-documentation-functions' in its minor more entry function. The reason it did that is historical and is partially described in the issues above. But, evidently, it never made much sense, because so many people want to override it, which requires setting 'eldoc-documentation-strategy' to the non-default value 'eldoc-documentation-compose'. The problem was made worse by the fact that setting it as usual in either the Customize menu or their init file didn't work, requiring a fairly complex Elisp snippet. That is now solved as of this commit. If the user does not do any setting, then Eglot works basically the same as before (i.e. shows only one piece of information). It is arguable that the default value for 'eldoc-documentation-strategy' should change globally to 'eldoc-documentation-compose', but that has other subtle implications and is not part of this commit. * lisp/progmodes/eglot.el (eglot--managed-mode): Don't set Eldoc variables greedily. commit 893ddd5903e1b0345f22b124e5d6fe8fc89ef6af Author: Theodor Thornhill Date: Sun Feb 19 11:11:13 2023 +0000 Eglot: improve treatment of completion items without :sortText (bug#61532) Previously, defaulting to the empty string put candidates without :sortText to the top of the list. since string-lessp is safe with nil arguments, this makes them sort to the end instead. * lisp/progmodes/eglot.el (eglot-completion-at-point): Simplify. commit 7678b7e46f2e394447f39c3a6cf02bc285e5a5a4 Author: ~kby Date: Tue Feb 14 23:25:05 2023 +0000 Eglot: check server capability before sending didSave (bug#61478) * lisp/progmodes/eglot.el (eglot--signal-textDocument/didSave): check server capability. Copyright-paperwork-exempt: Yes commit b04cce02ff41a2ecb850e9772ab8d415cc6c8b50 Author: Eli Zaretskii Date: Thu Feb 9 12:17:18 2023 +0200 Fix Scala entry in Eglot's DB of LSP servers * lisp/progmodes/eglot.el (eglot-server-programs): Support Scala LSP named "metals", in addition to "metals-emacs". (Bug#61312) commit 2ac8c4bbd6f47751a68b0230310f6fddd7da8de5 Author: Dmitry Gutov Date: Tue Feb 7 00:28:25 2023 +0200 (eglot-completion-at-point): Return correct values in :company-kind * lisp/progmodes/eglot.el (eglot-completion-at-point): Return the correct values in :company-kind for "EnumMember" and "TypeParameter". The convention is to use kebab case rather than plain downcasing. Reported in https://github.com/company-mode/company-mode/issues/1370. commit f72a394716f4373dbbdc79ad0816da90bdb032a1 Author: Basil L. Contovounesios Date: Fri Jan 27 00:27:26 2023 +0000 Work around package.el transitive dependency bug Eglot already depends transitively on Xref 1.4.0 via Project, but package.el doesn't pick up on this in Emacs 28 (which has Xref 1.3.0). * lisp/progmodes/eglot.el (Version): Bump to 1.11. (Package-Requires): Explicitly require Xref 1.4.0, which is the version already required by Project, for the benefit of Emacs 28 (bug#61048). commit f367ba3ed03526356448a94addbb8554ece2f482 Author: Eli Zaretskii Date: Mon Jan 16 16:52:48 2023 +0200 ; Avoid byte-compiler warning in eglot.el * lisp/progmodes/eglot.el (eglot): Rename INTERACTIVE to avoid byte-compiler warning. Update the doc string. (Bug#60557) commit 1b458aced723dc752a699e509816b92399087775 Author: Eli Zaretskii Date: Mon Jan 16 16:08:46 2023 +0200 ; * lisp/progmodes/eglot.el: Remove stray space. commit 67df34c143d1adbf6d2817abb5da5dcad06369bb Author: João Távora Date: Mon Jan 16 11:48:09 2023 +0000 Fix M-x eglot prompt when connection already exists (bug#60557) Before this change, if a current connection existed at the time of M-x eglot, user would be first asked to enter into M-x eglot's interactive spec details about new command line arguments, and only afterwards be prompted about what to do with the current connection (which could be to reconnect to it using the same arguments, or tear it down and make a new one). This is very confusing, as users may not be fully aware of the distinction between "reconnect" vs "disconnect-and-connect". They might not know if any new command line arguments provided are taking effect or not. This change simplifies this and removes the option to "reconnect instead" from M-x eglot (users can do that at any time via M-x eglot-reconnect). It also ensures that users are informed about a current connection before asking to enter new command line arguments and not the other way round. * lisp/progmodes/eglot.el (eglot): Rework. commit f1032bf24e79cf32341473c5d9f447c4c74f9d2b Author: Eshel Yaron Date: Sat Jan 14 10:12:11 2023 +0200 Eglot: don't use "nil" as minibuffer initial input Doing M-x eglot in a buffer for which buffer-file-name is nil, prompts the user for a major mode to manage by invoking completing-read. The way completing-read was called would end up with the string "nil" as the initial minibuffer input, which is not very useful nor is it a valid input. * lisp/progmodes/eglot.el (eglot--guess-contact): Tweak prompt for major mode. (Bug#60379) Copyright-paperwork-exempt: yes commit 0562006da3b6f0147069a9aea75c08a9a0a4e6d8 Author: Perry Smith Date: Mon Jan 2 02:57:38 2023 +0200 Add ruby-ts-mode * etc/NEWS: Mention the new mode. * lisp/progmodes/ruby-ts-mode.el: New file. * test/lisp/progmodes/ruby-ts-mode-tests.el: New file. * lisp/progmodes/eglot.el (eglot-server-programs): Add ruby-ts-mode to the Ruby entry. Co-authored-by: Dmitry Gutov commit cae528457cb862dc886a34240c9d4c73035b6659 Author: Eli Zaretskii Date: Sun Jan 1 05:31:12 2023 -0500 ; Add 2023 to copyright years. commit 014232d3840e9d7249fe28636935b7166b85e675 Author: João Távora Date: Thu Dec 22 15:44:11 2022 +0000 Eglot: eglot--servers-by-xrefed-file doesn't need to be value-weak * lisp/progmodes/eglot.el (eglot--servers-by-xrefed-file): Doesn't need to be weak. commit bbe35c280c2bf9fb2fd9b6e33b2950b8fae67e2c Author: João Távora Date: Thu Dec 22 11:29:49 2022 +0000 Prevent stale servers when using eglot-extend-to-xref A weak-valued hash-table is not enough to guarantee that a reference to a zombie server in eglot--servers-by-xrefed-file variable won't survive long enough to confuse the next call to eglot--current-server in some buffers. So, before this fix it was common to get "Process EGLOT ... not running" errors if some xref-extended buffers (like system libraries) were open and M-x eglot-reconnect was issued. This should be prevented now. Note however, that even after this the eglot-extend-to-xref logic is still flawed. For example, if a buffer for the xref-extended buffer happens to be already visited by the time M-. is issued to navigate to it, Eglot won't be activated. A half-decent workaround is to kill the buffer and re-visit it. * lisp/progmodes/eglot.el (eglot--servers-by-xrefed-file): Move up. (eglot--on-shutdown): Make sure to cleanup eglot--servers-by-xrefed-file. commit d03ea8937803c6714df71dd148c79ca893d159e9 Author: Brian Leung Date: Mon Dec 19 15:03:06 2022 -0800 eglot.el: Add vscode-json-languageserver to eglot-server-programs * lisp/progmodes/eglot.el (eglot-server-programs): Add the alternative name of the vcscode JSON server. (Bug#60198) commit cb8ccdd26702606710258e2e08bd1fc35bbc1674 Author: Randy Taylor Date: Fri Dec 16 16:05:29 2022 -0500 Add rust-ts-mode (Bug#60136) * etc/NEWS: Mention it. * lisp/progmodes/eglot.el (eglot-server-programs): Add it. * lisp/progmodes/rust-ts-mode.el: New major mode with tree-sitter support. commit 4f9bccef556d57590444e384b16d94c81e5323c9 Author: Randy Taylor Date: Tue Dec 13 09:41:01 2022 -0500 Add yaml-ts-mode (Bug#60105) * admin/notes/tree-sitter/build-module/batch.sh: * admin/notes/tree-sitter/build-module/build.sh: Add yaml support. * etc/NEWS: Mention it. * lisp/textmodes/yaml-ts-mode.el: New major mode with tree-sitter support. * lisp/progmodes/eglot.el (eglot-server-programs): Add it. commit 7575c85efd27f3a4e8e968d0bef1c63d6a2b8d84 Author: João Távora Date: Fri Dec 16 09:23:21 2022 +0000 Bump Eglot version to 1.10 * lisp/progmodes/eglot.el (Version): Bump to 1.10 (Package-Requires): Bump required versions of jsonrpc and project. commit a0806bc7ea916bff49ecb452716c01d42307aec9 Author: João Távora Date: Fri Dec 16 09:29:32 2022 +0000 Eglot: fix discrepant eglot-guess-contact/eglot-command-history Due to a typo, the defvar eglot--command-history wasn't actually used in eglot-guess-contact as intended. That function used a single-dash-name version of the variable instead. This worked fine, except that two variables were created instead of one, and the one actually being used didn't have any docstring. Rename the variable to eglot-command-history to fix this. It's better than renaming the reference in eglot-guess-contact which would lose user's history for M-x eglot. * lisp/progmodes/eglot.el (eglot-command-history): Rename from eglot--command-history. commit 546aed35434fb2cd4082dc2cee93236b9b62a60c Author: Jostein Kjønigsen Date: Thu Dec 15 13:44:39 2022 +0100 eglot: Add support for new language server csharp-ls C# has two popular language servers: Omnisharp Roslyn (already supported) and csharp-ls (newer, more performant). * lisp/progmodes/eglot.el: Add new C# language server csharp-ls. (Bug#60089) commit fee2efe1b035d601ac53a32801227402e9be8bca Author: Randy Taylor Date: Sun Dec 11 18:41:16 2022 -0500 Add go-ts-mode and go-mod-ts-mode (Bug#60025) * admin/notes/tree-sitter/build-module/batch.sh: * admin/notes/tree-sitter/build-module/build.sh: Add go-mod support. * etc/NEWS: Mention them. * lisp/progmodes/eglot.el (eglot-server-programs): Add them. * lisp/progmodes/go-ts-mode.el: New major modes with tree-sitter support. commit ca67d988d8721e9ec24a040b977393136457873f Author: Randy Taylor Date: Sat Dec 10 21:40:25 2022 -0500 Add cmake-ts-mode * admin/notes/tree-sitter/build-module/batch.sh: * admin/notes/tree-sitter/build-module/build.sh: Add cmake support. * etc/NEWS: Mention it. * lisp/progmodes/cmake-ts-mode.el: New major mode with tree-sitter support. * lisp/progmodes/eglot.el (eglot-server-programs): Add it. commit d3669cfe156f43ca17b5d804fc9fd7fa1f8b0e26 Author: João Távora Date: Sun Dec 11 23:16:58 2022 +0000 Eglot: allow skipping compile-time warnings about LSP interfaces * lisp/progmodes/eglot.el (eglot-strict-mode): Add 'no-unknown-interfaces'. (eglot--check-object): Honour new eglot-strict-mode value. commit 8f49137c9bf614b285c19a3a845c7606fcba23a4 Author: Randy Taylor Date: Wed Dec 7 20:53:35 2022 -0500 Add dockerfile-ts-mode (Bug#59894) * admin/notes/tree-sitter/build-module/batch.sh: Add dockerfile support. * admin/notes/tree-sitter/build-module/build.sh: Support different namespaces and add dockerfile support. * etc/NEWS: Mention it. * lisp/progmodes/dockerfile-ts-mode.el: New major mode with tree-sitter support. * lisp/progmodes/eglot.el (eglot-server-programs): Add it. commit 74a009dd96a643b12a63a29fba3d40a74e7d82a2 Author: dannyfreeman Date: Fri Dec 9 12:49:26 2022 +0000 Eglot: Handle LSP progress with Emacs progress reporters (bug#59149) Co-authored-by: João Távora * lisp/progmodes/eglot.el (eglot-report-progress): New custom variable. (eglot-lsp-server): New slot for tracking active progress reporters. (eglot-handle-notification (eql $/progress)): New method. The LSP spec describes methods for reporting progress on long running jobs to the client: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#progress https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workDoneProgress This change reports those notifications in the minibuffer as they come in. It shows a percent indicator (if the server provides theme), or a spinner. This change could open the door for writing a "cancel long running request" command, which are identified by these progress notifications. See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_workDoneProgress_cancel * doc/misc/eglot.texi (Customizing Eglot): Describe new variable. commit 0cfeb1c2bc98b3b4c9ea8c28d176ac65b9211f7f Author: dannyfreeman Date: Fri Dec 9 11:40:53 2022 +0000 Eglot: cleanup whitespace and indentation Co-authored-by: João Távora * lisp/progmodes/eglot: Misc whitespace fixes. commit fef17557365010a03059f3954da89f74408b3485 Author: João Távora Date: Wed Dec 7 10:59:30 2022 +0000 Use new external-completion.el in Eglot This refactor simplifies Eglot's source code. * lisp/progmodes/eglot.el: (Package-Requires): Require external-completion. (external-completion): Require it. (xref-backend-identifier-completion-table): Use external-completion-table. (completion-category-overrides): No longer set it. ("Backend Completion"): Delete section. commit bfc00f1c12047ff431eaa9da3a744807c3f9e6e2 Author: João Távora Date: Wed Dec 7 11:30:34 2022 +0000 Eglot: fix setting of flymake-list-only-diagnostics (bug#59824) When Eglot receives diagnostics for a file not yet visited in Emacs, it stores them in flymake-list-only-diagnostics, which feed M-x flymake-show-project-diagnostics. If the file is eventually visited in a buffer and Eglot starts receibing diagnostics for it, the flymake-list-only-diagnostics database has to be updated accordingly, since the previous diagnostics are now stale. * lisp/progmodes/eglot.el (eglot-handle-notification): Reset flymake-list-only-diagnostics commit 2b9cd50f935d06e627c7a6de239b78dafbff670a Author: Brian Leung Date: Fri Dec 2 03:02:30 2022 -0800 Add tsx-ts-mode to eglot-server-programs * lisp/progmodes/eglot.el (eglot-server-programs): Add tsx-ts-mode. (Bug#59770) commit 00cb6e052a6adce087e24871068737e10c8adc10 Author: Stefan Kangas Date: Tue Nov 29 20:28:16 2022 +0100 ; Improve error message when LSP server not found * lisp/progmodes/eglot.el (eglot--guess-contact): Improve wording of error message when no LSP server was found. commit 5b325a03c50d5d828d2a7934bb74f30a45fa108d Author: Stefan Kangas Date: Mon Nov 28 15:32:25 2022 +0100 ; Fix typo in eglot-alternatives (Bug#59653) * lisp/progmodes/eglot.el (eglot-alternatives): Fix typo. Problem reported by Arash Esbati . commit 40ff40198d817b1385694499a4dce9adb313ea6f Author: Theodor Thornhill Date: Sat Nov 26 21:12:51 2022 +0100 Add js-ts-mode to eglot-server-programs * lisp/progmodes/eglot.el (eglot-server-programs): Add js-ts-mode as an alternative. (Bug#59252) commit 372f8c5bffd8c18c95900a1fb53eb992b8a493aa Author: Brian Leung Date: Fri Nov 25 18:16:52 2022 -0800 eglot-server-programs: ts-mode -> typescript-ts-mode * lisp/progmodes/eglot.el (eglot-server-programs): Fix name of 'typescript-ts-mode' after rename from 'ts-mode'. (Bug#59589) commit 5ce34327eef604c27ba0d4223045c49fdc66fd38 Author: Brian Leung Date: Thu Nov 24 10:48:07 2022 -0800 Add more tree-sitter modes to eglot-server-programs * lisp/progmodes/eglot.el (eglot-server-programs): Add python-ts-mode and bash-ts-mode. (Bug#59550) commit 80b2534605698c231abc970ff4ae6b1b87cee13c Author: Eli Zaretskii Date: Thu Nov 24 20:54:38 2022 +0200 ; * lisp/progmodes/eglot.el (eglot-server-programs): Add csharp-ts-mode. commit 94ec173a732641f00535997e8381b9ef2c2814cd Author: Brian Leung Date: Sat Nov 12 14:30:37 2022 -0800 Add tree-sitter-based modes to eglot-server-programs * lisp/progmodes/eglot.el (eglot-server-programs): Add tree-sitter-based major modes. (Bug#59229) commit d34fc7b7aa9d2779ebbada5cecd8bd2806e3e01e Author: dannyfreeman Date: Thu Nov 24 15:22:33 2022 +0000 Eglot: don't confuse URLs and windows file paths bug#59338 * lisp/progmodes/eglot.el (eglot--path-to-uri): Check for windows path commit fcd5fde090022c2b1a6c8e130634539892857f9a Author: João Távora Date: Thu Nov 24 14:48:35 2022 +0000 Remove fboundp check in eglot--connect project.el is a GNU ELPA :core package, so this kind of trick isn't needed. * lisp/progmodes/eglot.el (eglot--connect): Don't fboundp project-name. commit cf439636d466d999a1f064fe9783013d3f3db17d Author: Marcin Pajkowski Date: Sun Nov 20 20:03:57 2022 +0100 Eglot: Advertise completion.resolveSupport capabilities Some servers avoid reporting completion items that require "additionalTextEdits" capability. Actually, 'eglot-completion-at-point' function supports such feature so it can be advertised to LSP server. * lisp/progmodes/eglot.el (eglot-client-capabilities): Advertise resolveSupport. (bug#59465) Copyright-paperwork-exempt: yes commit 1f39da3098a2a4cec9985e6db934ed14b7b522b7 Author: Stephen Leake Date: Tue Nov 22 11:43:22 2022 -0800 * lisp/progmodes/eglot.el (eglot--connect): Use project-name As discussed in bug#48747. commit beaa2e49530b8149d2c22cb5fa15b8d48f7937b6 Author: Stefan Kangas Date: Mon Nov 21 15:16:08 2022 +0100 ; Fix typos (misspelled symbols) commit 6b0179f7908c658342d1e642e5444e3d2e1cd997 Author: Stephen Leake Date: Sun Nov 20 13:45:13 2022 -0800 Delete eglot spinner; not useful * lisp/progmodes/eglot.el (eglot-lsp-server): Delete slot spinner. (eglot--mode-line-format): Don't include spinner in mode-line. (eglot--signal-textDocument/didChange): Don't set spinner. commit 16318bfb518aa7bc06e502e6fad7e53ec91067f9 Author: Stefan Kangas Date: Sun Nov 20 12:59:39 2022 +0100 ; Fix typos commit b6a7b42b199d4e96312bf8147d36dddbcf14f4ea Author: Eli Zaretskii Date: Thu Nov 17 11:47:34 2022 +0200 * lisp/progmodes/eglot.el (eglot-server-programs): Remove Intelephense. commit 954a5e79dc8670e93ee72b3f835ec82e847dc23d Author: USAMI Kenta Date: Fri Nov 11 22:57:06 2022 +0900 Add Eglot alternatives for PHP language servers * lisp/progmodes/eglot.el (eglot-server-programs): Add alternatives for PHP. commit 93036209fae87dc620b27b2ce2e0146e6252113c Author: Stefan Kangas Date: Thu Nov 17 09:31:46 2022 +0100 ; Fix typos (duplicate words) commit 623db40dd1cd21623c5cecdc0abbf3ce885f92b1 Author: Juanma Barranquero Date: Thu Nov 17 08:45:57 2022 +0100 ; * lisp/*.el: Fix typos in docstrings * lisp/gnus/nnrss.el (nnrss-use-local, nnrss-fetch, nnrss-find-el): * lisp/leim/quail/japanese.el ("japanese"): * lisp/org/ol.el (org-link-search-must-match-exact-headline): * lisp/org/org-faces.el (org-column): * lisp/progmodes/eglot.el (eglot--stay-out-of-p) (eglot-workspace-configuration, eglot--read-execute-code-action): * lisp/vc/vc.el (vc-clone): Fix typos in docstrings. commit 7d53164162b3e36b53f52f4132cea3202919f749 Author: Ingo Lohmar Date: Sun Nov 13 17:27:12 2022 +0100 Eglot: fix null scopeUri regression in workspace/configuration * lisp/progmodes/eglot.el (eglot-handle-request): Commit 1a2d603bb3938ff68ed1a5412d131b41efd40a24 changed `eglot--uri-to-path' to return a nil uri untouched. (Before, `url-unhex-string' turned the parsed all-nil uri record into the empty string.) A nil return value must now be handled in the caller, do that for the workspace/configuration handler to avoid an uncaught error. commit ae9e4414159b974e299f891a4edb46c89ca5b7ba Author: Davide Masserut Date: Sun Nov 13 12:30:43 2022 +0100 Add new Go modes to eglot-server-programs * lisp/progmodes/eglot.el (eglot-server-programs): Add new major modes for Go to be used with gopls. (Bug#59245) commit 9d334f558a1151ff12b7022a88c75ceb4f1fbdf4 Author: Michal Dubiel Date: Sun Oct 23 19:54:31 2022 +0200 eglot: Support signature labels without a function name * lisp/progmodes/eglot.el (eglot--sig-info): Support signature labels without a function name. (Bug#58777) Copyright-paperwork-exempt: yes commit d794f1d3f22af458c496978fc89bbb5be37f433e Author: Shohei YOSHIDA Date: Sat Nov 12 22:13:57 2022 +0900 Add cperl-mode to eglot-server-programs commit be1745606354e8b34325bc9526c9bad9f7302cce Author: Brian Leung Date: Tue Feb 2 11:23:25 2021 -0800 Make Eglot consider FileSystemWatcher.kind when watching files bug#58677 * eglot.el (eglot-register-capability workspace/didChangeWatchedFiles): Rework Only send notifications of interest, as determined by the optional LSP FileSystemWatcher.kind bitmask provided by the server. When the FileSystemWatcher.kind property is omitted, use the default value of 7, which is computed from taking the bitwise OR operation WatchKind.Create (1) | WatchKind.Change (2) | WatchKind.Delete (4). commit 6f48b46b03cd58a541684c57e42eb4679794f0fd Author: Stefan Kangas Date: Fri Nov 11 15:46:23 2022 +0100 ; Fix typos commit c64d94c84979425665b9ca88c1cad5830d2fabe0 Author: Stefan Kangas Date: Fri Nov 11 13:55:11 2022 +0100 eglot: Remove menu entry for manual * lisp/progmodes/eglot.el (eglot-manual): Make obsolete. Open the eglot info manual instead of the less exhaustive README. (eglot-menu): Remove entry for reading the manual. (Bug#58892) commit 23b49c0178d0c651c3af115fe2f357df0cdaaf01 Author: João Távora Date: Fri Nov 11 09:30:49 2022 +0000 ; Tweak comment in lisp/progmodes/eglot.el (bug#58790) * lisp/progmodes/eglot.el (eglot--uri-to-path): Tweak comment. commit 05d1186b7af9b4daf4c067526ff390d53ac866ae Author: Eli Zaretskii Date: Fri Nov 11 10:22:29 2022 +0200 ; Fix a typo in last change. commit d764a2bfa88c4b33a290acd088ef4a4315d8902d Author: Eli Zaretskii Date: Fri Nov 11 10:21:45 2022 +0200 ; * lisp/progmodes/eglot.el (eglot--path-to-uri): Improve commentary. commit 1a2d603bb3938ff68ed1a5412d131b41efd40a24 Author: dannyfreeman Date: Thu Nov 3 09:39:16 2022 -0400 Eglot: Only handle URIs with the file:// scheme (bug#58790) Eglot will not attempt to parse URIs that are not file:// type at all. Instead let 'file-name-handler-alist' entries to deal with those. Not parsing them at all allows the 'file-name-handler-alist' regexps to identify them more accurately. By also checking if Eglot received a URI in 'eglot--path-to-uri', 'file-name-handler-alist' can provide the non-file type URI back to the lsp server, which presumably will know how to handle them since it is also giving them out to clients. This issue originated with clojure-lsp sending clients "jar:" type URIs that Emacs is unable to handle out of the box. Before this change, "jar:" URIs were parsed once, but since they contain a nested URI, this resulted in a file being dispatched with a partially parsed path that looked like "file://path/to.jar!/path/in/jar". * lisp/progmodes/eglot.el (eglot--path-to-uri): Noop if already an URI. (eglot--uri-to-path): Only handle file:// URIs commit c3b64985aa6f61886a24974836635284c86478ef Author: João Távora Date: Thu Nov 10 21:06:33 2022 +0000 Improve Eglot's docstrings and manual The examples in the manual can now be copy-pasted to user's init files as-is. * doc/misc/eglot.texi (Setting Up LSP Servers): Use `with-eval-after-load'. Add eglot-alternatives example. (Customizing Eglot): Use 'require and fix a typo. * lisp/progmodes/eglot.el (eglot-server-programs): Mention eglot-alternatives in eglot-server-program's docstring. commit c833b291f57e61613cbf09cffae478aa02e6ecc5 Author: Evgeni Kolev Date: Thu Nov 10 17:17:44 2022 +0000 Ignore errors when shutting down all LSP servers (bug#59146) eglot-shutdown-all has a bug that results in partially stopping servers: when a server shutdown timeouts (1.5s), an error is propagated and eglot-shutdown-all does not try to shutdown the rest of the servers. * eglot.el (eglot-shutdown-all): Ignore errors when shutting down servers, converting errors to messages. commit dc4aef3b85f22e5d529c5ceb0686f26781587601 Author: Arash Esbati Date: Thu Nov 10 10:18:43 2022 +0100 Support 'texlab' LSP server in Eglot OOTB * lisp/progmodes/eglot.el (eglot-server-programs): Add support for 'texlab' LSP server for tex-mode and similar languages. commit 43db0e2784bfafdb8b08a2f5f075e2d432df132f Author: Stephen Leake Date: Wed Nov 9 06:00:40 2022 -0800 Delete emacs < 26.2 workaround in eglot--apply-text-edits * lisp/progmodes/eglot.el (eglot--apply-text-edits): Delete emacs < 26.2 workaround; fixes bug in ada-mode test. commit 5b9b393c614f18f7cc2260436fbf99ed2a6d05ed Author: Stephen Leake Date: Tue Nov 8 09:45:26 2022 -0800 * lisp/progmodes/eglot.el (eglot--pos-to-lsp-position): Improve comment commit 969d71d11c62ae085f094a96ecea2fb504bb4602 Author: Juri Linkov Date: Wed Nov 2 20:22:10 2022 +0200 Add the parameter :noquery to open-network-stream (bug#58948) * doc/lispref/processes.texi (Network): Add :noquery for open-network-stream. * lisp/net/network-stream.el (open-network-stream): Pass the parameter :noquery to make-network-process. Doc fix. * lisp/progmodes/eglot.el (eglot--connect): Pass `:noquery t' to eglot--inferior-bootstrap to use in open-network-stream call, like `:noquery t' is passed to make-process in other places. commit 4cc32937c06f7dd66da025fdb98369f456f1af0a Author: Stephen Leake Date: Mon Oct 31 17:30:02 2022 -0700 Fix eglot-shutdown-all: Match current eglot-shutdown signature commit db2ed9f333879e5ac283fb48c8b06ed4022f0af9 Author: Stefan Kangas Date: Wed Oct 26 22:41:09 2022 +0200 ; Fix several symbol name typos commit c8fe6aae0ac352e48fff2bdec966a0605be63bac Author: Stefan Kangas Date: Tue Oct 25 16:42:58 2022 +0200 eglot: Prefer ensure-list on Emacs 28 or later * lisp/progmodes/eglot.el (eglot--ensure-list): Make into alias for 'ensure-list' on Emacs 28 or later. commit 8c3b8c36677eedfc3839488e3cef9f6a5937baa3 Author: Robert Pluim Date: Tue Oct 25 15:18:51 2022 +0200 Fix eglot defcustom types * lisp/progmodes/eglot.el (eglot-autoreconnect): Allow for 'nil'. (eglot-connect-timeout): Allow for 'nil' and add descriptions. (eglot-sync-connect): Split boolean into 'nil' and 't' and add descriptions. (eglot-confirm-server-initiated-edits): Change 'symbol' type to 'const'. commit 5b90a718ee6674544f892aa572ff042c1ae2a21f Author: Robert Pluim Date: Tue Oct 25 15:15:34 2022 +0200 * lisp/progmodes/eglot.el: Remove not very funny joke. commit bb95e597a9adcba0080cba85b2270fdf80696b3a Author: Brian Leung Date: Mon Oct 24 20:46:41 2022 -0700 eglot-server-programs: Account for new ts-mode At the time of writing, this exists only in the unmerged tree-sitter branch. It is not harmful to include, however. * lisp/progmodes/eglot.el (eglot-server-programs): Add new major mode to be used with the typescript-language-server. (Bug#58769) commit 566e410287d9898f90c81131bfa8c85462fab55e Author: Brian Leung Date: Mon Oct 24 20:43:50 2022 -0700 eglot-server-programs: Account for new js-json-mode js-json-mode derives from js-mode, so this reordering is necessary to prevent js-mode's eglot server from starting if a user has something like this in their config: (add-to-hook 'js-json-mode #'eglot-ensure) (add-to-hook 'js-mode #'eglot-ensure) * lisp/progmodes/eglot.el (eglot-server-programs): Move the json-language-server info before the entry for js-mode, and add on js-json-mode. (Bug#58769) commit 0b1eda215d32839c9f6281d20f8a347f34bb5ab5 Author: João Távora Date: Tue Oct 25 10:34:08 2022 +0100 Fix M-x eglot breakage due to typo * lisp/progmodes/eglot.el (eglot): Fix bug. commit 31945b6c3fcbdb6f242f0063811d2fb91e4520cd Author: Stephen Leake Date: Tue Oct 25 02:15:13 2022 -0700 * lisp/progmodes/eglot.el (eglot): Ensure managed-major-mode is a list commit 3ad9ac25a86b1ef98e27efebfb920780eca4bb77 Author: Brian Leung Date: Thu Oct 20 22:47:10 2022 -0700 Add the "nil" language server to eglot-server-programs * lisp/progmodes/eglot.el (eglot-server-programs): Add the "nil" language server. Put it before rnix-lsp since it is more featureful and more actively updated. (Bug#58676) Ref: https://github.com/oxalica/nil commit 81177e77cff1aa0912e5ad7a58db09bcb9901c36 Author: Brian Leung Date: Thu Oct 20 22:49:08 2022 -0700 Add lua-language-server to eglot-server-programs * lisp/progmodes/eglot.el (eglot-server-programs): Add lua-language-server. It is, at the time of writing, more actively developed and more popular than the lua-lsp server, so prioritize that. (Bug#58676) commit 1324baea728a11bf650303698881c682105155da Author: Eli Zaretskii Date: Thu Oct 20 20:50:34 2022 +0300 Add Eglot to the menu bar * lisp/progmodes/eglot.el (eglot): Improve the doc string. * lisp/menu-bar.el (menu-bar-tools-menu): Add Eglot to the menu. commit 8b3a7003274de7b184b71c4552e6c4518948bcfe Author: João Távora Date: Thu Oct 20 13:49:49 2022 +0100 ; fix warning about order of defvaralias/defconst * lisp/progmodes/eglot.el (eglot-{}): Declare alias before thing being aliased. commit 806734c1b1f433de43d59d9a5e3a1e89d64315f6 Author: João Távora Date: Thu Oct 20 11:06:44 2022 +0100 Expose eglot-{} to be used in eglot-workspace-configuration * eglot.el (eglot-{}): New variable alias. GitHub-reference: per https://github.com/joaotavora/eglot/issues/1084 commit 9801e217f9842190f2303e46f6d41202cfe6b546 Author: João Távora Date: Thu Oct 20 10:48:11 2022 +0100 Rework header of eglot.el * eglot.el (Commentary): Rework. commit eb9d6281b58f50927afdc2fdb2fcebf76e2ffe23 Author: João Távora Date: Mon Oct 10 13:57:26 2022 +0100 Do use eglot-connect-timeout if eglot-sync-connect is t Reported by Eli Zaretskii * eglot.el (eglot--connect): Use eglot-connect-timeout in the case eglot-sync-connect is t. --- eglot.el | 1006 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 620 insertions(+), 386 deletions(-) diff --git a/eglot.el b/eglot.el index 901bf30d..428f59a8 100644 --- a/eglot.el +++ b/eglot.el @@ -1,15 +1,15 @@ ;;; eglot.el --- The Emacs Client for LSP servers -*- lexical-binding: t; -*- -;; Copyright (C) 2018-2022 Free Software Foundation, Inc. +;; Copyright (C) 2018-2023 Free Software Foundation, Inc. -;; Version: 1.9 +;; Version: 1.12 ;; Author: João Távora ;; Maintainer: João Távora ;; URL: https://github.com/joaotavora/eglot ;; Keywords: convenience, languages -;; 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")) +;; Package-Requires: ((emacs "26.3") (jsonrpc "1.0.16") (flymake "1.2.1") (project "0.9.8") (xref "1.6.2") (eldoc "1.11.0") (seq "2.23") (external-completion "0.1")) -;; This is is a GNU ELPA :core package. Avoid adding functionality +;; This is a GNU ELPA :core package. Avoid adding functionality ;; that is not available in the version of Emacs recorded above or any ;; of the package dependencies. @@ -47,16 +47,17 @@ ;; definition-chasing, Flymake for diagnostics, Eldoc for at-point ;; documentation, etc. Eglot's job is generally *not* to provide ;; such a UI itself, though a small number of simple -;; counter-examples do exist, for example in the `eglot-rename' -;; command. When a new UI is evidently needed, consider adding a -;; new package to Emacs, or extending an existing one. +;; counter-examples do exist, e.g. in the `eglot-rename' command or +;; the `eglot-inlay-hints-mode' minor mode. When a new UI is +;; evidently needed, consider adding a new package to Emacs, or +;; extending an existing one. ;; ;; * Eglot was designed to function with just the UI facilities found ;; in the latest Emacs core, as long as those facilities are also ;; available as GNU ELPA :core packages. Historically, a number of ;; :core packages were added or reworked in Emacs to make this ;; possible. This principle should be upheld when adding new LSP -;; features or tweaking exising ones. Design any new facilities in +;; features or tweaking existing ones. Design any new facilities in ;; a way that they could work in the absence of LSP or using some ;; different protocol, then make sure Eglot can link up LSP ;; information to it. @@ -81,7 +82,8 @@ ;; in place during Eglot's LSP-enriched tenure over a project. Even ;; so, some of those decisions will invariably aggravate a minority ;; of Emacs power users, but these users can use `eglot-stay-out-of' -;; and `eglot-managed-mode-hook' to quench their OCD. +;; and `eglot-managed-mode-hook' to adjust things to their +;; preferences. ;; ;; * On occasion, to enable new features, Eglot can have soft ;; dependencies on popular libraries that are not in Emacs core. @@ -109,6 +111,7 @@ (require 'filenotify) (require 'ert) (require 'array) +(require 'external-completion) ;; ElDoc is preloaded in Emacs, so `require'-ing won't guarantee we are ;; using the latest version from GNU Elpa when we load eglot.el. Use an @@ -127,7 +130,8 @@ (defvar markdown-fontify-code-blocks-natively) (defvar company-backends) (defvar company-tooltip-align-annotations) - +(defvar tramp-ssh-controlmaster-options) +(defvar tramp-use-ssh-controlmaster-options) ;;; User tweakable stuff @@ -165,7 +169,7 @@ chosen (interactively or automatically)." (cond ((cdr available) (cdr (assoc (completing-read - "[eglot] More than one server executable available:" + "[eglot] More than one server executable available: " (mapcar #'car available) nil t nil nil (car (car available))) available #'equal))) @@ -180,59 +184,72 @@ chosen (interactively or automatically)." when probe return (cons probe args) finally (funcall err))))))) -(defvar eglot-server-programs `((rust-mode . ,(eglot-alternatives '("rust-analyzer" "rls"))) - (cmake-mode . ("cmake-language-server")) +(defvar eglot-server-programs `(((rust-ts-mode rust-mode) . ,(eglot-alternatives '("rust-analyzer" "rls"))) + ((cmake-mode cmake-ts-mode) . ("cmake-language-server")) (vimrc-mode . ("vim-language-server" "--stdio")) - (python-mode + ((python-mode python-ts-mode) . ,(eglot-alternatives '("pylsp" "pyls" ("pyright-langserver" "--stdio") "jedi-language-server"))) - ((js-mode typescript-mode) + ((js-json-mode json-mode json-ts-mode) + . ,(eglot-alternatives '(("vscode-json-language-server" "--stdio") + ("vscode-json-languageserver" "--stdio") + ("json-languageserver" "--stdio")))) + ((js-mode js-ts-mode tsx-ts-mode typescript-ts-mode typescript-mode) . ("typescript-language-server" "--stdio")) - (sh-mode . ("bash-language-server" "start")) + ((bash-ts-mode sh-mode) . ("bash-language-server" "start")) ((php-mode phps-mode) - . ("php" "vendor/felixfbecker/\ -language-server/bin/php-language-server.php")) - ((c++-mode c-mode) . ,(eglot-alternatives - '("clangd" "ccls"))) + . ,(eglot-alternatives + '(("phpactor" "language-server") + ("php" "vendor/felixfbecker/language-server/bin/php-language-server.php")))) + ((c-mode c-ts-mode c++-mode c++-ts-mode) + . ,(eglot-alternatives + '("clangd" "ccls"))) (((caml-mode :language-id "ocaml") (tuareg-mode :language-id "ocaml") reason-mode) . ("ocamllsp")) - (ruby-mode + ((ruby-mode ruby-ts-mode) . ("solargraph" "socket" "--port" :autoport)) (haskell-mode . ("haskell-language-server-wrapper" "--lsp")) (elm-mode . ("elm-language-server")) (mint-mode . ("mint" "ls")) (kotlin-mode . ("kotlin-language-server")) - (go-mode . ("gopls")) + ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode) + . ("gopls")) ((R-mode ess-r-mode) . ("R" "--slave" "-e" "languageserver::run()")) - (java-mode . ("jdtls")) + ((java-mode java-ts-mode) . ("jdtls")) (dart-mode . ("dart" "language-server" "--client-id" "emacs.eglot-dart")) (elixir-mode . ("language_server.sh")) (ada-mode . ("ada_language_server")) - (scala-mode . ("metals-emacs")) + (scala-mode . ,(eglot-alternatives + '("metals" "metals-emacs"))) (racket-mode . ("racket" "-l" "racket-langserver")) ((tex-mode context-mode texinfo-mode bibtex-mode) - . ("digestif")) + . ,(eglot-alternatives '("digestif" "texlab"))) (erlang-mode . ("erlang_ls" "--transport" "stdio")) - (yaml-mode . ("yaml-language-server" "--stdio")) - (nix-mode . ("rnix-lsp")) + ((yaml-ts-mode yaml-mode) . ("yaml-language-server" "--stdio")) + (nix-mode . ,(eglot-alternatives '("nil" "rnix-lsp"))) (gdscript-mode . ("localhost" 6008)) ((fortran-mode f90-mode) . ("fortls")) (futhark-mode . ("futhark" "lsp")) - (lua-mode . ("lua-lsp")) + (lua-mode . ,(eglot-alternatives + '("lua-language-server" "lua-lsp"))) (zig-mode . ("zls")) - (css-mode . ,(eglot-alternatives '(("vscode-css-language-server" "--stdio") ("css-languageserver" "--stdio")))) + ((css-mode css-ts-mode) + . ,(eglot-alternatives '(("vscode-css-language-server" "--stdio") + ("css-languageserver" "--stdio")))) (html-mode . ,(eglot-alternatives '(("vscode-html-language-server" "--stdio") ("html-languageserver" "--stdio")))) - (json-mode . ,(eglot-alternatives '(("vscode-json-language-server" "--stdio") ("json-languageserver" "--stdio")))) - (dockerfile-mode . ("docker-langserver" "--stdio")) - ((clojure-mode clojurescript-mode clojurec-mode) + ((dockerfile-mode dockerfile-ts-mode) . ("docker-langserver" "--stdio")) + ((clojure-mode clojurescript-mode clojurec-mode) . ("clojure-lsp")) - (csharp-mode . ("omnisharp" "-lsp")) + ((csharp-mode csharp-ts-mode) + . ,(eglot-alternatives + '(("omnisharp" "-lsp") + ("csharp-ls")))) (purescript-mode . ("purescript-language-server" "--stdio")) - (perl-mode . ("perl" "-MPerl::LanguageServer" "-e" "Perl::LanguageServer::run")) + ((perl-mode cperl-mode) . ("perl" "-MPerl::LanguageServer" "-e" "Perl::LanguageServer::run")) (markdown-mode . ("marksman" "server"))) "How the command `eglot' guesses the server to start. An association list of (MAJOR-MODE . CONTACT) pairs. MAJOR-MODE @@ -296,7 +313,10 @@ CONTACT can be: the call is interactive, the function can ask the user for hints on finding the required programs, etc. Otherwise, it should not ask the user for any input, and return nil or signal - an error if it can't produce a valid CONTACT.") + an error if it can't produce a valid CONTACT. The helper + function `eglot-alternatives' (which see) can be used to + produce a function that offers more than one server for a given + MAJOR-MODE.") (defface eglot-highlight-symbol-face '((t (:inherit bold))) @@ -321,13 +341,15 @@ never reconnect automatically after unexpected server shutdowns, crashes or network failures. A positive integer number says to only autoreconnect if the previous successful connection attempt lasted more than that many seconds." - :type '(choice (boolean :tag "Whether to inhibit autoreconnection") + :type '(choice (const :tag "Reconnect automatically" t) + (const :tag "Never reconnect" nil) (integer :tag "Number of seconds"))) (defcustom eglot-connect-timeout 30 "Number of seconds before timing out LSP connection attempts. If nil, never time out." - :type 'number) + :type '(choice (number :tag "Number of seconds") + (const :tag "Never time out" nil))) (defcustom eglot-sync-connect 3 "Control blocking of LSP connection attempts. @@ -335,8 +357,9 @@ If t, block for `eglot-connect-timeout' seconds. A positive integer number means block for that many seconds, and then wait for the connection in the background. nil has the same meaning as 0, i.e. don't block at all." - :type '(choice (boolean :tag "Whether to inhibit autoreconnection") - (integer :tag "Number of seconds"))) + :type '(choice (const :tag "Block for `eglot-connect-timeout' seconds" t) + (const :tag "Never block" nil) + (integer :tag "Number of seconds to block"))) (defcustom eglot-autoshutdown nil "If non-nil, shut down server after killing last managed buffer." @@ -361,7 +384,7 @@ done by `eglot-reconnect'." (defcustom eglot-confirm-server-initiated-edits 'confirm "Non-nil if server-initiated edits should be confirmed with user." :type '(choice (const :tag "Don't show confirmation prompt" nil) - (symbol :tag "Show confirmation prompt" 'confirm))) + (const :tag "Show confirmation prompt" confirm))) (defcustom eglot-extend-to-xref nil "If non-nil, activate Eglot in cross-referenced non-project files." @@ -371,6 +394,11 @@ done by `eglot-reconnect'." "String displayed in mode line when Eglot is active." :type 'string) +(defcustom eglot-report-progress t + "If non-nil, show progress of long running LSP server work" + :type 'boolean + :version "29.1") + (defvar eglot-withhold-process-id nil "If non-nil, Eglot will not send the Emacs process id to the language server. This can be useful when using docker to run a language server.") @@ -406,8 +434,8 @@ This can be useful when using docker to run a language server.") `((1 . eglot-diagnostic-tag-unnecessary-face) (2 . eglot-diagnostic-tag-deprecated-face))) -(defconst eglot--{} (make-hash-table :size 1) "The empty JSON object.") (defvaralias 'eglot-{} 'eglot--{}) +(defconst eglot--{} (make-hash-table :size 1) "The empty JSON object.") (defun eglot--executable-find (command &optional remote) "Like Emacs 27's `executable-find', ignore REMOTE on Emacs 26." @@ -455,8 +483,12 @@ This can be useful when using docker to run a language server.") (TextDocumentEdit (:textDocument :edits) ()) (TextEdit (:range :newText)) (VersionedTextDocumentIdentifier (:uri :version) ()) + (WorkDoneProgress (:kind) (:title :message :percentage :cancellable)) (WorkspaceEdit () (:changes :documentChanges)) - (WorkspaceSymbol (:name :kind) (:containerName :location :data))) + (WorkspaceSymbol (:name :kind) (:containerName :location :data)) + (InlayHint (:position :label) (:kind :textEdits :tooltip :paddingLeft + :paddingRight :data)) + (InlayHintLabelPart (:value) (:tooltip :location :command))) "Alist (INTERFACE-NAME . INTERFACE) of known external LSP interfaces. INTERFACE-NAME is a symbol designated by the spec as @@ -477,7 +509,7 @@ Here's what an element of this alist might look like: ;; disallow-non-standard-keys ;; enforce-required-keys ;; enforce-optional-keys - ) + no-unknown-interfaces) "How strictly to check LSP interfaces at compile- and run-time. Value is a list of symbols (if the list is empty, no checks are @@ -498,7 +530,10 @@ happens at run-time. At compile-time, a warning is raised if a destructuring spec doesn't use all optional fields. If the symbol `disallow-unknown-methods' is present, Eglot warns -on unknown notifications and errors on unknown requests.")) +on unknown notifications and errors on unknown requests. + +If the symbol `no-unknown-interfaces' is present, Eglot warns at +compile time if an undeclared LSP interface is used.")) (cl-defun eglot--check-object (interface-name object @@ -525,7 +560,7 @@ on unknown notifications and errors on unknown requests.")) for type = (or (cdr (assoc k types)) t) ;; FIXME: enforce nil type? unless (cl-typep v type) do (eglot--error "A `%s' must have a %s as %s, but has %s" - interface-name ))) + interface-name))) t)) (eval-and-compile @@ -572,13 +607,13 @@ on unknown notifications and errors on unknown requests.")) (when missing-out (byte-compile-warn "Destructuring for %s is missing out on %s" interface-name missing-out)))) - (t + ((memq 'no-unknown-interfaces eglot-strict-mode) (byte-compile-warn "Unknown LSP interface %s" interface-name)))))) (cl-defmacro eglot--dbind (vars object &body body) "Destructure OBJECT, binding VARS in BODY. VARS is ([(INTERFACE)] SYMS...) -Honour `eglot-strict-mode'." +Honor `eglot-strict-mode'." (declare (indent 2) (debug (sexp sexp &rest form))) (let ((interface-name (if (consp (car vars)) (car (pop vars)))) @@ -605,15 +640,15 @@ Honour `eglot-strict-mode'." (cl-defmacro eglot--lambda (cl-lambda-list &body body) "Function of args CL-LAMBDA-LIST for processing INTERFACE objects. -Honour `eglot-strict-mode'." +Honor `eglot-strict-mode'." (declare (indent 1) (debug (sexp &rest form))) (let ((e (cl-gensym "jsonrpc-lambda-elem"))) - `(lambda (,e) (eglot--dbind ,cl-lambda-list ,e ,@body)))) + `(lambda (,e) (cl-block nil (eglot--dbind ,cl-lambda-list ,e ,@body))))) (cl-defmacro eglot--dcase (obj &rest clauses) "Like `pcase', but for the LSP object OBJ. CLAUSES is a list (DESTRUCTURE FORMS...) where DESTRUCTURE is -treated as in `eglot-dbind'." +treated as in `eglot--dbind'." (declare (indent 1) (debug (sexp &rest (sexp &rest form)))) (let ((obj-once (make-symbol "obj-once"))) `(let ((,obj-once ,obj)) @@ -728,6 +763,10 @@ treated as in `eglot-dbind'." t :json-false) :deprecatedSupport t + :resolveSupport (:properties + ["documentation" + "details" + "additionalTextEdits"]) :tagSupport (:valueSet [1])) :contextSupport t) :hover (list :dynamicRegistration :json-false @@ -769,6 +808,7 @@ treated as in `eglot-dbind'." :formatting `(:dynamicRegistration :json-false) :rangeFormatting `(:dynamicRegistration :json-false) :rename `(:dynamicRegistration :json-false) + :inlayHint `(:dynamicRegistration :json-false) :publishDiagnostics (list :relatedInformation :json-false ;; TODO: We can support :codeDescription after ;; adding an appropriate UI to @@ -778,6 +818,7 @@ treated as in `eglot-dbind'." `(:valueSet [,@(mapcar #'car eglot--tag-faces)]))) + :general (list :positionEncodings ["utf-32" "utf-8" "utf-16"]) :experimental eglot--{}))) (cl-defgeneric eglot-workspace-folders (server) @@ -812,9 +853,9 @@ treated as in `eglot-dbind'." (project :documentation "Project associated with server." :accessor eglot--project) - (spinner - :documentation "List (ID DOING-WHAT DONE-P) representing server progress." - :initform `(nil nil t) :accessor eglot--spinner) + (progress-reporters + :initform (make-hash-table :test #'equal) :accessor eglot--progress-reporters + :documentation "Maps LSP progress tokens to progress reporters.") (inhibit-autoreconnect :initform t :documentation "Generalized boolean inhibiting auto-reconnection if true." @@ -872,7 +913,10 @@ SERVER." PRESERVE-BUFFERS as in `eglot-shutdown', which see." (interactive (list current-prefix-arg)) (cl-loop for ss being the hash-values of eglot--servers-by-project - do (cl-loop for s in ss do (eglot-shutdown s nil preserve-buffers)))) + do (with-demoted-errors "[eglot] shutdown all: %s" + (cl-loop for s in ss do (eglot-shutdown s nil nil preserve-buffers))))) + +(defvar eglot--servers-by-xrefed-file (make-hash-table :test 'equal)) (defun eglot--on-shutdown (server) "Called by jsonrpc.el when SERVER is already dead." @@ -892,6 +936,9 @@ PRESERVE-BUFFERS as in `eglot-shutdown', which see." (setf (gethash (eglot--project server) eglot--servers-by-project) (delq server (gethash (eglot--project server) eglot--servers-by-project))) + (maphash (lambda (f s) + (when (eq s server) (remhash f eglot--servers-by-xrefed-file))) + eglot--servers-by-xrefed-file) (cond ((eglot--shutdown-requested server) t) ((not (eglot--inhibit-autoreconnect server)) @@ -908,14 +955,14 @@ PRESERVE-BUFFERS as in `eglot-shutdown', which see." (push sym retval)))) retval)) -(defvar eglot--command-history nil +(defvar eglot-command-history nil "History of CONTACT arguments to `eglot'.") (defun eglot--lookup-mode (mode) "Lookup `eglot-server-programs' for MODE. Return (MANAGED-MODES LANGUAGE-ID CONTACT-PROXY). -MANAGED-MODES is a list with MODE as its first elements. +MANAGED-MODES is a list with MODE as its first element. Subsequent elements are other major modes also potentially managed by the server that is to manage MODE. @@ -952,6 +999,7 @@ Return (MANAGED-MODE PROJECT CLASS CONTACT LANG-ID). If INTERACTIVE is non-nil, maybe prompt user, else error as soon as something can't be guessed." (let* ((guessed-mode (if buffer-file-name major-mode)) + (guessed-mode-name (and guessed-mode (symbol-name guessed-mode))) (main-mode (cond ((and interactive @@ -961,7 +1009,7 @@ be guessed." (completing-read "[eglot] Start a server to manage buffers of what major mode? " (mapcar #'symbol-name (eglot--all-major-modes)) nil t - (symbol-name guessed-mode) nil (symbol-name guessed-mode) nil))) + guessed-mode-name nil guessed-mode-name nil))) ((not guessed-mode) (eglot--error "Can't guess mode to manage for `%s'" (current-buffer))) (t guessed-mode))) @@ -994,7 +1042,7 @@ be guessed." (and base-prompt (cond (current-prefix-arg base-prompt) ((null guess) - (format "[eglot] Sorry, couldn't guess for `%s'!\n%s" + (format "[eglot] Couldn't guess LSP server for `%s'\n%s" main-mode base-prompt)) ((and program (not (file-name-absolute-p program)) @@ -1023,9 +1071,6 @@ be guessed." (put 'eglot-lsp-context 'variable-documentation "Dynamically non-nil when searching for projects in LSP context.") -(defvar eglot--servers-by-xrefed-file - (make-hash-table :test 'equal :weakness 'value)) - (defun eglot--current-project () "Return a project object for Eglot's LSP purposes. This relies on `project-current' and thus on @@ -1034,31 +1079,36 @@ variable (which see) can query the value `eglot-lsp-context' to decide whether a given directory is a project containing a suitable root directory for a given LSP server's purposes." (let ((eglot-lsp-context t)) - (or (project-current) `(transient . ,default-directory)))) + (or (project-current) + `(transient . ,(expand-file-name default-directory))))) ;;;###autoload (defun eglot (managed-major-mode project class contact language-id - &optional interactive) - "Manage a project with a Language Server Protocol (LSP) server. + &optional _interactive) + "Start LSP server in support of PROJECT's buffers under MANAGED-MAJOR-MODE. -The LSP server of CLASS is started (or contacted) via CONTACT. -If this operation is successful, current *and future* file -buffers of MANAGED-MAJOR-MODE inside PROJECT become \"managed\" -by the LSP server, meaning information about their contents is -exchanged periodically to provide enhanced code-analysis via -`xref-find-definitions', `flymake-mode', `eldoc-mode', -`completion-at-point', among others. +This starts a Language Server Protocol (LSP) server suitable for the +buffers of PROJECT whose `major-mode' is MANAGED-MAJOR-MODE. +CLASS is the class of the LSP server to start and CONTACT specifies +how to connect to the server. Interactively, the command attempts to guess MANAGED-MAJOR-MODE -from current buffer, CLASS and CONTACT from -`eglot-server-programs' and PROJECT from +from the current buffer's `major-mode', CLASS and CONTACT from +`eglot-server-programs' looked up by the major mode, and PROJECT from `project-find-functions'. The search for active projects in this context binds `eglot-lsp-context' (which see). -If it can't guess, the user is prompted. With a single -\\[universal-argument] prefix arg, it always prompt for COMMAND. -With two \\[universal-argument] prefix args, also prompts for -MANAGED-MAJOR-MODE. +If it can't guess, it prompts the user for the mode and the server. +With a single \\[universal-argument] prefix arg, it always prompts for COMMAND. +With two \\[universal-argument], it also always prompts for MANAGED-MAJOR-MODE. + +The LSP server of CLASS is started (or contacted) via CONTACT. +If this operation is successful, current *and future* file +buffers of MANAGED-MAJOR-MODE inside PROJECT become \"managed\" +by the LSP server, meaning the information about their contents is +exchanged periodically with the server to provide enhanced +code-analysis via `xref-find-definitions', `flymake-mode', +`eldoc-mode', and `completion-at-point', among others. PROJECT is a project object as returned by `project-current'. @@ -1071,16 +1121,17 @@ described in `eglot-server-programs', which see. LANGUAGE-ID is the language ID string to send to the server for MANAGED-MAJOR-MODE, which matters to a minority of servers. -INTERACTIVE is t if called interactively." - (interactive (append (eglot--guess-contact t) '(t))) - (let* ((current-server (eglot-current-server)) - (live-p (and current-server (jsonrpc-running-p current-server)))) - (if (and live-p - interactive - (y-or-n-p "[eglot] Live process found, reconnect instead? ")) - (eglot-reconnect current-server interactive) - (when live-p (ignore-errors (eglot-shutdown current-server))) - (eglot--connect managed-major-mode project class contact language-id)))) +INTERACTIVE is ignored and provided for backward compatibility." + (interactive + (let ((current-server (eglot-current-server))) + (unless (or (null current-server) + (y-or-n-p "\ +[eglot] Shut down current connection before attempting new one?")) + (user-error "[eglot] Connection attempt aborted by user.")) + (prog1 (append (eglot--guess-contact t) '(t)) + (when current-server (ignore-errors (eglot-shutdown current-server)))))) + (eglot--connect (eglot--ensure-list managed-major-mode) + project class contact language-id)) (defun eglot-reconnect (server &optional interactive) "Reconnect to SERVER. @@ -1103,13 +1154,13 @@ INTERACTIVE is t if called interactively." (let ((buffer (current-buffer))) (cl-labels ((maybe-connect - () - (remove-hook 'post-command-hook #'maybe-connect nil) - (eglot--when-live-buffer buffer - (unless eglot--managed-mode - (apply #'eglot--connect (eglot--guess-contact)))))) + () + (eglot--when-live-buffer buffer + (remove-hook 'post-command-hook #'maybe-connect t) + (unless eglot--managed-mode + (apply #'eglot--connect (eglot--guess-contact)))))) (when buffer-file-name - (add-hook 'post-command-hook #'maybe-connect 'append nil))))) + (add-hook 'post-command-hook #'maybe-connect 'append t))))) (defun eglot-events-buffer (server) "Display events buffer for SERVER. @@ -1156,10 +1207,10 @@ Each function is passed the server as an argument") ;; ;; Not only does this seem like there should be a better way, ;; but it almost certainly doesn’t work on non-unix systems. - (list "sh" "-c" + (list shell-file-name "-c" (string-join (cons "stty raw > /dev/null;" (mapcar #'shell-quote-argument contact)) - " ")) + " ")) contact)) (defvar-local eglot--cached-server nil @@ -1169,7 +1220,7 @@ Each function is passed the server as an argument") "Connect to MANAGED-MODES, LANGUAGE-ID, PROJECT, CLASS and CONTACT. This docstring appeases checkdoc, that's all." (let* ((default-directory (project-root project)) - (nickname (file-name-base (directory-file-name default-directory))) + (nickname (project-name project)) (readable-name (format "EGLOT (%s/%s)" nickname managed-modes)) autostart-inferior-process server-info @@ -1190,7 +1241,8 @@ This docstring appeases checkdoc, that's all." (pcase-let ((`(,connection . ,inferior) (eglot--inferior-bootstrap readable-name - contact))) + contact + '(:noquery t)))) (setq autostart-inferior-process inferior) connection)))) ((stringp (car contact)) @@ -1199,7 +1251,15 @@ This docstring appeases checkdoc, that's all." (contact (cl-subseq contact 0 probe))) `(:process ,(lambda () - (let ((default-directory default-directory)) + (let ((default-directory default-directory) + ;; bug#61350: Tramp turns on a feature + ;; by default that can't (yet) handle + ;; very much data so we turn it off + ;; unconditionally -- just for our + ;; process. + (tramp-use-ssh-controlmaster-options t) + (tramp-ssh-controlmaster-options + "-o ControlMaster=no -o ControlPath=none")) (make-process :name readable-name :command (setq server-info (eglot--cmd contact)) @@ -1212,7 +1272,7 @@ This docstring appeases checkdoc, that's all." ,@more-initargs))))) (spread (lambda (fn) (lambda (server method params) (let ((eglot--cached-server server)) - (apply fn server method (append params nil)))))) + (apply fn server method (append params nil)))))) (server (apply #'make-instance class @@ -1222,7 +1282,7 @@ This docstring appeases checkdoc, that's all." :request-dispatcher (funcall spread #'eglot-handle-request) :on-shutdown #'eglot--on-shutdown initargs)) - (cancelled nil) + (canceled nil) (tag (make-symbol "connected-catch-tag"))) (when server-info (jsonrpc--debug server "Running language server: %s" @@ -1234,7 +1294,7 @@ This docstring appeases checkdoc, that's all." (setf (eglot--language-id server) language-id) (setf (eglot--inferior-process server) autostart-inferior-process) (run-hook-with-args 'eglot-server-initialized-hook server) - ;; Now start the handshake. To honour `eglot-sync-connect' + ;; Now start the handshake. To honor `eglot-sync-connect' ;; maybe-sync-maybe-async semantics we use `jsonrpc-async-request' ;; and mimic most of `jsonrpc-request'. (unwind-protect @@ -1261,7 +1321,7 @@ This docstring appeases checkdoc, that's all." :workspaceFolders (eglot-workspace-folders server)) :success-fn (eglot--lambda ((InitializeResult) capabilities serverInfo) - (unless cancelled + (unless canceled (push server (gethash project eglot--servers-by-project)) (setf (eglot--capabilities server) capabilities) @@ -1285,10 +1345,7 @@ This docstring appeases checkdoc, that's all." (lambda () (setf (eglot--inhibit-autoreconnect server) (null eglot-autoreconnect))))))) - (let ((default-directory (project-root project)) - (major-mode (car managed-modes))) - (hack-dir-local-variables-non-file-buffer) - (run-hook-with-args 'eglot-connect-hook server)) + (run-hook-with-args 'eglot-connect-hook server) (eglot--message "Connected! Server `%s' now managing `%s' buffers \ in project `%s'." @@ -1299,13 +1356,13 @@ in project `%s'." (when tag (throw tag t)))) :timeout eglot-connect-timeout :error-fn (eglot--lambda ((ResponseError) code message) - (unless cancelled + (unless canceled (jsonrpc-shutdown server) (let ((msg (format "%s: %s" code message))) (if tag (throw tag `(error . ,msg)) (eglot--error msg))))) :timeout-fn (lambda () - (unless cancelled + (unless canceled (jsonrpc-shutdown server) (let ((msg (format "Timed out after %s seconds" eglot-connect-timeout))) @@ -1322,7 +1379,7 @@ in project `%s'." (jsonrpc-name server)) nil) (_ server))) - (quit (jsonrpc-shutdown server) (setq cancelled 'quit))) + (quit (jsonrpc-shutdown server) (setq canceled 'quit))) (setq tag nil)))) (defun eglot--inferior-bootstrap (name contact &optional connect-args) @@ -1391,70 +1448,111 @@ CONNECT-ARGS are passed as additional arguments to (let ((warning-minimum-level :error)) (display-warning 'eglot (apply #'format format args) :warning))) -(defun eglot-current-column () (- (point) (line-beginning-position))) - -(defvar eglot-current-column-function #'eglot-lsp-abiding-column - "Function to calculate the current column. +(defalias 'eglot--bol + (if (fboundp 'pos-bol) #'pos-bol + (lambda (&optional n) (let ((inhibit-field-text-motion t)) + (line-beginning-position n)))) + "Return position of first character in current line.") -This is the inverse operation of -`eglot-move-to-column-function' (which see). It is a function of -no arguments returning a column number. For buffers managed by -fully LSP-compliant servers, this should be set to -`eglot-lsp-abiding-column' (the default), and -`eglot-current-column' for all others.") - -(defun eglot-lsp-abiding-column (&optional lbp) - "Calculate current COLUMN as defined by the LSP spec. -LBP defaults to `line-beginning-position'." - (/ (- (length (encode-coding-region (or lbp (line-beginning-position)) + +;;; Encoding fever +;;; +(define-obsolete-function-alias + 'eglot-lsp-abiding-column 'eglot-utf-16-linepos "29.1") +(define-obsolete-function-alias + 'eglot-current-column 'eglot-utf-32-linepos "29.1") +(define-obsolete-variable-alias + 'eglot-current-column-function 'eglot-current-linepos-function "29.1") + +(defvar eglot-current-linepos-function #'eglot-utf-16-linepos + "Function calculating position relative to line beginning. + +It is a function of no arguments considering the text from line +beginning up to current point. The return value is the number of +UTF code units needed to encode that text from the LSP server's +perspective. This may be a number of octets, 16-bit words or +Unicode code points, depending on whether the LSP server's +`positionEncoding' capability is UTF-8, UTF-16 or UTF-32, +respectively. Position of point should remain unaltered if that +return value is fed through the corresponding inverse function +`eglot-move-to-linepos-function' (which see).") + +(defun eglot-utf-8-linepos () + "Calculate number of UTF-8 bytes from line beginning." + (length (encode-coding-region (eglot--bol) (point) 'utf-8-unix t))) + +(defun eglot-utf-16-linepos (&optional lbp) + "Calculate number of UTF-16 code units from position given by LBP. +LBP defaults to `eglot--bol'." + (/ (- (length (encode-coding-region (or lbp (eglot--bol)) ;; Fix github#860 (min (point) (point-max)) 'utf-16 t)) 2) 2)) +(defun eglot-utf-32-linepos () + "Calculate number of Unicode codepoints from line beginning." + (- (point) (eglot--bol))) + (defun eglot--pos-to-lsp-position (&optional pos) "Convert point POS to LSP position." (eglot--widening - (list :line (1- (line-number-at-pos pos t)) ; F!@&#$CKING OFF-BY-ONE + ;; LSP line is zero-origin; emacs is one-origin. + (list :line (1- (line-number-at-pos pos t)) :character (progn (when pos (goto-char pos)) - (funcall eglot-current-column-function))))) - -(defvar eglot-move-to-column-function #'eglot-move-to-lsp-abiding-column - "Function to move to a column reported by the LSP server. - -According to the standard, LSP column/character offsets are based -on a count of UTF-16 code units, not actual visual columns. So -when LSP says position 3 of a line containing just \"aXbc\", -where X is a multi-byte character, it actually means `b', not -`c'. However, many servers don't follow the spec this closely. - -For buffers managed by fully LSP-compliant servers, this should -be set to `eglot-move-to-lsp-abiding-column' (the default), and -`eglot-move-to-column' for all others.") - -(defun eglot-move-to-column (column) - "Move to COLUMN without closely following the LSP spec." + (funcall eglot-current-linepos-function))))) + +(define-obsolete-function-alias + 'eglot-move-to-current-column 'eglot-move-to-utf-32-linepos "29.1") +(define-obsolete-function-alias + 'eglot-move-to-lsp-abiding-column 'eglot-move-to-utf-16-linepos "29.1") +(define-obsolete-variable-alias +'eglot-move-to-column-function 'eglot-move-to-linepos-function "29.1") + +(defvar eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos + "Function to move to a position within a line reported by the LSP server. + +Per the LSP spec, character offsets in LSP Position objects count +UTF-16 code units, not actual code points. So when LSP says +position 3 of a line containing just \"aXbc\", where X is a funny +looking character in the UTF-16 \"supplementary plane\", it +actually means `b', not `c'. The default value +`eglot-move-to-utf-16-linepos' accounts for this. + +This variable can also be set to `eglot-move-to-utf-8-linepos' or +`eglot-move-to-utf-32-linepos' for servers not closely following +the spec. Also, since LSP 3.17 server and client may agree on an +encoding and Eglot will set this variable automatically.") + +(defun eglot-move-to-utf-8-linepos (n) + "Move to line's Nth byte as computed by LSP's UTF-8 criterion." + (let* ((bol (eglot--bol)) + (goal-byte (+ (position-bytes bol) n)) + (eol (line-end-position))) + (goto-char bol) + (while (and (< (position-bytes (point)) goal-byte) (< (point) eol)) + ;; raw bytes take 2 bytes in the buffer + (when (>= (char-after) #x3fff80) (setq goal-byte (1+ goal-byte))) + (forward-char 1)))) + +(defun eglot-move-to-utf-16-linepos (n) + "Move to line's Nth code unit as computed by LSP's UTF-16 criterion." + (let* ((bol (eglot--bol)) + (goal-char (+ bol n)) + (eol (line-end-position))) + (goto-char bol) + (while (and (< (point) goal-char) (< (point) eol)) + ;; code points in the "supplementary place" use two code units + (when (<= #x010000 (char-after) #x10ffff) (setq goal-char (1- goal-char))) + (forward-char 1)))) + +(defun eglot-move-to-utf-32-linepos (n) + "Move to line's Nth codepoint as computed by LSP's UTF-32 criterion." ;; We cannot use `move-to-column' here, because it moves to *visual* - ;; columns, which can be different from LSP columns in case of + ;; columns, which can be different from LSP characters in case of ;; `whitespace-mode', `prettify-symbols-mode', etc. (github#296, ;; github#297) - (goto-char (min (+ (line-beginning-position) column) - (line-end-position)))) - -(defun eglot-move-to-lsp-abiding-column (column) - "Move to COLUMN abiding by the LSP spec." - (save-restriction - (cl-loop - with lbp = (line-beginning-position) - initially - (narrow-to-region lbp (line-end-position)) - (move-to-column column) - for diff = (- column - (eglot-lsp-abiding-column lbp)) - until (zerop diff) - do (condition-case eob-err - (forward-char (/ (if (> diff 0) (1+ diff) (1- diff)) 2)) - (end-of-buffer (cl-return eob-err)))))) + (goto-char (min (+ (eglot--bol) n) (line-end-position)))) (defun eglot--lsp-position-to-point (pos-plist &optional marker) "Convert LSP position POS-PLIST to Emacs point. @@ -1466,16 +1564,17 @@ If optional MARKER, return a marker instead" (forward-line (min most-positive-fixnum (plist-get pos-plist :line))) (unless (eobp) ;; if line was excessive leave point at eob - (let ((tab-width 1) - (col (plist-get pos-plist :character))) + (let ((col (plist-get pos-plist :character))) (unless (wholenump col) (eglot--warn "Caution: LSP server sent invalid character position %s. Using 0 instead." col) (setq col 0)) - (funcall eglot-move-to-column-function col))) + (funcall eglot-move-to-linepos-function col))) (if marker (copy-marker (point-marker)) (point))))) + +;;; More helpers (defconst eglot--uri-path-allowed-chars (let ((vec (copy-sequence url-path-allowed-chars))) (aset vec ?: nil) ;; see github#639 @@ -1485,29 +1584,45 @@ If optional MARKER, return a marker instead" (defun eglot--path-to-uri (path) "URIfy PATH." (let ((truepath (file-truename path))) - (concat "file://" - ;; Add a leading "/" for local MS Windows-style paths. - (if (and (eq system-type 'windows-nt) - (not (file-remote-p truepath))) - "/") - (url-hexify-string - ;; Again watch out for trampy paths. - (directory-file-name (file-local-name truepath)) - eglot--uri-path-allowed-chars)))) + (if (and (url-type (url-generic-parse-url path)) + ;; It might be MS Windows path which includes a drive + ;; letter that looks like a URL scheme (bug#59338) + (not (and (eq system-type 'windows-nt) + (file-name-absolute-p truepath)))) + ;; Path is already a URI, so forward it to the LSP server + ;; untouched. The server should be able to handle it, since + ;; it provided this URI to clients in the first place. + path + (concat "file://" + ;; Add a leading "/" for local MS Windows-style paths. + (if (and (eq system-type 'windows-nt) + (not (file-remote-p truepath))) + "/") + (url-hexify-string + ;; Again watch out for trampy paths. + (directory-file-name (file-local-name truepath)) + eglot--uri-path-allowed-chars))))) (defun eglot--uri-to-path (uri) "Convert URI to file path, helped by `eglot--current-server'." (when (keywordp uri) (setq uri (substring (symbol-name uri) 1))) (let* ((server (eglot-current-server)) (remote-prefix (and server (eglot--trampish-p server))) - (retval (url-unhex-string (url-filename (url-generic-parse-url uri)))) - ;; Remove the leading "/" for local MS Windows-style paths. - (normalized (if (and (not remote-prefix) - (eq system-type 'windows-nt) - (cl-plusp (length retval))) - (substring retval 1) - retval))) - (concat remote-prefix normalized))) + (url (url-generic-parse-url uri))) + ;; Only parse file:// URIs, leave other URI untouched as + ;; `file-name-handler-alist' should know how to handle them + ;; (bug#58790). + (if (string= "file" (url-type url)) + (let* ((retval (url-unhex-string (url-filename url))) + ;; Remove the leading "/" for local MS Windows-style paths. + (normalized (if (and (not remote-prefix) + (eq system-type 'windows-nt) + (cl-plusp (length retval))) + (substring retval 1) + retval))) + (concat remote-prefix normalized)) + + uri))) (defun eglot--snippet-expansion-fn () "Compute a function to expand snippets. @@ -1529,7 +1644,7 @@ Doubles as an indicator of snippet support." (setq-local markdown-fontify-code-blocks-natively t) (insert string) (let ((inhibit-message t) - (message-log-max nil)) + (message-log-max nil)) (ignore-errors (delay-mode-hooks (funcall mode)))) (font-lock-ensure) (string-trim (buffer-string))))) @@ -1564,7 +1679,8 @@ under cursor." (const :tag "Highlight links in document" :documentLinkProvider) (const :tag "Decorate color references" :colorProvider) (const :tag "Fold regions of buffer" :foldingRangeProvider) - (const :tag "Execute custom commands" :executeCommandProvider))) + (const :tag "Execute custom commands" :executeCommandProvider) + (const :tag "Inlay hints" :inlayHintProvider))) (defun eglot--server-capable (&rest feats) "Determine if current server is capable of FEATS." @@ -1580,6 +1696,14 @@ under cursor." if (not (listp (cadr probe))) do (cl-return (if more nil (cadr probe))) finally (cl-return (or (cadr probe) t))))) +(defun eglot--server-capable-or-lose (&rest feats) + "Like `eglot--server-capable', but maybe error out." + (let ((retval (apply #'eglot--server-capable feats))) + (unless retval + (eglot--error "Unsupported or ignored LSP capability `%s'" + (mapconcat #'symbol-name feats " "))) + retval)) + (defun eglot--range-region (range &optional markers) "Return region (BEG . END) that represents LSP RANGE. If optional MARKERS, make markers." @@ -1622,6 +1746,8 @@ and just return it. PROMPT shouldn't end with a question mark." (cl-loop for (k _v) on plist by #'cddr collect k)) (defun eglot--ensure-list (x) (if (listp x) x (list x))) +(when (fboundp 'ensure-list) ; Emacs 28 or later + (define-obsolete-function-alias 'eglot--ensure-list #'ensure-list "29.1")) ;;; Minor modes @@ -1646,7 +1772,7 @@ against a variable's name. Examples include the string Before Eglot starts \"managing\" a particular buffer, it opinionatedly sets some peripheral Emacs facilities, such as Flymake, Xref and Company. These overriding settings help ensure -consistent Eglot behaviour and only stay in place until +consistent Eglot behavior and only stay in place until \"managing\" stops (usually via `eglot-shutdown'), whereupon the previous settings are restored. @@ -1658,7 +1784,7 @@ For example, to keep your Company customization, add the symbol `company' to this variable.") (defun eglot--stay-out-of-p (symbol) - "Tell if Eglot should stay of of SYMBOL." + "Tell if Eglot should stay out of SYMBOL." (cl-find (symbol-name symbol) eglot-stay-out-of :test (lambda (s thing) (let ((re (if (symbolp thing) (symbol-name thing) thing))) @@ -1682,6 +1808,14 @@ Use `eglot-managed-p' to determine if current buffer is managed.") :init-value nil :lighter nil :keymap eglot-mode-map (cond (eglot--managed-mode + (pcase (plist-get (eglot--capabilities (eglot-current-server)) + :positionEncoding) + ("utf-32" + (eglot--setq-saving eglot-current-linepos-function #'eglot-utf-32-linepos) + (eglot--setq-saving eglot-move-to-linepos-function #'eglot-move-to-utf-32-linepos)) + ("utf-8" + (eglot--setq-saving eglot-current-linepos-function #'eglot-utf-8-linepos) + (eglot--setq-saving eglot-move-to-linepos-function #'eglot-move-to-utf-8-linepos))) (add-hook 'after-change-functions 'eglot--after-change nil t) (add-hook 'before-change-functions 'eglot--before-change nil t) (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t) @@ -1697,20 +1831,22 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (add-hook 'change-major-mode-hook #'eglot--managed-mode-off nil t) (add-hook 'post-self-insert-hook 'eglot--post-self-insert-hook nil t) (add-hook 'pre-command-hook 'eglot--pre-command-hook nil t) - (eglot--setq-saving eldoc-documentation-functions - '(eglot-signature-eldoc-function - eglot-hover-eldoc-function)) - (eglot--setq-saving eldoc-documentation-strategy - #'eldoc-documentation-enthusiast) (eglot--setq-saving xref-prompt-for-identifier nil) (eglot--setq-saving flymake-diagnostic-functions '(eglot-flymake-backend)) (eglot--setq-saving company-backends '(company-capf)) (eglot--setq-saving company-tooltip-align-annotations t) + (eglot--setq-saving eldoc-documentation-strategy + #'eldoc-documentation-compose) (unless (eglot--stay-out-of-p 'imenu) (add-function :before-until (local 'imenu-create-index-function) #'eglot-imenu)) (unless (eglot--stay-out-of-p 'flymake) (flymake-mode 1)) - (unless (eglot--stay-out-of-p 'eldoc) (eldoc-mode 1)) + (unless (eglot--stay-out-of-p 'eldoc) + (add-hook 'eldoc-documentation-functions #'eglot-hover-eldoc-function + nil t) + (add-hook 'eldoc-documentation-functions #'eglot-signature-eldoc-function + nil t) + (eldoc-mode 1)) (cl-pushnew (current-buffer) (eglot--managed-buffers (eglot-current-server)))) (t (remove-hook 'after-change-functions 'eglot--after-change t) @@ -1726,6 +1862,8 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (remove-hook 'change-major-mode-hook #'eglot--managed-mode-off t) (remove-hook 'post-self-insert-hook 'eglot--post-self-insert-hook t) (remove-hook 'pre-command-hook 'eglot--pre-command-hook t) + (remove-hook 'eldoc-documentation-functions #'eglot-hover-eldoc-function t) + (remove-hook 'eldoc-documentation-functions #'eglot-signature-eldoc-function t) (cl-loop for (var . saved-binding) in eglot--saved-bindings do (set (make-local-variable var) saved-binding)) (remove-function (local 'imenu-create-index-function) #'eglot-imenu) @@ -1739,12 +1877,11 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (delq (current-buffer) (eglot--managed-buffers server))) (when (and eglot-autoshutdown (null (eglot--managed-buffers server))) - (eglot-shutdown server)))))) - ;; Note: the public hook runs before the internal eglot--managed-mode-hook. - (run-hooks 'eglot-managed-mode-hook)) + (eglot-shutdown server))))))) (defun eglot--managed-mode-off () "Turn off `eglot--managed-mode' unconditionally." + (remove-overlays nil nil 'eglot--overlay t) (eglot--managed-mode -1)) (defun eglot-current-server () @@ -1783,9 +1920,12 @@ If it is activated, also signal textDocument/didOpen." (when (and buffer-file-name (eglot-current-server)) (setq eglot--diagnostics nil) (eglot--managed-mode) - (eglot--signal-textDocument/didOpen)))) + (eglot--signal-textDocument/didOpen) + ;; Run user hook after 'textDocument/didOpen' so server knows + ;; about the buffer. + (eglot-inlay-hints-mode 1) + (run-hooks 'eglot-managed-mode-hook)))) -(add-hook 'find-file-hook 'eglot--maybe-activate-editing-mode) (add-hook 'after-change-major-mode-hook 'eglot--maybe-activate-editing-mode) (defun eglot-clear-status (server) @@ -1811,13 +1951,13 @@ If it is activated, also signal textDocument/didOpen." (call-interactively what) (force-mode-line-update t)))))) -(defun eglot-manual () "Open on-line documentation." - (interactive) (browse-url "https://github.com/joaotavora/eglot#readme")) +(defun eglot-manual () "Open documentation." + (declare (obsolete info "29.1")) + (interactive) (info "(eglot)")) (easy-menu-define eglot-menu nil "Eglot" `("Eglot" ;; Commands for getting information and customization. - ["Read manual" eglot-manual] ["Customize Eglot" (lambda () (interactive) (customize-group "eglot"))] "--" ;; xref like commands. @@ -1893,12 +2033,11 @@ Uses THING, FACE, DEFS and PREPEND." (defun eglot--mode-line-format () "Compose the Eglot's mode-line." - (pcase-let* ((server (eglot-current-server)) - (nick (and server (eglot-project-nickname server))) - (pending (and server (hash-table-count - (jsonrpc--request-continuations server)))) - (`(,_id ,doing ,done-p ,_detail) (and server (eglot--spinner server))) - (last-error (and server (jsonrpc-last-error server)))) + (let* ((server (eglot-current-server)) + (nick (and server (eglot-project-nickname server))) + (pending (and server (hash-table-count + (jsonrpc--request-continuations server)))) + (last-error (and server (jsonrpc-last-error server)))) (append `(,(propertize eglot-menu-string @@ -1918,15 +2057,12 @@ Uses THING, FACE, DEFS and PREPEND." 'keymap (let ((map (make-sparse-keymap))) (define-key map [mode-line down-mouse-1] eglot-server-menu) map)) - ,@(when last-error + ,@(when last-error `("/" ,(eglot--mode-line-props "error" 'compilation-mode-line-fail '((mouse-3 eglot-clear-status "Clear this status")) (format "An error occurred: %s\n" (plist-get last-error - :message))))) - ,@(when (and doing (not done-p)) - `("/" ,(eglot--mode-line-props doing - 'compilation-mode-line-run '()))) + :message))))) ,@(when (cl-plusp pending) `("/" ,(eglot--mode-line-props (format "%d" pending) 'warning @@ -1949,13 +2085,13 @@ still unanswered LSP requests to the server\n")))))))) (defalias 'eglot--diag-data 'flymake-diagnostic-data) (cl-loop for i from 1 - for type in '(eglot-note eglot-warning eglot-error ) + for type in '(eglot-note eglot-warning eglot-error) do (put type 'flymake-overlay-control `((mouse-face . highlight) (priority . ,(+ 50 i)) (keymap . ,(let ((map (make-sparse-keymap))) (define-key map [mouse-1] - (eglot--mouse-call 'eglot-code-actions)) + (eglot--mouse-call 'eglot-code-actions)) map))))) @@ -2011,6 +2147,27 @@ COMMAND is a symbol naming the command." (_server (_method (eql telemetry/event)) &rest _any) "Handle notification telemetry/event.") ;; noop, use events buffer +(cl-defmethod eglot-handle-notification + (server (_method (eql $/progress)) &key token value) + "Handle $/progress notification identified by TOKEN from SERVER." + (when eglot-report-progress + (cl-flet ((fmt (&rest args) (mapconcat #'identity args " "))) + (eglot--dbind ((WorkDoneProgress) kind title percentage message) value + (pcase kind + ("begin" + (let* ((prefix (format (concat "[eglot] %s %s:" (when percentage " ")) + (eglot-project-nickname server) token)) + (pr (puthash token + (if percentage + (make-progress-reporter prefix 0 100 percentage 1 0) + (make-progress-reporter prefix nil nil nil 1 0)) + (eglot--progress-reporters server)))) + (progress-reporter-update pr percentage (fmt title message)))) + ("report" + (when-let ((pr (gethash token (eglot--progress-reporters server)))) + (progress-reporter-update pr percentage (fmt title message)))) + ("end" (remhash token (eglot--progress-reporters server)))))))) + (cl-defmethod eglot-handle-notification (_server (_method (eql textDocument/publishDiagnostics)) &key uri diagnostics &allow-other-keys) ; FIXME: doesn't respect `eglot-strict-mode' @@ -2022,9 +2179,11 @@ COMMAND is a symbol naming the command." (t 'eglot-note))) (mess (source code message) (concat source (and code (format " [%s]" code)) ": " message))) - (if-let ((buffer (find-buffer-visiting (eglot--uri-to-path uri)))) + (if-let* ((path (expand-file-name (eglot--uri-to-path uri))) + (buffer (find-buffer-visiting path))) (with-current-buffer buffer (cl-loop + initially (assoc-delete-all path flymake-list-only-diagnostics #'string=) for diag-spec across diagnostics collect (eglot--dbind ((Diagnostic) range code message severity source tags) diag-spec @@ -2043,7 +2202,7 @@ COMMAND is a symbol naming the command." (eglot--widening (goto-char (point-min)) (setq beg - (line-beginning-position + (eglot--bol (1+ (plist-get (plist-get range :start) :line)))) (setq end (line-end-position @@ -2067,7 +2226,6 @@ COMMAND is a symbol naming the command." (t (setq eglot--diagnostics diags))))) (cl-loop - with path = (expand-file-name (eglot--uri-to-path uri)) for diag-spec across diagnostics collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec (setq message (mess source code message)) @@ -2134,7 +2292,7 @@ THINGS are either registrations or unregisterations (sic)." (append (eglot--VersionedTextDocumentIdentifier) (list :languageId - (eglot--language-id (eglot--current-server-or-lose)) + (eglot--language-id (eglot--current-server-or-lose)) :text (eglot--widening (buffer-substring-no-properties (point-min) (point-max)))))) @@ -2191,6 +2349,7 @@ THINGS are either registrations or unregisterations (sic)." (defun eglot--before-change (beg end) "Hook onto `before-change-functions' with BEG and END." + (remove-overlays beg end 'eglot--overlay t) (when (listp eglot--recent-changes) ;; Records BEG and END, crucially convert them into LSP ;; (line/char) positions before that information is lost (because @@ -2203,6 +2362,9 @@ THINGS are either registrations or unregisterations (sic)." (,end . ,(copy-marker end t))) eglot--recent-changes))) +(defvar eglot--document-changed-hook '(eglot--signal-textDocument/didChange) + "Internal hook for doing things when the document changes.") + (defun eglot--after-change (beg end pre-change-length) "Hook onto `after-change-functions'. Records BEG, END and PRE-CHANGE-LENGTH locally." @@ -2243,7 +2405,7 @@ Records BEG, END and PRE-CHANGE-LENGTH locally." eglot-send-changes-idle-time nil (lambda () (eglot--when-live-buffer buf (when eglot--managed-mode - (eglot--signal-textDocument/didChange) + (run-hooks 'eglot--document-changed-hook) (setq eglot--change-idle-timer nil)))))))) ;; HACK! Launching a deferred sync request with outstanding changes is a @@ -2268,8 +2430,7 @@ Instead of a plist, an alist ((SECTION . VALUE) ...) can be used instead, but this variant is less reliable and not recommended. This variable should be set as a directory-local variable. See -See info node `(emacs)Directory Variables' for various ways to to -that. +info node `(emacs)Directory Variables' for various ways to do that. Here's an example value that establishes two sections relevant to the Pylsp and Gopls LSP servers: @@ -2290,9 +2451,7 @@ format described above.") (defun eglot-show-workspace-configuration (&optional server) "Dump `eglot-workspace-configuration' as JSON for debugging." - (interactive (list (and (eglot-current-server) - (eglot--read-server "Server configuration" - (eglot-current-server))))) + (interactive (list (eglot--read-server "Show workspace configuration for" t))) (let ((conf (eglot--workspace-configuration-plist server))) (with-current-buffer (get-buffer-create "*EGLOT workspace configuration*") (erase-buffer) @@ -2303,14 +2462,23 @@ format described above.") (json-pretty-print-buffer)) (pop-to-buffer (current-buffer))))) -(defun eglot--workspace-configuration (server) - (if (functionp eglot-workspace-configuration) - (funcall eglot-workspace-configuration server) - eglot-workspace-configuration)) - -(defun eglot--workspace-configuration-plist (server) - "Returns `eglot-workspace-configuration' suitable for serialization." - (let ((val (eglot--workspace-configuration server))) +(defun eglot--workspace-configuration-plist (server &optional path) + "Returns SERVER's workspace configuration as a plist. +If PATH consider that file's `file-name-directory' to get the +local value of the `eglot-workspace-configuration' variable, else +use the root of SERVER's `eglot--project'." + (let ((val (with-temp-buffer + (setq default-directory + (if path + (file-name-directory path) + (project-root (eglot--project server)))) + ;; Set the major mode to be the first of the managed + ;; modes. This is the one the user started eglot in. + (setq major-mode (car (eglot--major-modes server))) + (hack-dir-local-variables-non-file-buffer)() + (if (functionp eglot-workspace-configuration) + (funcall eglot-workspace-configuration server) + eglot-workspace-configuration)))) (or (and (consp (car val)) (cl-loop for (section . v) in val collect (if (keywordp section) section @@ -2335,24 +2503,17 @@ When called interactively, use the currently active server" (apply #'vector (mapcar (eglot--lambda ((ConfigurationItem) scopeUri section) - (with-temp-buffer - (let* ((uri-path (eglot--uri-to-path scopeUri)) - (default-directory - (if (and (not (string-empty-p uri-path)) - (file-directory-p uri-path)) - (file-name-as-directory uri-path) - (project-root (eglot--project server))))) - (setq-local major-mode (car (eglot--major-modes server))) - (hack-dir-local-variables-non-file-buffer) - (cl-loop for (wsection o) - on (eglot--workspace-configuration-plist server) - by #'cddr - when (string= - (if (keywordp wsection) - (substring (symbol-name wsection) 1) - wsection) - section) - return o)))) + (cl-loop + with scope-uri-path = (and scopeUri (eglot--uri-to-path scopeUri)) + for (wsection o) + on (eglot--workspace-configuration-plist server scope-uri-path) + by #'cddr + when (string= + (if (keywordp wsection) + (substring (symbol-name wsection) 1) + wsection) + section) + return o)) items))) (defun eglot--signal-textDocument/didChange () @@ -2383,7 +2544,6 @@ When called interactively, use the currently active server" vconcat `[,(list :range `(:start ,beg :end ,end) :rangeLength len :text text)])))) (setq eglot--recent-changes nil) - (setf (eglot--spinner server) (list nil :textDocument/didChange t)) (jsonrpc--call-deferred server)))) (defun eglot--signal-textDocument/didOpen () @@ -2402,7 +2562,7 @@ When called interactively, use the currently active server" :textDocument/didClose `(:textDocument ,(eglot--TextDocumentIdentifier))))) (defun eglot--signal-textDocument/willSave () - "Send textDocument/willSave to server." + "Maybe send textDocument/willSave to server." (let ((server (eglot--current-server-or-lose)) (params `(:reason 1 :textDocument ,(eglot--TextDocumentIdentifier)))) (when (eglot--server-capable :textDocumentSync :willSave) @@ -2414,22 +2574,23 @@ When called interactively, use the currently active server" :timeout 0.5)))))) (defun eglot--signal-textDocument/didSave () - "Send textDocument/didSave to server." + "Maybe send textDocument/didSave to server." (eglot--signal-textDocument/didChange) - (jsonrpc-notify - (eglot--current-server-or-lose) - :textDocument/didSave - (list - ;; TODO: Handle TextDocumentSaveRegistrationOptions to control this. - :text (buffer-substring-no-properties (point-min) (point-max)) - :textDocument (eglot--TextDocumentIdentifier)))) + (when (eglot--server-capable :textDocumentSync :save) + (jsonrpc-notify + (eglot--current-server-or-lose) + :textDocument/didSave + (list + ;; TODO: Handle TextDocumentSaveRegistrationOptions to control this. + :text (buffer-substring-no-properties (point-min) (point-max)) + :textDocument (eglot--TextDocumentIdentifier))))) (defun eglot-flymake-backend (report-fn &rest _more) "A Flymake backend for Eglot. Calls REPORT-FN (or arranges for it to be called) when the server publishes diagnostics. Between calls to this function, REPORT-FN may be called multiple times (respecting the protocol of -`flymake-backend-functions')." +`flymake-diagnostic-functions')." (cond (eglot--managed-mode (setq eglot--current-flymake-report-fn report-fn) (eglot--report-to-flymake eglot--diagnostics)) @@ -2452,7 +2613,7 @@ may be called multiple times (respecting the protocol of (defun eglot-xref-backend () "Eglot xref backend." 'eglot) (defvar eglot--temp-location-buffers (make-hash-table :test #'equal) - "Helper variable for `eglot--handling-xrefs'.") + "Helper variable for `eglot--collecting-xrefs'.") (defvar eglot-xref-lessp-function #'ignore "Compare two `xref-item' objects for sorting.") @@ -2480,14 +2641,14 @@ Try to visit the target file for a richer summary line." (collect (lambda () (eglot--widening (pcase-let* ((`(,beg . ,end) (eglot--range-region range)) - (bol (progn (goto-char beg) (line-beginning-position))) + (bol (progn (goto-char beg) (eglot--bol))) (substring (buffer-substring bol (line-end-position))) (hi-beg (- beg bol)) (hi-end (- (min (line-end-position) end) bol))) (add-face-text-property hi-beg hi-end 'xref-match t substring) (list substring (line-number-at-pos (point) t) - (eglot-current-column) (- end beg)))))) + (eglot-utf-32-linepos) (- end beg)))))) (`(,summary ,line ,column ,length) (cond (visiting (with-current-buffer visiting (funcall collect))) @@ -2510,8 +2671,7 @@ Try to visit the target file for a richer summary line." "Ask for :workspace/symbol on PAT, return list of formatted strings. If BUFFER, switch to it before." (with-current-buffer (or buffer (current-buffer)) - (unless (eglot--server-capable :workspaceSymbolProvider) - (eglot--error "This LSP server isn't a :workspaceSymbolProvider")) + (eglot--server-capable-or-lose :workspaceSymbolProvider) (mapcar (lambda (wss) (eglot--dbind ((WorkspaceSymbol) name containerName kind) wss @@ -2534,7 +2694,7 @@ If BUFFER, switch to it before." (let ((probe (gethash pat cache :missing))) (if (eq probe :missing) (puthash pat (refresh pat) cache) probe))) - (lookup (pat) + (lookup (pat _point) (let ((res (lookup-1 pat)) (def (and (string= pat "") (gethash :default cache)))) (append def res nil))) @@ -2542,16 +2702,12 @@ If BUFFER, switch to it before." (cl-getf (get-text-property 0 'eglot--lsp-workspaceSymbol c) :score 0))) - (lambda (string _pred action) - (pcase action - (`metadata `(metadata - (cycle-sort-function - . ,(lambda (completions) - (cl-sort completions #'> :key #'score))) - (category . eglot-indirection-joy))) - (`(eglot--lsp-tryc . ,point) `(eglot--lsp-tryc . (,string . ,point))) - (`(eglot--lsp-allc . ,_point) `(eglot--lsp-allc . ,(lookup string))) - (_ nil)))))) + (external-completion-table + 'eglot-indirection-joy + #'lookup + `((cycle-sort-function + . ,(lambda (completions) + (cl-sort completions #'> :key #'score)))))))) (defun eglot--recover-workspace-symbol-meta (string) "Search `eglot--workspace-symbols-cache' for rich entry of STRING." @@ -2563,9 +2719,6 @@ If BUFFER, switch to it before." (setq v (cdr v)))) eglot--workspace-symbols-cache))) -(add-to-list 'completion-category-overrides - '(eglot-indirection-joy (styles . (eglot--lsp-backend-style)))) - (cl-defmethod xref-backend-identifier-at-point ((_backend (eql eglot))) (let ((attempt (and (xref--prompt-p this-command) @@ -2580,13 +2733,12 @@ If BUFFER, switch to it before." (cl-defun eglot--lsp-xrefs-for-method (method &key extra-params capability) "Make `xref''s for METHOD, EXTRA-PARAMS, check CAPABILITY." - (unless (eglot--server-capable - (or capability - (intern - (format ":%sProvider" - (cadr (split-string (symbol-name method) - "/")))))) - (eglot--error "Sorry, this server doesn't do %s" method)) + (eglot--server-capable-or-lose + (or capability + (intern + (format ":%sProvider" + (cadr (split-string (symbol-name method) + "/")))))) (let ((response (jsonrpc-request (eglot--current-server-or-lose) @@ -2604,7 +2756,7 @@ If BUFFER, switch to it before." uri range)))))) (if (vectorp response) response (and response (list response))))))) -(cl-defun eglot--lsp-xref-helper (method &key extra-params capability ) +(cl-defun eglot--lsp-xref-helper (method &key extra-params capability) "Helper for `eglot-find-declaration' & friends." (let ((eglot--lsp-xref-refs (eglot--lsp-xrefs-for-method method @@ -2636,7 +2788,7 @@ If BUFFER, switch to it before." (get-text-property 0 'eglot--lsp-workspaceSymbol probe) (eglot--dbind ((Location) uri range) location (list (eglot--xref-make-match name uri range)))) - (eglot--lsp-xrefs-for-method :textDocument/definition)))) + (eglot--lsp-xrefs-for-method :textDocument/definition)))) (cl-defmethod xref-backend-references ((_backend (eql eglot)) _identifier) (or @@ -2675,7 +2827,7 @@ for which LSP on-type-formatting should be requested." `(:textDocument/onTypeFormatting :documentOnTypeFormattingProvider ,`(:position ,(eglot--pos-to-lsp-position beg) - :ch ,(string on-type-format)))) + :ch ,(string on-type-format)))) ((and beg end) `(:textDocument/rangeFormatting :documentRangeFormattingProvider @@ -2683,8 +2835,7 @@ for which LSP on-type-formatting should be requested." :end (eglot--pos-to-lsp-position end))))) (t '(:textDocument/formatting :documentFormattingProvider nil))))) - (unless (eglot--server-capable cap) - (eglot--error "Server can't format!")) + (eglot--server-capable-or-lose cap) (eglot--apply-text-edits (jsonrpc-request (eglot--current-server-or-lose) @@ -2708,10 +2859,9 @@ for which LSP on-type-formatting should be requested." (cl-sort completions #'string-lessp :key (lambda (c) - (or (plist-get - (get-text-property 0 'eglot--lsp-item c) - :sortText) - ""))))) + (plist-get + (get-text-property 0 'eglot--lsp-item c) + :sortText))))) (metadata `(metadata (category . eglot) (display-sort-function . ,sort-completions))) resp items (cached-proxies :none) @@ -2731,16 +2881,20 @@ for which LSP on-type-formatting should be requested." (mapcar (jsonrpc-lambda (&rest item &key label insertText insertTextFormat - &allow-other-keys) + textEdit &allow-other-keys) (let ((proxy - (cond ((and (eql insertTextFormat 2) - (eglot--snippet-expansion-fn)) + ;; Snippet or textEdit, it's safe to + ;; display/insert the label since + ;; it'll be adjusted. If no usable + ;; insertText at all, label is best, + ;; too. + (cond ((or (and (eql insertTextFormat 2) + (eglot--snippet-expansion-fn)) + textEdit + (null insertText) + (string-empty-p insertText)) (string-trim-left label)) - ((and insertText - (not (string-empty-p insertText))) - insertText) - (t - (string-trim-left label))))) + (t insertText)))) (unless (zerop (length proxy)) (put-text-property 0 1 'eglot--lsp-item item proxy)) proxy)) @@ -2801,7 +2955,10 @@ for which LSP on-type-formatting should be requested." (when-let* ((lsp-item (get-text-property 0 'eglot--lsp-item proxy)) (kind (alist-get (plist-get lsp-item :kind) eglot--kind-names))) - (intern (downcase kind)))) + (pcase kind + ("EnumMember" 'enum-member) + ("TypeParameter" 'type-parameter) + (_ (intern (downcase kind)))))) :company-deprecated (lambda (proxy) (when-let ((lsp-item (get-text-property 0 'eglot--lsp-item proxy))) @@ -2835,7 +2992,7 @@ for which LSP on-type-formatting should be requested." (looking-back (regexp-opt (cl-coerce (cl-getf completion-capability :triggerCharacters) 'list)) - (line-beginning-position)))) + (eglot--bol)))) :exit-function (lambda (proxy status) (when (memq status '(finished exact)) @@ -2893,7 +3050,7 @@ for which LSP on-type-formatting should be requested." (defun eglot--hover-info (contents &optional _range) (mapconcat #'eglot--format-markup (if (vectorp contents) contents (list contents)) "\n")) - + (defun eglot--sig-info (sigs active-sig sig-help-active-param) (cl-loop for (sig . moresigs) on (append sigs nil) for i from 0 @@ -2904,7 +3061,7 @@ for which LSP on-type-formatting should be requested." (let ((active-param (or activeParameter sig-help-active-param)) params-start params-end) ;; Ad-hoc attempt to parse label as () - (when (looking-at "\\([^(]+\\)(\\([^)]+\\))") + (when (looking-at "\\([^(]*\\)(\\([^)]+\\))") (setq params-start (match-beginning 2) params-end (match-end 2)) (add-face-text-property (match-beginning 1) (match-end 1) 'font-lock-function-name-face)) @@ -3081,25 +3238,7 @@ Returns a list as described in docstring of `imenu--index-alist'." (save-excursion (save-restriction (narrow-to-region beg end) - - ;; On emacs versions < 26.2, - ;; `replace-buffer-contents' is buggy - it calls - ;; change functions with invalid arguments - so we - ;; manually call the change functions here. - ;; - ;; See emacs bugs #32237, #32278: - ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32237 - ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32278 - (let ((inhibit-modification-hooks t) - (length (- end beg)) - (beg (marker-position beg)) - (end (marker-position end))) - (run-hook-with-args 'before-change-functions - beg end) - (replace-buffer-contents temp) - (run-hook-with-args 'after-change-functions - beg (+ beg (length newText)) - length)))) + (replace-buffer-contents temp))) (progress-reporter-update reporter (cl-incf done))))))) (mapcar (eglot--lambda ((TextEdit) range newText) (cons newText (eglot--range-region range 'markers))) @@ -3128,7 +3267,7 @@ Returns a list as described in docstring of `imenu--index-alist'." (unless (y-or-n-p (format "[eglot] Server wants to edit:\n %s\n Proceed? " (mapconcat #'identity (mapcar #'car prepared) "\n "))) - (jsonrpc-error "User cancelled server edit"))) + (jsonrpc-error "User canceled server edit"))) (cl-loop for edit in prepared for (path edits version) = edit do (with-current-buffer (find-file-noselect path) @@ -3143,8 +3282,7 @@ Returns a list as described in docstring of `imenu--index-alist'." "unknown symbol")) nil nil nil nil (symbol-name (symbol-at-point))))) - (unless (eglot--server-capable :renameProvider) - (eglot--error "Server can't rename!")) + (eglot--server-capable-or-lose :renameProvider) (eglot--apply-workspace-edit (jsonrpc-request (eglot--current-server-or-lose) :textDocument/rename `(,@(eglot--TextDocumentPositionParams) @@ -3171,9 +3309,7 @@ at point. With prefix argument, prompt for ACTION-KIND." '("quickfix" "refactor.extract" "refactor.inline" "refactor.rewrite" "source.organizeImports"))) t)) - (unless (or (not interactive) - (eglot--server-capable :codeActionProvider)) - (eglot--error "Server can't execute code actions!")) + (eglot--server-capable-or-lose :codeActionProvider) (let* ((server (eglot--current-server-or-lose)) (actions (jsonrpc-request @@ -3200,7 +3336,7 @@ at point. With prefix argument, prompt for ACTION-KIND." actions))) (defun eglot--read-execute-code-action (actions server &optional action-kind) - "Helper for interactive calls to `eglot-code-actions'" + "Helper for interactive calls to `eglot-code-actions'." (let* ((menu-items (or (cl-loop for a in actions collect (cons (plist-get a :title) a)) @@ -3253,8 +3389,12 @@ at point. With prefix argument, prompt for ACTION-KIND." (eglot-unregister-capability server method id) (let* (success (globs (mapcar - (eglot--lambda ((FileSystemWatcher) globPattern) - (eglot--glob-compile globPattern t t)) + (eglot--lambda ((FileSystemWatcher) globPattern kind) + (cons (eglot--glob-compile globPattern t t) + ;; the default "7" means bitwise OR of + ;; WatchKind.Create (1), WatchKind.Change + ;; (2), WatchKind.Delete (4) + (or kind 7))) watchers)) (dirs-to-watch (delete-dups (mapcar #'file-name-directory @@ -3262,21 +3402,24 @@ at point. With prefix argument, prompt for ACTION-KIND." (eglot--project server)))))) (cl-labels ((handle-event - (event) - (pcase-let ((`(,desc ,action ,file ,file1) event)) - (cond - ((and (memq action '(created changed deleted)) - (cl-find file globs :test (lambda (f g) (funcall g f)))) - (jsonrpc-notify - server :workspace/didChangeWatchedFiles - `(:changes ,(vector `(:uri ,(eglot--path-to-uri file) - :type ,(cl-case action - (created 1) - (changed 2) - (deleted 3))))))) - ((eq action 'renamed) - (handle-event `(,desc 'deleted ,file)) - (handle-event `(,desc 'created ,file1))))))) + (event) + (pcase-let* ((`(,desc ,action ,file ,file1) event) + (action-type (cl-case action + (created 1) (changed 2) (deleted 3))) + (action-bit (when action-type + (ash 1 (1- action-type))))) + (cond + ((and (memq action '(created changed deleted)) + (cl-loop for (glob . kind-bitmask) in globs + thereis (and (> (logand kind-bitmask action-bit) 0) + (funcall glob file)))) + (jsonrpc-notify + server :workspace/didChangeWatchedFiles + `(:changes ,(vector `(:uri ,(eglot--path-to-uri file) + :type ,action-type))))) + ((eq action 'renamed) + (handle-event `(,desc 'deleted ,file)) + (handle-event `(,desc 'created ,file1))))))) (unwind-protect (progn (dolist (dir dirs-to-watch) @@ -3392,6 +3535,123 @@ If NOERROR, return predicate, else erroring function." (revert-buffer) (pop-to-buffer (current-buffer))))) + +;;; Inlay hints +(defface eglot-inlay-hint-face '((t (:height 0.8 :inherit shadow))) + "Face used for inlay hint overlays.") + +(defface eglot-type-hint-face '((t (:inherit eglot-inlay-hint-face))) + "Face used for type inlay hint overlays.") + +(defface eglot-parameter-hint-face '((t (:inherit eglot-inlay-hint-face))) + "Face used for parameter inlay hint overlays.") + +(defvar-local eglot--outstanding-inlay-hints-region (cons nil nil) + "Jit-lock-calculated (FROM . TO) region with potentially outdated hints") + +(defvar-local eglot--outstanding-inlay-hints-last-region nil) + +(defvar-local eglot--outstanding-inlay-regions-timer nil + "Helper timer for `eglot--update-hints'") + +(defun eglot--update-hints (from to) + "Jit-lock function for Eglot inlay hints." + (cl-symbol-macrolet ((region eglot--outstanding-inlay-hints-region) + (last-region eglot--outstanding-inlay-hints-last-region) + (timer eglot--outstanding-inlay-regions-timer)) + (setcar region (min (or (car region) (point-max)) from)) + (setcdr region (max (or (cdr region) (point-min)) to)) + ;; HACK: We're relying on knowledge of jit-lock internals here. The + ;; condition comparing `jit-lock-context-unfontify-pos' to + ;; `point-max' is a heuristic for telling whether this call to + ;; `jit-lock-functions' happens after `jit-lock-context-timer' has + ;; just run. Only after this delay should we start the smoothing + ;; timer that will eventually call `eglot--update-hints-1' with the + ;; coalesced region. I wish we didn't need the timer, but sometimes + ;; a lot of "non-contextual" calls come in all at once and do verify + ;; the condition. Notice it is a 0 second timer though, so we're + ;; not introducing any more delay over jit-lock's timers. + (when (= jit-lock-context-unfontify-pos (point-max)) + (if timer (cancel-timer timer)) + (let ((buf (current-buffer))) + (setq timer (run-at-time + 0 nil + (lambda () + (eglot--when-live-buffer buf + ;; HACK: In some pathological situations + ;; (Emacs's own coding.c, for example), + ;; jit-lock is calling `eglot--update-hints' + ;; repeatedly with same sequence of + ;; arguments, which leads to + ;; `eglot--update-hints-1' being called with + ;; the same region repeatedly. This happens + ;; even if the hint-painting code does + ;; nothing else other than widen, narrow, + ;; move point then restore these things. + ;; Possible Emacs bug, but this fixes it. + (unless (equal last-region region) + (eglot--update-hints-1 (max (car region) (point-min)) + (min (cdr region) (point-max))) + (setq last-region region)) + (setq region (cons nil nil) + timer nil))))))))) + +(defun eglot--update-hints-1 (from to) + "Do most work for `eglot--update-hints', including LSP request." + (let* ((buf (current-buffer)) + (paint-hint + (eglot--lambda ((InlayHint) position kind label paddingLeft paddingRight) + (goto-char (eglot--lsp-position-to-point position)) + (when (or (> (point) to) (< (point) from)) (cl-return)) + (let ((left-pad (and paddingLeft + (not (eq paddingLeft :json-false)) + (not (memq (char-before) '(32 9))) " ")) + (right-pad (and paddingRight + (not (eq paddingRight :json-false)) + (not (memq (char-after) '(32 9))) " "))) + (cl-flet + ((do-it (text lpad rpad) + (let ((ov (make-overlay (point) (point)))) + (overlay-put ov 'before-string + (propertize + (concat lpad text rpad) + 'face (pcase kind + (1 'eglot-type-hint-face) + (2 'eglot-parameter-hint-face) + (_ 'eglot-inlay-hint-face)))) + (overlay-put ov 'eglot--inlay-hint t) + (overlay-put ov 'eglot--overlay t)))) + (if (stringp label) (do-it label left-pad right-pad) + (cl-loop + for i from 0 for ldetail across label + do (eglot--dbind ((InlayHintLabelPart) value) ldetail + (do-it value + (and (zerop i) left-pad) + (and (= i (1- (length label))) right-pad)))))))))) + (jsonrpc-async-request + (eglot--current-server-or-lose) + :textDocument/inlayHint + (list :textDocument (eglot--TextDocumentIdentifier) + :range (list :start (eglot--pos-to-lsp-position from) + :end (eglot--pos-to-lsp-position to))) + :success-fn (lambda (hints) + (eglot--when-live-buffer buf + (eglot--widening + (remove-overlays from to 'eglot--inlay-hint t) + (mapc paint-hint hints)))) + :deferred 'eglot--update-hints-1))) + +(define-minor-mode eglot-inlay-hints-mode + "Minor mode for annotating buffers with LSP server's inlay hints." + :global nil + (cond (eglot-inlay-hints-mode + (if (eglot--server-capable :inlayHintProvider) + (jit-lock-register #'eglot--update-hints 'contextual) + (eglot-inlay-hints-mode -1))) + (t + (jit-lock-unregister #'eglot--update-hints) + (remove-overlays nil nil 'eglot--inlay-hint t)))) + ;;; Hacks ;;; @@ -3404,6 +3664,16 @@ If NOERROR, return predicate, else erroring function." (add-to-list 'desktop-minor-mode-handlers '(eglot--managed-mode . ignore))) +;;; Misc +;;; +;;;###autoload +(progn + (put 'eglot--debbugs-or-github-bug-uri 'bug-reference-url-format t) + (defun eglot--debbugs-or-github-bug-uri () + (format (if (string= (match-string 2) "github") + "https://github.com/joaotavora/eglot/issues/%s" + "https://debbugs.gnu.org/%s") + (match-string 3)))) ;;; Obsolete ;;; @@ -3411,46 +3681,10 @@ If NOERROR, return predicate, else erroring function." 'eglot-managed-mode-hook "1.6") (provide 'eglot) - -;;; Backend completion - -;; Written by Stefan Monnier circa 2016. Something to move to -;; minibuffer.el "ASAP" (with all the `eglot--lsp-' replaced by -;; something else. The very same code already in SLY and stable for a -;; long time. - -;; This "completion style" delegates all the work to the "programmable -;; completion" table which is then free to implement its own -;; completion style. Typically this is used to take advantage of some -;; external tool which already has its own completion system and -;; doesn't give you efficient access to the prefix completion needed -;; by other completion styles. The table should recognize the symbols -;; 'eglot--lsp-tryc and 'eglot--lsp-allc as ACTION, reply with -;; (eglot--lsp-tryc COMP...) or (eglot--lsp-allc . (STRING . POINT)), -;; accordingly. tryc/allc names made akward/recognizable on purpose. - -(add-to-list 'completion-styles-alist - '(eglot--lsp-backend-style - eglot--lsp-backend-style-try-completion - eglot--lsp-backend-style-all-completions - "Ad-hoc completion style provided by the completion table.")) - -(defun eglot--lsp-backend-style-call (op string table pred point) - (when (functionp table) - (let ((res (funcall table string pred (cons op point)))) - (when (eq op (car-safe res)) - (cdr res))))) - -(defun eglot--lsp-backend-style-try-completion (string table pred point) - (eglot--lsp-backend-style-call 'eglot--lsp-tryc string table pred point)) - -(defun eglot--lsp-backend-style-all-completions (string table pred point) - (eglot--lsp-backend-style-call 'eglot--lsp-allc string table pred point)) - ;; Local Variables: -;; bug-reference-bug-regexp: "\\(github#\\([0-9]+\\)\\)" -;; bug-reference-url-format: "https://github.com/joaotavora/eglot/issues/%s" +;; bug-reference-bug-regexp: "\\(\\(github\\|bug\\)#\\([0-9]+\\)\\)" +;; bug-reference-url-format: eglot--debbugs-or-github-bug-uri ;; checkdoc-force-docstrings-flag: nil ;; End: