aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoão Távora2026-01-10 17:10:38 +0000
committerJoão Távora2026-01-11 03:42:01 +0000
commit89633fef71286b9f50585ec95f883de7041b4be7 (patch)
tree36b77e3dc1fcfa84eb2dcfd03f1fad8ad3c965b4
parent6921244718522b27461b06cca7b29e187861f46f (diff)
downloademacs-89633fef71286b9f50585ec95f883de7041b4be7.tar.gz
emacs-89633fef71286b9f50585ec95f883de7041b4be7.zip
Eglot: rework Flymake integration ahead of more changes
LSP Diagnostics are converted to Flymake diagnostics just-in-time. Introduce helpers that allow precise control over the type of the reports (clearing or incremental) and the inhibition of reports. * lisp/progmodes/eglot.el (eglot--pulled-diagnostics) (eglot--pushed-diagnostics): Rework docstring. (eglot--flymake-sniff-diagnostics): Rename from eglot--flymake-diagnostics. (eglot--diagnostics-map, cl-loop, eglot-warning) (eglot-note, eglot-error): Move to Flymake section. (eglot--find-buffer-visiting): New helper.. (eglot--flymake-handle-push): New helper. (eglot--flymake-report-1, eglot--flymake-report-2) (eglot--flymake-report-push+pulled): New helpers. (eglot--flymake-make-diag): Take REGION arg. (eglot--handle-notification<textDocument/PublishDiagnostics>): Use eglot--flymake-handle-push. (eglot--flymake-pull): Call eglot--flymake-report-push+pulled. (eglot--flymake-report): Delete. (eglot--flymake-reset): New helper. (eglot--managed-mode): Use eglot--flymake-reset. (eglot--diag-to-lsp-diag): Delete. (eglot--signal-textDocument/didOpen) (eglot--managed-mode): Use eglot--flymake-reset. (eglot--maybe-activate-editing-mode): Don't reset Flymake things here. (eglot--code-action-params): Tweak. (eglot--code-action-bounds): Use eglot--flymake-sniff-diagnostics. (eglot--capf-session-flush): Tweak.
-rw-r--r--lisp/progmodes/eglot.el321
1 files changed, 182 insertions, 139 deletions
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index b6c4d5b7a89..a4440235cbe 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -2227,15 +2227,16 @@ Use `eglot-managed-p' to determine if current buffer is managed.")
2227(defvar eglot--highlights nil "Overlays for `eglot-highlight-eldoc-function'.") 2227(defvar eglot--highlights nil "Overlays for `eglot-highlight-eldoc-function'.")
2228 2228
2229(defvar-local eglot--pulled-diagnostics nil 2229(defvar-local eglot--pulled-diagnostics nil
2230 "A list (DIAGNOSTICS RESULT-ID) \"pulled\" for current buffer. 2230 "A list (DIAGNOSTICS VERSION RESULT-ID) \"pulled\" for current buffer.
2231DIAGNOSTICS is a list of Flymake diagnostics objects. RESULT-ID 2231DIAGNOSTICS is a sequence of LSP or Flymake diagnostics objects.
2232identifies this diagnostic result as is used for incremental updates.") 2232RESULT-ID identifies this diagnostic result as is used for incremental
2233updates.")
2233 2234
2234(defvar-local eglot--pushed-diagnostics nil 2235(defvar-local eglot--pushed-diagnostics nil
2235 "A list (DIAGNOSTICS VERSION) \"pushed\" for current buffer. 2236 "A list (DIAGNOSTICS VERSION) \"pushed\" for current buffer.
2236DIAGNOSTICS is a list of Flymake diagnostics objects. VERSION is the 2237DIAGNOSTICS is a sequence of LSP or Flymake diagnostics objects.
2237LSP Document version reported for DIAGNOSTICS (comparable to 2238VERSION is the LSP Document version reported for DIAGNOSTICS (comparable
2238`eglot--docver') or nil if server didn't bother.") 2239to `eglot--docver') or nil if server didn't bother.")
2239 2240
2240(defvar-local eglot--suggestion-overlay (make-overlay 0 0) 2241(defvar-local eglot--suggestion-overlay (make-overlay 0 0)
2241 "Overlay for `eglot-code-action-suggestion'.") 2242 "Overlay for `eglot-code-action-suggestion'.")
@@ -2316,11 +2317,8 @@ LSP Document version reported for DIAGNOSTICS (comparable to
2316 (cl-loop for (var . saved-binding) in eglot--saved-bindings 2317 (cl-loop for (var . saved-binding) in eglot--saved-bindings
2317 do (set (make-local-variable var) saved-binding)) 2318 do (set (make-local-variable var) saved-binding))
2318 (remove-function (local 'imenu-create-index-function) #'eglot-imenu) 2319 (remove-function (local 'imenu-create-index-function) #'eglot-imenu)
2319 (when eglot--flymake-report-fn 2320 (eglot--flymake-reset)
2320 (setq eglot--pulled-diagnostics nil 2321 (setq eglot--flymake-report-fn nil)
2321 eglot--pushed-diagnostics nil)
2322 (eglot--flymake-report)
2323 (setq eglot--flymake-report-fn nil))
2324 (run-hooks 'eglot-managed-mode-hook) 2322 (run-hooks 'eglot-managed-mode-hook)
2325 (let ((server eglot--cached-server)) 2323 (let ((server eglot--cached-server))
2326 (setq eglot--cached-server nil) 2324 (setq eglot--cached-server nil)
@@ -2373,8 +2371,6 @@ If it is activated, also signal textDocument/didOpen."
2373 ;; Called when `revert-buffer-in-progress-p' is t but 2371 ;; Called when `revert-buffer-in-progress-p' is t but
2374 ;; `revert-buffer-preserve-modes' is nil. 2372 ;; `revert-buffer-preserve-modes' is nil.
2375 (when (and buffer-file-name (eglot-current-server)) 2373 (when (and buffer-file-name (eglot-current-server))
2376 (setq eglot--pulled-diagnostics nil
2377 eglot--pushed-diagnostics nil)
2378 (eglot--managed-mode) 2374 (eglot--managed-mode)
2379 (eglot--signal-textDocument/didOpen) 2375 (eglot--signal-textDocument/didOpen)
2380 ;; Run user hook after 'textDocument/didOpen' so server knows 2376 ;; Run user hook after 'textDocument/didOpen' so server knows
@@ -2644,40 +2640,6 @@ still unanswered LSP requests to the server\n"))))
2644 "] "))) 2640 "] ")))
2645 2641
2646 2642
2647;;; Flymake customization
2648;;;
2649(put 'eglot-note 'flymake-category 'flymake-note)
2650(put 'eglot-warning 'flymake-category 'flymake-warning)
2651(put 'eglot-error 'flymake-category 'flymake-error)
2652
2653(defun eglot--flymake-diagnostics (beg &optional end)
2654 "Like `flymake-diagnostics', but for Eglot-specific diagnostics."
2655 (cl-loop for diag in (flymake-diagnostics beg end)
2656 for data = (flymake-diagnostic-data diag)
2657 for lsp-diag = (alist-get 'eglot-lsp-diag data)
2658 for version = (alist-get 'eglot--doc-version data)
2659 when (and lsp-diag (or (null version)
2660 (= version eglot--docver)))
2661 collect diag))
2662
2663(defun eglot--diag-to-lsp-diag (diag)
2664 (alist-get 'eglot-lsp-diag (flymake-diagnostic-data diag)))
2665
2666(defvar eglot-diagnostics-map
2667 (let ((map (make-sparse-keymap)))
2668 (define-key map [mouse-2] #'eglot-code-actions-at-mouse)
2669 (define-key map [left-margin mouse-1] #'eglot-code-actions-at-mouse)
2670 map)
2671 "Keymap active in Eglot-backed Flymake diagnostic overlays.")
2672
2673(cl-loop for i from 1
2674 for type in '(eglot-note eglot-warning eglot-error)
2675 do (put type 'flymake-overlay-control
2676 `((mouse-face . highlight)
2677 (priority . ,(+ 50 i))
2678 (keymap . ,eglot-diagnostics-map))))
2679
2680
2681;;; Protocol implementation (Requests, notifications, etc) 2643;;; Protocol implementation (Requests, notifications, etc)
2682;;; 2644;;;
2683(cl-defmethod eglot-handle-notification 2645(cl-defmethod eglot-handle-notification
@@ -2765,56 +2727,6 @@ Value is (TRUENAME . (:uri STR)), where STR is what is sent to the
2765server on textDocument/didOpen and similar calls. TRUENAME is the 2727server on textDocument/didOpen and similar calls. TRUENAME is the
2766expensive cached value of `file-truename'.") 2728expensive cached value of `file-truename'.")
2767 2729
2768(cl-defmethod eglot-handle-notification
2769 (server (_method (eql textDocument/publishDiagnostics))
2770 &key uri diagnostics version
2771 &allow-other-keys) ; FIXME: doesn't respect `eglot-strict-mode'
2772 "Handle notification publishDiagnostics."
2773 (cl-flet ((find-it (abspath)
2774 ;; `find-buffer-visiting' would be natural, but calls the
2775 ;; potentially slow `file-truename' (bug#70036).
2776 (cl-loop for b in (eglot--managed-buffers server)
2777 when (with-current-buffer b
2778 (equal (car eglot--TextDocumentIdentifier-cache)
2779 abspath))
2780 return b)))
2781 (if-let* ((path (expand-file-name (eglot-uri-to-path uri)))
2782 (buffer (find-it path)))
2783 (with-current-buffer buffer
2784 (cl-loop
2785 initially
2786 (if (and version (/= version eglot--docver))
2787 (cl-return))
2788 (setq
2789 ;; if no explicit version received, assume it's current.
2790 version eglot--docver
2791 flymake-list-only-diagnostics
2792 (assoc-delete-all path flymake-list-only-diagnostics))
2793 for diag-spec across diagnostics
2794 collect (eglot--flymake-make-diag diag-spec version)
2795 into diags
2796 finally
2797 (setq eglot--pushed-diagnostics (list diags version))
2798 (when (not (null flymake-no-changes-timeout ))
2799 ;; only add to current report if Flymake
2800 ;; starts on idle-timer (github#957)
2801 (eglot--flymake-report))))
2802 (cl-loop
2803 for diag-spec across diagnostics
2804 collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec
2805 (let* ((start (plist-get range :start))
2806 (line (1+ (plist-get start :line)))
2807 (char (1+ (plist-get start :character))))
2808 (flymake-make-diagnostic
2809 path (cons line char) nil
2810 (eglot--flymake-diag-type severity)
2811 (list source code message))))
2812 into diags
2813 finally
2814 (setq flymake-list-only-diagnostics
2815 (assoc-delete-all path flymake-list-only-diagnostics))
2816 (push (cons path diags) flymake-list-only-diagnostics)))))
2817
2818(cl-defun eglot--register-unregister (server things how) 2730(cl-defun eglot--register-unregister (server things how)
2819 "Helper for `registerCapability'. 2731 "Helper for `registerCapability'.
2820THINGS are either registrations or unregisterations (sic)." 2732THINGS are either registrations or unregisterations (sic)."
@@ -3155,6 +3067,7 @@ When called interactively, use the currently active server"
3155 (setq eglot--recent-changes nil 3067 (setq eglot--recent-changes nil
3156 eglot--docver 0 3068 eglot--docver 0
3157 eglot--TextDocumentIdentifier-cache nil) 3069 eglot--TextDocumentIdentifier-cache nil)
3070 (eglot--flymake-reset)
3158 (jsonrpc-notify 3071 (jsonrpc-notify
3159 (eglot--current-server-or-lose) 3072 (eglot--current-server-or-lose)
3160 :textDocument/didOpen `(:textDocument ,(eglot--TextDocumentItem)))) 3073 :textDocument/didOpen `(:textDocument ,(eglot--TextDocumentItem))))
@@ -3192,6 +3105,80 @@ When called interactively, use the currently active server"
3192 :text (buffer-substring-no-properties (point-min) (point-max)) 3105 :text (buffer-substring-no-properties (point-min) (point-max))
3193 :textDocument (eglot--TextDocumentIdentifier))))) 3106 :textDocument (eglot--TextDocumentIdentifier)))))
3194 3107
3108(defun eglot--find-buffer-visiting (server abspath)
3109 ;; `find-buffer-visiting' would be natural, but calls the
3110 ;; potentially slow `file-truename' (bug#70036).
3111 (cl-loop for b in (eglot--managed-buffers server)
3112 when (with-current-buffer b
3113 (equal (car eglot--TextDocumentIdentifier-cache)
3114 abspath))
3115 return b))
3116
3117
3118;;; Flymake integration
3119
3120(put 'eglot-note 'flymake-category 'flymake-note)
3121(put 'eglot-warning 'flymake-category 'flymake-warning)
3122(put 'eglot-error 'flymake-category 'flymake-error)
3123
3124(defvar eglot-diagnostics-map
3125 (let ((map (make-sparse-keymap)))
3126 (define-key map [mouse-2] #'eglot-code-actions-at-mouse)
3127 (define-key map [left-margin mouse-1] #'eglot-code-actions-at-mouse)
3128 map)
3129 "Keymap active in Eglot-backed Flymake diagnostic overlays.")
3130
3131(cl-loop for i from 1
3132 for type in '(eglot-note eglot-warning eglot-error)
3133 do (put type 'flymake-overlay-control
3134 `((mouse-face . highlight)
3135 (priority . ,(+ 50 i))
3136 (keymap . ,eglot-diagnostics-map))))
3137
3138(defun eglot--flymake-sniff-diagnostics (beg &optional end)
3139 "Like `flymake-diagnostics', but for Eglot-specific diagnostics."
3140 (cl-loop for diag in (flymake-diagnostics beg end)
3141 for data = (flymake-diagnostic-data diag)
3142 for lsp-diag = (alist-get 'eglot-lsp-diag data)
3143 for version = (alist-get 'eglot--doc-version data)
3144 when (and lsp-diag (or (null version)
3145 (= version eglot--docver)))
3146 collect diag))
3147
3148(cl-defmacro eglot--flymake-report-1 (diags mode &key (version 'eglot--docver) force)
3149 "Maybe convert, report and store the diagnostics objects DIAGS.
3150DIAGS is either a vector of LSP diagnostics or a list of Flymake
3151diagnostics. MODE can be `:stay' or `:clear' depending on whether we
3152want to accumulate or reset diagnostics in the buffer. VERSION is the
3153version the diagnostics pertain to."
3154 ;; JT@2026-01-10: criteria for "incremental" reports could be
3155 ;; tightened to e.g. check eglot--capf-session nillness, but we'd have
3156 ;; to schedule an after-session re-report, and that's way too complex
3157 `(when (and (or ,force flymake-no-changes-timeout)
3158 eglot--flymake-report-fn)
3159 (when (and ,diags (vectorp ,diags))
3160 (setf ,diags
3161 (cl-loop
3162 for d across ,diags
3163 collect (eglot--flymake-make-diag
3164 d
3165 ,version (eglot-range-region (plist-get d :range))))))
3166 (eglot--flymake-report-2 ,diags ,mode)))
3167
3168(cl-defmethod eglot-handle-notification
3169 (server (_method (eql textDocument/publishDiagnostics))
3170 &key uri diagnostics version
3171 &allow-other-keys)
3172 "Handle notification publishDiagnostics."
3173 (eglot--flymake-handle-push
3174 server uri diagnostics version
3175 (lambda (diags)
3176 (setq eglot--pushed-diagnostics (list diags eglot--docver))
3177 (when (not (null flymake-no-changes-timeout ))
3178 ;; only add to current report if Flymake
3179 ;; starts on idle-timer (github#957)
3180 (eglot--flymake-report-push+pulled)))))
3181
3195(defun eglot--flymake-diag-type (severity) 3182(defun eglot--flymake-diag-type (severity)
3196 "Convert LSP diagnostic SEVERITY to Eglot/Flymake diagnostic type." 3183 "Convert LSP diagnostic SEVERITY to Eglot/Flymake diagnostic type."
3197 (cond ((null severity) 'eglot-error) 3184 (cond ((null severity) 'eglot-error)
@@ -3199,13 +3186,14 @@ When called interactively, use the currently active server"
3199 ((= severity 2) 'eglot-warning) 3186 ((= severity 2) 'eglot-warning)
3200 (t 'eglot-note))) 3187 (t 'eglot-note)))
3201 3188
3202(defun eglot--flymake-make-diag (diag-spec version) 3189(defun eglot--flymake-make-diag (diag-spec version region)
3203 "Convert LSP diagnostic DIAG-SPEC to Flymake diagnostic. 3190 "Convert LSP diagnostic DIAG-SPEC to Flymake diagnostic.
3204VERSION is the document version number." 3191REGION is the (BEG . END) region the diagnostics pertina to. VERSION is
3192the document version number."
3205 (eglot--dbind ((Diagnostic) range code message severity source tags) 3193 (eglot--dbind ((Diagnostic) range code message severity source tags)
3206 diag-spec 3194 diag-spec
3207 (pcase-let 3195 (pcase-let
3208 ((`(,beg . ,end) (eglot-range-region range))) 3196 ((`(,beg . ,end) region))
3209 ;; Fallback to `flymake-diag-region' if server botched the range 3197 ;; Fallback to `flymake-diag-region' if server botched the range
3210 (when (= beg end) 3198 (when (= beg end)
3211 (if-let* ((st (plist-get range :start)) 3199 (if-let* ((st (plist-get range :start))
@@ -3247,11 +3235,48 @@ may be called multiple times (respecting the protocol of
3247 ((eglot-server-capable :diagnosticProvider) 3235 ((eglot-server-capable :diagnosticProvider)
3248 (eglot--flymake-pull)) 3236 (eglot--flymake-pull))
3249 ;; Otherwise push whatever we might have, and wait for 3237 ;; Otherwise push whatever we might have, and wait for
3250 ;; `textDocument/publishDiagnostics'. 3238 ;; further `textDocument/publishDiagnostics'.
3251 (t (eglot--flymake-report)))) 3239 (t (eglot--flymake-report-push+pulled :force t))))
3252 (t 3240 (t
3253 (funcall report-fn nil)))) 3241 (funcall report-fn nil))))
3254 3242
3243(cl-defun eglot--flymake-handle-push (server uri diagnostics version then)
3244 "Handle a diagnostics \"push\" from SERVER for document URI.
3245DIAGNOSTICS is a list of LSP diagnostic objects. VERSION is the
3246LSP-reported version comparable to `eglot--docver' for which these
3247objects presumably pertain. If diagnostics are thought to belong to
3248`eglot--docver' THEN is a unary function taking DIAGNOSTICS and tasked
3249to eventually report the corresponding Flymake conversions of each
3250object. The originator of this \"push\" is usually either regular
3251`textDocument/publishDiagnostics' or an experimental
3252`$/streamDiagnostics' notification."
3253 (if-let* ((path (expand-file-name (eglot-uri-to-path uri)))
3254 (buffer (eglot--find-buffer-visiting server path)))
3255 (with-current-buffer buffer
3256 (if (and version (/= version eglot--docver))
3257 (cl-return-from eglot--flymake-handle-push))
3258 (setq
3259 ;; if no explicit version received, assume it's current.
3260 version eglot--docver
3261 flymake-list-only-diagnostics
3262 (assoc-delete-all path flymake-list-only-diagnostics))
3263 (funcall then diagnostics))
3264 (cl-loop
3265 for diag-spec across diagnostics
3266 collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec
3267 (let* ((start (plist-get range :start))
3268 (line (1+ (plist-get start :line)))
3269 (char (1+ (plist-get start :character))))
3270 (flymake-make-diagnostic
3271 path (cons line char) nil
3272 (eglot--flymake-diag-type severity)
3273 (list source code message))))
3274 into diags
3275 finally
3276 (setq flymake-list-only-diagnostics
3277 (assoc-delete-all path flymake-list-only-diagnostics))
3278 (push (cons path diags) flymake-list-only-diagnostics))))
3279
3255(cl-defun eglot--flymake-pull (&aux (server (eglot--current-server-or-lose)) 3280(cl-defun eglot--flymake-pull (&aux (server (eglot--current-server-or-lose))
3256 (origin (current-buffer))) 3281 (origin (current-buffer)))
3257 "Pull diagnostics from server, for all managed buffers. 3282 "Pull diagnostics from server, for all managed buffers.
@@ -3260,7 +3285,7 @@ When response arrives call registered `eglot--flymake-report-fn'."
3260 ((pull-for (buf &optional then) 3285 ((pull-for (buf &optional then)
3261 (with-current-buffer buf 3286 (with-current-buffer buf
3262 (let ((version eglot--docver) 3287 (let ((version eglot--docver)
3263 (prev-result-id (cadr eglot--pulled-diagnostics))) 3288 (prev-result-id (caddr eglot--pulled-diagnostics)))
3264 (eglot--async-request 3289 (eglot--async-request
3265 server 3290 server
3266 :textDocument/diagnostic 3291 :textDocument/diagnostic
@@ -3274,14 +3299,11 @@ When response arrives call registered `eglot--flymake-report-fn'."
3274 (pcase kind 3299 (pcase kind
3275 ("full" 3300 ("full"
3276 (setq eglot--pulled-diagnostics 3301 (setq eglot--pulled-diagnostics
3277 (list 3302 (list items version resultId))
3278 (cl-loop 3303 (eglot--flymake-report-push+pulled :force t))
3279 for spec across items
3280 collect (eglot--flymake-make-diag spec version))
3281 resultId))
3282 (eglot--flymake-report))
3283 ("unchanged" 3304 ("unchanged"
3284 (when (eq buf origin) (eglot--flymake-report 'void))))) 3305 (when (eq buf origin)
3306 (eglot--flymake-report-1 nil :stay :force t)))))
3285 (when then (funcall then))) 3307 (when then (funcall then)))
3286 :hint :textDocument/diagnostic))))) 3308 :hint :textDocument/diagnostic)))))
3287 ;; JT@2025-12-15: No known server yet supports "relatedDocuments" so 3309 ;; JT@2025-12-15: No known server yet supports "relatedDocuments" so
@@ -3294,38 +3316,47 @@ When response arrives call registered `eglot--flymake-report-fn'."
3294 (mapc #'pull-for 3316 (mapc #'pull-for
3295 (remove origin (eglot--managed-buffers server)))))))) 3317 (remove origin (eglot--managed-buffers server))))))))
3296 3318
3297(cl-defun eglot--flymake-report 3319(defun eglot--flymake-reset ()
3298 (&optional keep 3320 (setq eglot--pulled-diagnostics nil
3321 eglot--pushed-diagnostics nil)
3322 (when eglot--flymake-report-fn
3323 (eglot--flymake-report-1 nil :clear :force t)))
3324
3325(cl-defun eglot--flymake-report-2 (diags mode)
3326 "Really report the Flymake diagnostics objects DIAGS.
3327MODE is like `eglot--flymake-report-1'."
3328 (apply eglot--flymake-report-fn
3329 diags
3330 (cond ((eq mode :clear)
3331 `(:region ,(cons (point-min) (point-max))))
3332 ((eq mode :stay)
3333 `(:region ,(cons (point-min) (point-min)))))))
3334
3335(cl-defun eglot--flymake-report-push+pulled
3336 (&key force
3299 &aux 3337 &aux
3300 (pushed-docver (cadr eglot--pushed-diagnostics)) 3338 (pushed-docver (cadr eglot--pushed-diagnostics))
3301 (pushed-outdated-p (and pushed-docver (< pushed-docver eglot--docver)))) 3339 (pushed-outdated-p (and pushed-docver (< pushed-docver eglot--docver))))
3302 "Push previously collected diagnostics to `eglot--flymake-report-fn'. 3340 "Push previously collected diagnostics to `eglot--flymake-report-fn'.
3303If KEEP, knowingly push a dummy do-nothing update." 3341If KEEP, knowingly push a dummy do-nothing update."
3304 (unless eglot--flymake-report-fn
3305 ;; Occasionally called from contexts where report-fn not setup, such
3306 ;; as a `didOpen''ed but yet undisplayed buffer.
3307 (cl-return-from eglot--flymake-report))
3308 (eglot--widening 3342 (eglot--widening
3309 (if (or keep (and (null eglot--pulled-diagnostics) pushed-outdated-p)) 3343 (if (and (null eglot--pulled-diagnostics) pushed-outdated-p)
3310 ;; Here, we don't have anything interesting to give to 3344 ;; Here, we don't have anything interesting to give to Flymake.
3311 ;; Flymake. Either a textDocument/diagnostics response 3345 ;; Either a textDocument/diagnostics response specifically told
3312 ;; specifically told use that nothing changed, or 3346 ;; use that nothing changed, or `flymake-start' kicked in before
3313 ;; `flymake-start' kicked in before server had a chance to 3347 ;; server had a chance to push something. We just want to keep
3314 ;; push something. We just want to keep whatever diagnostics 3348 ;; whatever diagnostics it has annotated in the buffer and and
3315 ;; it has annotated in the buffer but as a nice-to-have, we 3349 ;; clear a possible "Wait" state.
3316 ;; want to signal we're alive and clear a possible "Wait" 3350 (eglot--flymake-report-2 nil :stay)
3317 ;; state. We hackingly achieve this by reporting an empty 3351 (cl-macrolet ((report (x m)
3318 ;; list and making sure it pertains to a 0-length region. 3352 `(eglot--flymake-report-1
3319 (funcall eglot--flymake-report-fn nil 3353 (car ,x) ,m :force force)))
3320 :region (cons (point-min) (point-min))) 3354 (report eglot--pulled-diagnostics :clear)
3321 ;; Using :region keyword always forces Flymake to delete them 3355 (unless pushed-outdated-p
3322 ;; (github#159). 3356 (report eglot--pushed-diagnostics :stay))))))
3323 (funcall eglot--flymake-report-fn
3324 (append (car eglot--pulled-diagnostics)
3325 (unless pushed-outdated-p
3326 (car eglot--pushed-diagnostics)))
3327 :region (cons (point-min) (point-max))))))
3328 3357
3358
3359;;; Xref integration
3329(defun eglot-xref-backend () "Eglot xref backend." 'eglot) 3360(defun eglot-xref-backend () "Eglot xref backend." 'eglot)
3330 3361
3331(defvar eglot--temp-location-buffers (make-hash-table :test #'equal) 3362(defvar eglot--temp-location-buffers (make-hash-table :test #'equal)
@@ -3523,6 +3554,8 @@ If BUFFER, switch to it before."
3523 :workspace/symbol 3554 :workspace/symbol
3524 `(:query ,pattern)))))) 3555 `(:query ,pattern))))))
3525 3556
3557
3558;;; Eglot interactive commands and helpers
3526(defun eglot-format-buffer () 3559(defun eglot-format-buffer ()
3527 "Format contents of current buffer." 3560 "Format contents of current buffer."
3528 (interactive) 3561 (interactive)
@@ -3565,12 +3598,15 @@ for which LSP on-type-formatting should be requested."
3565 nil 3598 nil
3566 on-type-format))) 3599 on-type-format)))
3567 3600
3601
3602
3603;;; Completion
3568(defvar eglot-cache-session-completions t 3604(defvar eglot-cache-session-completions t
3569 "If non-nil Eglot caches data during completion sessions.") 3605 "If non-nil Eglot caches data during completion sessions.")
3570 3606
3571(defvar eglot--capf-session :none "A cache used by `eglot-completion-at-point'.") 3607(defvar eglot--capf-session :none "A cache used by `eglot-completion-at-point'.")
3572 3608
3573(defun eglot--capf-session-flush (&optional _) (setq eglot--capf-session :none)) 3609(defun eglot--capf-session-flush (&optional _) (setq eglot--capf-session nil))
3574 3610
3575(defun eglot--dumb-flex (pat comp ignorecase) 3611(defun eglot--dumb-flex (pat comp ignorecase)
3576 "Return destructively fontified COMP iff PAT matches it." 3612 "Return destructively fontified COMP iff PAT matches it."
@@ -3826,6 +3862,8 @@ for which LSP on-type-formatting should be requested."
3826 (eglot--apply-text-edits additionalTextEdits))) 3862 (eglot--apply-text-edits additionalTextEdits)))
3827 (eglot--signal-textDocument/didChange))))))))) 3863 (eglot--signal-textDocument/didChange)))))))))
3828 3864
3865
3866;;; Eldoc integration
3829(defun eglot--hover-info (contents &optional _range) 3867(defun eglot--hover-info (contents &optional _range)
3830 (mapconcat #'eglot--format-markup 3868 (mapconcat #'eglot--format-markup
3831 (if (vectorp contents) contents (list contents)) "\n")) 3869 (if (vectorp contents) contents (list contents)) "\n"))
@@ -3971,6 +4009,8 @@ for which LSP on-type-formatting should be requested."
3971 :hint :textDocument/documentHighlight) 4009 :hint :textDocument/documentHighlight)
3972 nil))) 4010 nil)))
3973 4011
4012
4013;;; Imenu integration
3974(defun eglot--imenu-SymbolInformation (res) 4014(defun eglot--imenu-SymbolInformation (res)
3975 "Compute `imenu--index-alist' for RES vector of SymbolInformation." 4015 "Compute `imenu--index-alist' for RES vector of SymbolInformation."
3976 (mapcar 4016 (mapcar
@@ -4032,6 +4072,8 @@ Returns a list as described in docstring of `imenu--index-alist'."
4032 (((SymbolInformation)) (eglot--imenu-SymbolInformation res)) 4072 (((SymbolInformation)) (eglot--imenu-SymbolInformation res))
4033 (((DocumentSymbol)) (eglot--imenu-DocumentSymbol res)))))) 4073 (((DocumentSymbol)) (eglot--imenu-DocumentSymbol res))))))
4034 4074
4075
4076;;; Code actions and rename
4035(cl-defun eglot--apply-text-edits (edits &optional version silent) 4077(cl-defun eglot--apply-text-edits (edits &optional version silent)
4036 "Apply EDITS for current buffer if at VERSION, or if it's nil. 4078 "Apply EDITS for current buffer if at VERSION, or if it's nil.
4037If SILENT, don't echo progress in mode-line." 4079If SILENT, don't echo progress in mode-line."
@@ -4187,7 +4229,7 @@ edit proposed by the server."
4187 "Calculate appropriate bounds depending on region and point." 4229 "Calculate appropriate bounds depending on region and point."
4188 (let (diags boftap) 4230 (let (diags boftap)
4189 (cond ((use-region-p) `(,(region-beginning) ,(region-end))) 4231 (cond ((use-region-p) `(,(region-beginning) ,(region-end)))
4190 ((setq diags (eglot--flymake-diagnostics (point))) 4232 ((setq diags (eglot--flymake-sniff-diagnostics (point)))
4191 (cl-loop for d in diags 4233 (cl-loop for d in diags
4192 minimizing (flymake-diagnostic-beg d) into beg 4234 minimizing (flymake-diagnostic-beg d) into beg
4193 maximizing (flymake-diagnostic-end d) into end 4235 maximizing (flymake-diagnostic-end d) into end
@@ -4203,8 +4245,9 @@ edit proposed by the server."
4203 :range (eglot-region-range beg end) 4245 :range (eglot-region-range beg end)
4204 :context 4246 :context
4205 `(:diagnostics 4247 `(:diagnostics
4206 [,@(mapcar #'eglot--diag-to-lsp-diag 4248 [,@(mapcar (lambda (x)
4207 (eglot--flymake-diagnostics beg end))] 4249 (alist-get 'eglot-lsp-diag (flymake-diagnostic-data x)))
4250 (eglot--flymake-sniff-diagnostics beg end))]
4208 ,@(when only `(:only [,only])) 4251 ,@(when only `(:only [,only]))
4209 ,@(when triggerKind `(:triggerKind ,triggerKind))))) 4252 ,@(when triggerKind `(:triggerKind ,triggerKind)))))
4210 4253