From 7263d821c2d217c5748965aad57df8c0311f7e95 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sun, 11 Jun 2023 23:18:12 +0200 Subject: [PATCH 1/2] Allow for LSP edits to be generated as diffs * doc/misc/eglot.texi (Eglot Variables): Document the new behaviour. * lisp/progmodes/eglot.el (eglot-confirm-server-initiated-edits): Add new value 'diff'. (eglot-execute): Check if 'eglot-confirm-server-initiated-edits' is set to 'diff'. (eglot--apply-workspace-edit): Respect the new 'diff' value. (eglot-rename): Pass 'eglot-confirm-server-initiated-edits' instead of just the value 'current-prefix-arg'. (Bug#60338) --- doc/misc/eglot.texi | 6 ++-- lisp/progmodes/eglot.el | 62 +++++++++++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/doc/misc/eglot.texi b/doc/misc/eglot.texi index 962e6c914ce..305feda43a6 100644 --- a/doc/misc/eglot.texi +++ b/doc/misc/eglot.texi @@ -833,9 +833,11 @@ Eglot Variables @item eglot-confirm-server-initiated-edits Various Eglot commands and code actions result in the language server sending editing commands to Emacs. If this option's value is -non-@code{nil} (the default), Eglot will ask for confirmation before +@code{confirm} (the default), Eglot will ask for confirmation before performing edits initiated by the server or edits whose scope affects -buffers other than the one where the user initiated the request. +buffers other than the one where the user initiated the request. If +set to @code{diff}, Eglot will generate and display a diff to display +the changes. @item eglot-ignored-server-capabilities This variable's value is a list of language server capabilities that diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 7d5d786dea3..9c9c3152472 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -108,6 +108,7 @@ (require 'filenotify) (require 'ert) (require 'text-property-search nil t) +(require 'diff) ;; These dependencies are also GNU ELPA core packages. Because of ;; bug#62576, since there is a risk that M-x package-install, despite @@ -386,9 +387,14 @@ eglot-events-buffer-size :type '(choice (const :tag "No limit" nil) (integer :tag "Number of characters"))) (defcustom eglot-confirm-server-initiated-edits 'confirm - "Non-nil if server-initiated edits should be confirmed with user." + "Control how server edits are applied. +If set to `confirm', a prompt is presented to confirm server +changes. If set to `diff', a buffer will pop up with changes +that can be applied manually. If set to nil, modifications are +made automatically." :type '(choice (const :tag "Don't show confirmation prompt" nil) + (const :tag "Present as diffs in a buffer" diff) (const :tag "Show confirmation prompt" confirm))) (defcustom eglot-extend-to-xref nil @@ -740,7 +746,8 @@ eglot-execute (eglot--dcase action (((Command)) (eglot--request server :workspace/executeCommand action)) (((CodeAction) edit command) - (when edit (eglot--apply-workspace-edit edit)) + (when edit + (eglot--apply-workspace-edit edit (eq eglot-confirm-server-initiated-edits t))) (when command (eglot--request server :workspace/executeCommand command)))))) (cl-defgeneric eglot-initialization-options (server) @@ -3408,18 +3415,43 @@ eglot--apply-workspace-edit ;; prefer documentChanges over changes. (cl-loop for (uri edits) on changes by #'cddr do (push (list (eglot--uri-to-path uri) edits) prepared))) - (if (or confirm - (cl-notevery #'find-buffer-visiting - (mapcar #'car prepared))) - (unless (y-or-n-p + (cond + ((eq confirm 'diff) + (with-current-buffer (get-buffer-create " *Changes*") + (buffer-disable-undo (current-buffer)) + (let ((buffer-read-only t)) + (diff-mode)) + (let ((inhibit-read-only t) + (target (current-buffer))) + (erase-buffer) + (pcase-dolist (`(,path ,edits ,_) prepared) + (with-temp-buffer + (let ((diff (current-buffer))) + (with-temp-buffer + (insert-file-contents path) + (eglot--apply-text-edits edits) + (diff-no-select path (current-buffer) + nil t diff)) + (with-current-buffer target + (insert-buffer-substring diff)))))) + (setq-local buffer-read-only t) + (buffer-enable-undo (current-buffer)) + (goto-char (point-min)) + (pop-to-buffer (current-buffer)) + (font-lock-ensure))) + ((and (or (eq confirm 'confirm) + (cl-find-if-not #'find-buffer-visiting prepared + :key #'car)) + ;; Should the list of buffers be popped up temporarily in + ;; a buffer instead of resizing the minibufffer? + (not (y-or-n-p (format "[eglot] Server wants to edit:\n %s\n Proceed? " - (mapconcat #'identity (mapcar #'car prepared) "\n "))) - (jsonrpc-error "User canceled server edit"))) - (cl-loop for edit in prepared - for (path edits version) = edit - do (with-current-buffer (find-file-noselect path) - (eglot--apply-text-edits edits version)) - finally (eldoc) (eglot--message "Edit successful!"))))) + (mapconcat #'identity (mapcar #'car prepared) "\n "))))) + (jsonrpc-error "User canceled server edit")) + ((cl-loop for (path edits version) in prepared + do (with-current-buffer (find-file-noselect path) + (eglot--apply-text-edits edits version)) + finally (eldoc) (eglot--message "Edit successful!"))))))) (defun eglot-rename (newname) "Rename the current symbol to NEWNAME." @@ -3434,7 +3466,7 @@ eglot-rename (eglot--request (eglot--current-server-or-lose) :textDocument/rename `(,@(eglot--TextDocumentPositionParams) :newName ,newname)) - current-prefix-arg)) + (and current-prefix-arg eglot-confirm-server-initiated-edits))) (defun eglot--region-bounds () "Region bounds if active, else bounds of things at point." -- 2.39.2