aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoão Távora2025-04-17 00:31:07 +0100
committerJoão Távora2025-04-17 00:31:38 +0100
commitc5b97b7b321c873dbe8a36d27781435ba10a2278 (patch)
treee724c6f583a293ce7e81d7f525b951a3759d49f5
parent2330e7b6d676761b60c3247f53224815512a9a58 (diff)
downloademacs-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.el29
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
2658server on textDocument/didOpen and similar calls. TRUENAME is the 2674server on textDocument/didOpen and similar calls. TRUENAME is the
2659expensive cached value of `file-truename'.") 2675expensive 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