GNU bug report logs - #63372
[PATCH] Add variable: eglot-apply-text-edits-function

Previous Next

Package: emacs;

Reported by: Felician Nemeth <felician.nemeth <at> gmail.com>

Date: Mon, 8 May 2023 14:30:02 UTC

Severity: normal

Tags: patch

To reply to this bug, email your comments to 63372 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to joaotavora <at> gmail.com, eliz <at> gnu.org, bug-gnu-emacs <at> gnu.org:
bug#63372; Package emacs. (Mon, 08 May 2023 14:30:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Felician Nemeth <felician.nemeth <at> gmail.com>:
New bug report received and forwarded. Copy sent to joaotavora <at> gmail.com, eliz <at> gnu.org, bug-gnu-emacs <at> gnu.org. (Mon, 08 May 2023 14:30:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Felician Nemeth <felician.nemeth <at> gmail.com>
To: bug-gnu-emacs <at> gnu.org
Subject: [PATCH] Add variable: eglot-apply-text-edits-function
Date: Mon, 08 May 2023 16:28:56 +0200
[Message part 1 (text/plain, inline)]
In https://lists.gnu.org/archive/html/emacs-devel/2023-05/msg00173.html
Eli Zaretskii <eliz <at> gnu.org> writes:

>> From: Felician Nemeth <felician.nemeth <at> gmail.com>
>> Cc: joaotavora <at> gmail.com,  emacs-devel <at> gnu.org
>> Date: Sat, 06 May 2023 17:17:14 +0200
>> 
>> 3. Eglot uses the eglot--apply-text-edits defun to apply server
>>    initiated edits.  There is an extension that allows the server to
>>    send the edits in a different format (snippet-text-edits).
>> 
>>    Eglot-x puts an advise on eglot--apply-text-edits to check the format
>>    of the edits and act accordingly.
>> 
>>    I don't know how to avoid this advice.
>
> Some hook or function variable, perhaps?  If they don't exist, perhaps
> they could be added?

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

(Independently of this issue, maybe a configurable
apply-workspace-edit-function would be useful as well.  One alternative
implementation of the current eglot--apply-workspace-edit could be to
apply all edits without asking for confirmation and then show a
`vc-diff'-like interface allowing the user to revert/accept all of the
changes with a single keystroke.)

Thank you.

[0001-Add-variable-eglot-apply-text-edits-function.patch (text/x-diff, attachment)]

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#63372; Package emacs. (Mon, 08 May 2023 14:55:02 GMT) Full text and rfc822 format available.

Message #8 received at 63372 <at> debbugs.gnu.org (full text, mbox):

From: Felician Nemeth <felician.nemeth <at> gmail.com>
To: 63372 <at> debbugs.gnu.org
Cc: Eli Zaretskii <eliz <at> gnu.org>, joaotavora <at> gmail.com
Subject: Re: bug#63372: [PATCH] Add variable: eglot-apply-text-edits-function
Date: Mon, 08 May 2023 16:54:38 +0200
Felician Nemeth <felician.nemeth <at> gmail.com> 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))))




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#63372; Package emacs. (Mon, 08 May 2023 16:34:02 GMT) Full text and rfc822 format available.

Message #11 received at 63372 <at> debbugs.gnu.org (full text, mbox):

From: João Távora <joaotavora <at> gmail.com>
To: Felician Nemeth <felician.nemeth <at> gmail.com>
Cc: 63372 <at> debbugs.gnu.org, Eli Zaretskii <eliz <at> gnu.org>
Subject: Re: bug#63372: [PATCH] Add variable: eglot-apply-text-edits-function
Date: Mon, 8 May 2023 17:33:06 +0100
On Mon, May 8, 2023 at 3:30 PM Felician Nemeth
<felician.nemeth <at> gmail.com> wrote:
>
> In https://lists.gnu.org/archive/html/emacs-devel/2023-05/msg00173.html
> Eli Zaretskii <eliz <at> gnu.org> writes:
>
> >> From: Felician Nemeth <felician.nemeth <at> gmail.com>
> >> Cc: joaotavora <at> gmail.com,  emacs-devel <at> gnu.org
> >> Date: Sat, 06 May 2023 17:17:14 +0200
> >>
> >> 3. Eglot uses the eglot--apply-text-edits defun to apply server
> >>    initiated edits.  There is an extension that allows the server to
> >>    send the edits in a different format (snippet-text-edits).
> >>
> >>    Eglot-x puts an advise on eglot--apply-text-edits to check the format
> >>    of the edits and act accordingly.
> >>
> >>    I don't know how to avoid this advice.
> >
> > Some hook or function variable, perhaps?  If they don't exist, perhaps
> > they could be added?
>
> I've attached a patch with my first attempt at this.  João, what do you
> think of this approach?

It could work.  But I think   Some comments

? +(defvar eglot-apply-text-edits-function #'eglot--apply-lsp-text-edits

eglot--apply-lsp-text-edits should in theory be external, because it
is something pluggable on and off.

IME these things also tend lend themselves to "multiple handlers"
in the future, so maybe a generalized "special" hook (ending in
"functions", plural) is better.  Then some `run-hook-wrapped`
would iterate through it.

But then, I wonder, since most of Eglot's API is already CLOS
based, isn't a generic function best?  A generic function can
have :around methods, and if we follow the convention of
passing the server as the first argument, third parties
can make server-specific extensions.

Also, in some CLOS versions (not eieio.el's yet) there
are even method combinations that simulate hooks.

> +(cl-defun eglot--apply-lsp-text-edits (edits)
> +  "Apply EDITS for current buffer."
>    (atomic-change-group
>     (let* ((change-group (prepare-change-group))

Won't every "apply edit" function need this as boilerplate to
guarantee undo-stability?  Likely this should be popped to
around the call to the generic function.

> (Independently of this issue, maybe a configurable
> apply-workspace-edit-function would be useful as well.  One alternative
> implementation of the current eglot--apply-workspace-edit could be to
> apply all edits without asking for confirmation and then show a
> `vc-diff'-like interface allowing the user to revert/accept all of the
> changes with a single keystroke.)

If this is truly independent, request it in a different bug report
(or a different thread within this bug) with a different patch.
If it is not  independent, let's focus on the infrastructure functionality
first.

João




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#63372; Package emacs. (Wed, 10 May 2023 19:36:02 GMT) Full text and rfc822 format available.

Message #14 received at 63372 <at> debbugs.gnu.org (full text, mbox):

From: Felician Nemeth <felician.nemeth <at> gmail.com>
To: João Távora <joaotavora <at> gmail.com>
Cc: 63372 <at> debbugs.gnu.org, Eli Zaretskii <eliz <at> gnu.org>
Subject: Re: bug#63372: [PATCH] Add variable: eglot-apply-text-edits-function
Date: Wed, 10 May 2023 21:35:12 +0200
[Message part 1 (text/plain, inline)]
João Távora <joaotavora <at> gmail.com> writes:

>> >> 3. Eglot uses the eglot--apply-text-edits defun to apply server
>> >>    initiated edits.  There is an extension that allows the server to
>> >>    send the edits in a different format (snippet-text-edits).
>> >>
>> >>    Eglot-x puts an advise on eglot--apply-text-edits to check the format
>> >>    of the edits and act accordingly.
>> >>
>> >>    I don't know how to avoid this advice.
>> >
>> > Some hook or function variable, perhaps?  If they don't exist, perhaps
>> > they could be added?

> But then, I wonder, since most of Eglot's API is already CLOS
> based, isn't a generic function best?  A generic function can
> have :around methods, and if we follow the convention of
> passing the server as the first argument, third parties
> can make server-specific extensions.

You're right, I think.  In the attached patch, I simply changed
eglot--apply-text-edits to a cl-defgeneric and renamed it.  The patch is
straightforward, but I had to eliminate a cl-return-from call, because
generic functions don't seem to support that.

Thanks.

[0001-Eglot-Replace-eglot-apply-text-edits-with-a-public-f.patch (text/x-diff, attachment)]

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#63372; Package emacs. (Thu, 11 May 2023 19:55:02 GMT) Full text and rfc822 format available.

Message #17 received at 63372 <at> debbugs.gnu.org (full text, mbox):

From: João Távora <joaotavora <at> gmail.com>
To: Felician Nemeth <felician.nemeth <at> gmail.com>
Cc: 63372 <at> debbugs.gnu.org, Eli Zaretskii <eliz <at> gnu.org>
Subject: Re: bug#63372: [PATCH] Add variable: eglot-apply-text-edits-function
Date: Thu, 11 May 2023 20:54:32 +0100
[Message part 1 (text/plain, inline)]
After having another look at eglot-x.el i don't
think eglot--apply-text-edits (plural) is the right
place to put the generic.  You'd just repeat a lot
of code of the original, with no clear way to reuse it.

Maybe you want something more akin to the attached patch,
which introduces eglot-apply-text-edit (singular).  In your
override for this function you can check conditions to either
proceed with the non-standard edit or delegate to the default
implementation with (cl-call-next-method).

João
[0001-Eglot-allow-extensions-to-application-of-LSP-edits-b.patch (application/octet-stream, attachment)]

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#63372; Package emacs. (Mon, 15 May 2023 20:04:01 GMT) Full text and rfc822 format available.

Message #20 received at 63372 <at> debbugs.gnu.org (full text, mbox):

From: Felician Nemeth <felician.nemeth <at> gmail.com>
To: João Távora <joaotavora <at> gmail.com>
Cc: 63372 <at> debbugs.gnu.org, Eli Zaretskii <eliz <at> gnu.org>
Subject: Re: bug#63372: [PATCH] Add variable: eglot-apply-text-edits-function
Date: Mon, 15 May 2023 22:03:37 +0200
João Távora <joaotavora <at> gmail.com> writes:

> After having another look at eglot-x.el i don't
> think eglot--apply-text-edits (plural) is the right
> place to put the generic.  You'd just repeat a lot
> of code of the original, with no clear way to reuse it.
>
> Maybe you want something more akin to the attached patch,
> which introduces eglot-apply-text-edit (singular).  In your
> override for this function you can check conditions to either
> proceed with the non-standard edit or delegate to the default
> implementation with (cl-call-next-method).

Unfortunately, I think this won't help me cleanly implement the
snippet-text-edits feature.  The server can send many text-edits and at
most one of them can be a snippet-text-edit, which currently means that
it contains a "$0" to tell the client where to put the point after
applying the edits.

My implementation applied all the edits and save the snippet (if there
was any) for later use.  Then it called eglot--snippet-expansion-fn on
the saved snippet.  This way the user saw results of all the edits
before the snippet expansion.

If the latest patch is merged, then I can I override the singular
eglot-apply-text-edit, but I think I have no way to run a custom code
after all the edits are applied.

Thanks,
Felicián




This bug report was last modified 338 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.