diff options
| author | João Távora | 2025-04-17 00:31:07 +0100 |
|---|---|---|
| committer | João Távora | 2025-04-17 00:31:38 +0100 |
| commit | c5b97b7b321c873dbe8a36d27781435ba10a2278 (patch) | |
| tree | e724c6f583a293ce7e81d7f525b951a3759d49f5 | |
| parent | 2330e7b6d676761b60c3247f53224815512a9a58 (diff) | |
| download | emacs-c5b97b7b321c873dbe8a36d27781435ba10a2278.tar.gz emacs-c5b97b7b321c873dbe8a36d27781435ba10a2278.zip | |
Eglot: be aware of LSP version of contextual diagnostics
In certain situations, Eglot has to report to the server parts
of the diagnostics that it itself sent us. The server may use
this as context to compute code actions, for example. Eglot
selects diagnostics by asking Flymake, looking for the ones that
contain Eglot-specific cookies.
But when doing so, it is important to also be aware of the LSP
document version these each of these diagnostics pertain to,
since if a diagonstic in the buffer pertains to an older version
of the LSP document (because Flymake fired or the server hasn't
pushed a new set), that diagnostics 'eglot-lsp-data' cookie is
invalid and possibly harmful.
An example is when a diagnostic extends all the way to the end
of the buffer. If we attempt to fix by shortening the buffer,
an Eldoc-started code actions request may be sent to the server
considering the soon-to-be-deleted Flymake diagnostic as
context. But that diagnostic's 'eglot-lsp-data' cookie is no
longer valid and when processing that context we try to go past
point-max and burp an annoying error.
Best to check the version of the diagnostic (if we have it) and
ignore the ones that don't match the document version.
* lisp/progmodes/eglot.el (eglot--versioned-identifier): Move up.
(eglot--flymake-diagnostics, eglot--diag-to-lsp-diag): New helpers.
(eglot-handle-notification): Record LSP doc version in diagnostics.
(eglot--code-action-bounds)
(eglot--code-action-params): Use eglot--flymake-diagnostics.
| -rw-r--r-- | lisp/progmodes/eglot.el | 29 |
1 files changed, 21 insertions, 8 deletions
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 8231c542d29..b077f4a6207 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el | |||
| @@ -1240,6 +1240,9 @@ If optional MARKERS, make markers instead." | |||
| 1240 | (cl-defmethod initialize-instance :before ((_server eglot-lsp-server) &optional args) | 1240 | (cl-defmethod initialize-instance :before ((_server eglot-lsp-server) &optional args) |
| 1241 | (cl-remf args :initializationOptions)) | 1241 | (cl-remf args :initializationOptions)) |
| 1242 | 1242 | ||
| 1243 | (defvar-local eglot--versioned-identifier 0 | ||
| 1244 | "LSP document version. Bumped on `eglot--after-change'.") | ||
| 1245 | |||
| 1243 | (defvar eglot--servers-by-project (make-hash-table :test #'equal) | 1246 | (defvar eglot--servers-by-project (make-hash-table :test #'equal) |
| 1244 | "Keys are projects. Values are lists of processes.") | 1247 | "Keys are projects. Values are lists of processes.") |
| 1245 | 1248 | ||
| @@ -2556,6 +2559,19 @@ still unanswered LSP requests to the server\n")))) | |||
| 2556 | (defalias 'eglot--make-diag #'flymake-make-diagnostic) | 2559 | (defalias 'eglot--make-diag #'flymake-make-diagnostic) |
| 2557 | (defalias 'eglot--diag-data #'flymake-diagnostic-data) | 2560 | (defalias 'eglot--diag-data #'flymake-diagnostic-data) |
| 2558 | 2561 | ||
| 2562 | (defun eglot--flymake-diagnostics (beg &optional end) | ||
| 2563 | "Like `flymake-diagnostics', but for Eglot-specific diagnostics." | ||
| 2564 | (cl-loop for diag in (flymake-diagnostics beg end) | ||
| 2565 | for data = (eglot--diag-data diag) | ||
| 2566 | for lsp-diag = (alist-get 'eglot-lsp-diag data) | ||
| 2567 | for version = (alist-get 'eglot--doc-version data) | ||
| 2568 | when (and lsp-diag (or (null version) | ||
| 2569 | (= version eglot--versioned-identifier))) | ||
| 2570 | collect diag)) | ||
| 2571 | |||
| 2572 | (defun eglot--diag-to-lsp-diag (diag) | ||
| 2573 | (alist-get 'eglot-lsp-diag (eglot--diag-data diag))) | ||
| 2574 | |||
| 2559 | (defvar eglot-diagnostics-map | 2575 | (defvar eglot-diagnostics-map |
| 2560 | (let ((map (make-sparse-keymap))) | 2576 | (let ((map (make-sparse-keymap))) |
| 2561 | (define-key map [mouse-2] #'eglot-code-actions-at-mouse) | 2577 | (define-key map [mouse-2] #'eglot-code-actions-at-mouse) |
| @@ -2658,8 +2674,6 @@ Value is (TRUENAME . (:uri STR)), where STR is what is sent to the | |||
| 2658 | server on textDocument/didOpen and similar calls. TRUENAME is the | 2674 | server on textDocument/didOpen and similar calls. TRUENAME is the |
| 2659 | expensive cached value of `file-truename'.") | 2675 | expensive cached value of `file-truename'.") |
| 2660 | 2676 | ||
| 2661 | (defvar-local eglot--versioned-identifier 0) | ||
| 2662 | |||
| 2663 | (cl-defmethod eglot-handle-notification | 2677 | (cl-defmethod eglot-handle-notification |
| 2664 | (server (_method (eql textDocument/publishDiagnostics)) | 2678 | (server (_method (eql textDocument/publishDiagnostics)) |
| 2665 | &key uri diagnostics version | 2679 | &key uri diagnostics version |
| @@ -2715,7 +2729,8 @@ expensive cached value of `file-truename'.") | |||
| 2715 | (eglot--make-diag | 2729 | (eglot--make-diag |
| 2716 | (current-buffer) beg end | 2730 | (current-buffer) beg end |
| 2717 | (eglot--diag-type severity) | 2731 | (eglot--diag-type severity) |
| 2718 | message `((eglot-lsp-diag . ,diag-spec)) | 2732 | message `((eglot-lsp-diag . ,diag-spec) |
| 2733 | (eglot--doc-version . ,version)) | ||
| 2719 | (when-let* ((faces | 2734 | (when-let* ((faces |
| 2720 | (cl-loop for tag across tags | 2735 | (cl-loop for tag across tags |
| 2721 | when (alist-get tag eglot--tag-faces) | 2736 | when (alist-get tag eglot--tag-faces) |
| @@ -3983,7 +3998,7 @@ edit proposed by the server." | |||
| 3983 | "Calculate appropriate bounds depending on region and point." | 3998 | "Calculate appropriate bounds depending on region and point." |
| 3984 | (let (diags boftap) | 3999 | (let (diags boftap) |
| 3985 | (cond ((use-region-p) `(,(region-beginning) ,(region-end))) | 4000 | (cond ((use-region-p) `(,(region-beginning) ,(region-end))) |
| 3986 | ((setq diags (flymake-diagnostics (point))) | 4001 | ((setq diags (eglot--flymake-diagnostics (point))) |
| 3987 | (cl-loop for d in diags | 4002 | (cl-loop for d in diags |
| 3988 | minimizing (flymake-diagnostic-beg d) into beg | 4003 | minimizing (flymake-diagnostic-beg d) into beg |
| 3989 | maximizing (flymake-diagnostic-end d) into end | 4004 | maximizing (flymake-diagnostic-end d) into end |
| @@ -4000,10 +4015,8 @@ edit proposed by the server." | |||
| 4000 | :end (eglot--pos-to-lsp-position end)) | 4015 | :end (eglot--pos-to-lsp-position end)) |
| 4001 | :context | 4016 | :context |
| 4002 | `(:diagnostics | 4017 | `(:diagnostics |
| 4003 | [,@(cl-loop for diag in (flymake-diagnostics beg end) | 4018 | [,@(mapcar #'eglot--diag-to-lsp-diag |
| 4004 | when (cdr (assoc 'eglot-lsp-diag | 4019 | (eglot--flymake-diagnostics beg end))] |
| 4005 | (eglot--diag-data diag))) | ||
| 4006 | collect it)] | ||
| 4007 | ,@(when only `(:only [,only])) | 4020 | ,@(when only `(:only [,only])) |
| 4008 | ,@(when triggerKind `(:triggerKind ,triggerKind))))) | 4021 | ,@(when triggerKind `(:triggerKind ,triggerKind))))) |
| 4009 | 4022 | ||