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.
This commit is contained in:
João Távora 2023-09-01 22:45:51 +01:00
parent 7d60d1652f
commit 82f45b9580

View file

@ -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))))))))