Subject: bug#63372: [PATCH] Add variable: eglot-apply-text-edits-function

From: Felician Nemeth <[email-masked]>
Date: Mon, 08 May 2023 16:54:38 +0200

Felician Nemeth <[email-masked]> writes:

> I've attached a patch with my first attempt at this.  João, what do you
> think of this approach?

I forgot to add that alternatively SnippetTextEdit support can be added
to Eglot as well.  The patch without the boring parts would look like
the following.

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index eb79a8d2d3..0a4738b3b9 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -3350,14 +3350,13 @@ eglot-imenu
 
 (cl-defun eglot--apply-text-edits (edits &optional version)
   "Apply EDITS for current buffer if at VERSION, or if it's nil."
+  ;; This is quite rust-analyzer specific.  It assumes there is at
+  ;; most one meaningful SnippetTextEdit and that can be identified by
+  ;; searching for "$0".
   (unless edits (cl-return-from eglot--apply-text-edits))
   (unless (or (not version) (equal version eglot--versioned-identifier))
     (jsonrpc-error "Edits on `%s' require version %d, you have %d"
                    (current-buffer) version eglot--versioned-identifier))
   (atomic-change-group
     (let* ((change-group (prepare-change-group))
            (howmany (length edits))
@@ -3366,7 +3365,7 @@ eglot--apply-lsp-text-edits
                               howmany (current-buffer))
                       0 howmany))
-           (done 0))
-      (mapc (pcase-lambda (`(,newText ,beg . ,end))
+           (done 0)
+           snippet snippet-beg snippet-end)
+      (mapc (pcase-lambda (`(,newText ,insertTextFormat (,beg . ,end)))
               (let ((source (current-buffer)))
                 (with-temp-buffer
                   (insert newText)
@@ -3375,11 +3374,30 @@ eglot--apply-lsp-text-edits
                       (save-excursion
                         (save-restriction
                           (narrow-to-region beg end)
-                          (replace-buffer-contents temp)))
+                          (replace-buffer-contents temp))
+                                                  (when (and (eql insertTextFormat 2)
+                                     (string-match "\\$\\(0\\|{0[^}]*}\\)"
+                                                   newText))
+                            ;; "At the moment, rust-analyzer
+                            ;; guarantees that only a single edit will
+                            ;; have InsertTextFormat.Snippet.", but:
+                            ;; https://github.com/rust-analyzer/rust-analyzer/issues/11006
+                            ;; Every one of them has insertTextFormat
+                            ;; = 2, and there's no easy, reliable way
+                            ;; to tell, which one contains a real
+                            ;; snippet. RA's own .ts implementation
+                            ;; uses the regexp above.
+                            (setq snippet newText)
+                            (setq snippet-beg (point-min-marker))
+                            (setq snippet-end (point-max-marker))))
                       (eglot--reporter-update reporter (cl-incf done)))))))
-            (mapcar (eglot--lambda ((TextEdit) range newText)
-                      (cons newText (eglot--range-region range 'markers)))
+            (mapcar (eglot--lambda ((SnippetTextEdit) range newText insertTextFormat)
+                      (list newText insertTextFormat (eglot--range-region range 'markers)))
                     (reverse edits)))
+      (when snippet
+        (goto-char snippet-beg)
+        (delete-region snippet-beg snippet-end)
+        (funcall (eglot--snippet-expansion-fn) snippet))
       (undo-amalgamate-change-group change-group)
       (progress-reporter-done reporter))))