diff options
| -rw-r--r-- | lisp/progmodes/eglot.el | 321 |
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. |
| 2231 | DIAGNOSTICS is a list of Flymake diagnostics objects. RESULT-ID | 2231 | DIAGNOSTICS is a sequence of LSP or Flymake diagnostics objects. |
| 2232 | identifies this diagnostic result as is used for incremental updates.") | 2232 | RESULT-ID identifies this diagnostic result as is used for incremental |
| 2233 | updates.") | ||
| 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. |
| 2236 | DIAGNOSTICS is a list of Flymake diagnostics objects. VERSION is the | 2237 | DIAGNOSTICS is a sequence of LSP or Flymake diagnostics objects. |
| 2237 | LSP Document version reported for DIAGNOSTICS (comparable to | 2238 | VERSION is the LSP Document version reported for DIAGNOSTICS (comparable |
| 2238 | `eglot--docver') or nil if server didn't bother.") | 2239 | to `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 | |||
| 2765 | server on textDocument/didOpen and similar calls. TRUENAME is the | 2727 | server on textDocument/didOpen and similar calls. TRUENAME is the |
| 2766 | expensive cached value of `file-truename'.") | 2728 | expensive 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'. |
| 2820 | THINGS are either registrations or unregisterations (sic)." | 2732 | THINGS 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. | ||
| 3150 | DIAGS is either a vector of LSP diagnostics or a list of Flymake | ||
| 3151 | diagnostics. MODE can be `:stay' or `:clear' depending on whether we | ||
| 3152 | want to accumulate or reset diagnostics in the buffer. VERSION is the | ||
| 3153 | version 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. |
| 3204 | VERSION is the document version number." | 3191 | REGION is the (BEG . END) region the diagnostics pertina to. VERSION is |
| 3192 | the 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. | ||
| 3245 | DIAGNOSTICS is a list of LSP diagnostic objects. VERSION is the | ||
| 3246 | LSP-reported version comparable to `eglot--docver' for which these | ||
| 3247 | objects presumably pertain. If diagnostics are thought to belong to | ||
| 3248 | `eglot--docver' THEN is a unary function taking DIAGNOSTICS and tasked | ||
| 3249 | to eventually report the corresponding Flymake conversions of each | ||
| 3250 | object. 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. | ||
| 3327 | MODE 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'. |
| 3303 | If KEEP, knowingly push a dummy do-nothing update." | 3341 | If 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. |
| 4037 | If SILENT, don't echo progress in mode-line." | 4079 | If 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 | ||