From 82f45b958063e1128f183f4a54628428408910dc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Fri, 1 Sep 2023 22:45:51 +0100 Subject: [PATCH] Eglot: handle unsaved buffers in new eglot--propose-changes-as-diff * lisp/progmodes/eglot.el (eglot--confirm-server-edits): Add docstring. (eglot--propose-changes-as-diff): Rework. Handle unsaved buffers. (eglot--apply-workspace-edit): Rework. --- lisp/progmodes/eglot.el | 62 ++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 8d95019c3ed..810c6cb4638 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -3459,6 +3459,10 @@ If SILENT, don't echo progress in mode-line." (progress-reporter-done reporter))))) (defun eglot--confirm-server-edits (origin _prepared) + "Helper for `eglot--apply-workspace-edit. +ORIGIN is a symbol designating a command. Reads the +`eglot-confirm-server-edits' user option and returns a symbol +like `diff', `summary' or nil." (let (v) (cond ((symbolp eglot-confirm-server-edits) eglot-confirm-server-edits) ((setq v (assoc origin eglot-confirm-server-edits)) (cdr v)) @@ -3466,7 +3470,9 @@ If SILENT, don't echo progress in mode-line." (defun eglot--propose-changes-as-diff (prepared) "Helper for `eglot--apply-workspace-edit'. -PREPARED is a list ((FILENAME EDITS VERSION)...)." +Goal is to popup a `diff-mode' buffer containing all the changes +of PREPARED, ready to apply with C-c C-a. PREPARED is a +list ((FILENAME EDITS VERSION)...)." (with-current-buffer (get-buffer-create "*EGLOT proposed server changes*") (buffer-disable-undo (current-buffer)) (let ((buffer-read-only t)) @@ -3476,12 +3482,30 @@ PREPARED is a list ((FILENAME EDITS VERSION)...)." (erase-buffer) (pcase-dolist (`(,path ,edits ,_) prepared) (with-temp-buffer - (let ((diff (current-buffer))) + (let* ((diff (current-buffer)) + (existing-buf (find-buffer-visiting path)) + (existing-buf-label (prin1-to-string existing-buf))) + ;; `existing-buf' might be an unsaved buffer, so we do + ;; this complicated little dance to diff it with a + ;; temporary file with the proposed changes applied. (with-temp-buffer - (insert-file-contents path) - (eglot--apply-text-edits edits) - (diff-no-select path (current-buffer) - nil t diff)) + (if existing-buf + (let ((temp (current-buffer))) + (with-current-buffer existing-buf + (copy-to-buffer temp (point-min) (point-max)))) + (insert-file-contents path)) + (eglot--apply-text-edits edits nil t) + (diff-no-select (or existing-buf path) (current-buffer) + nil t diff) + (when existing-buf + ;; Here we have to pretend the label of the unsaved + ;; buffer is the actual file, just so that we can + ;; diff-apply without troubles. If there's a better + ;; way, it probably involves changes to `diff.el'. + (with-current-buffer diff + (goto-char (point-min)) + (while (search-forward existing-buf-label nil t) + (replace-match (buffer-file-name existing-buf)))))) (with-current-buffer target (insert-buffer-substring diff)))))) (setq-local buffer-read-only t) @@ -3491,7 +3515,7 @@ PREPARED is a list ((FILENAME EDITS VERSION)...)." (font-lock-ensure))) (defun eglot--apply-workspace-edit (wedit origin) - "Apply the workspace edit WEDIT. + "Apply (or offer to apply) the workspace edit WEDIT. ORIGIN is a symbol designating the command that originated this edit proposed by the server." (eglot--dbind ((WorkspaceEdit) changes documentChanges) wedit @@ -3510,16 +3534,15 @@ edit proposed by the server." (cl-flet ((notevery-visited-p () (cl-notevery #'find-buffer-visiting (mapcar #'car prepared))) - (prompt () - (unless (y-or-n-p - (format "[eglot] Server wants to edit:\n%sProceed? " - (cl-loop - for (f eds _) in prepared - concat (format - " %s (%d change%s)\n" - f (length eds) - (if (> (length eds) 1) "s" ""))))) - (jsonrpc-error "User canceled server edit"))) + (accept-p () + (y-or-n-p + (format "[eglot] Server wants to edit:\n%sProceed? " + (cl-loop + for (f eds _) in prepared + concat (format + " %s (%d change%s)\n" + f (length eds) + (if (> (length eds) 1) "s" "")))))) (apply () (cl-loop for edit in prepared for (path edits version) = edit @@ -3531,10 +3554,9 @@ edit proposed by the server." ((or (eq decision 'diff) (and (eq decision 'maybe-diff) (notevery-visited-p))) (eglot--propose-changes-as-diff prepared)) - ((or (eq decision 'summary) + ((or (memq decision '(t summary)) (and (eq decision 'maybe-summary) (notevery-visited-p))) - (prompt) - (apply)) + (when (accept-p) (apply))) (t (apply)))))))) -- 2.39.2