]> git.eshelyaron.com Git - emacs.git/commitdiff
Eglot: add support for insertReplaceEdit (bug#73857)
authorJoão Távora <joaotavora@gmail.com>
Mon, 20 Jan 2025 18:58:05 +0000 (18:58 +0000)
committerEshel Yaron <me@eshelyaron.com>
Thu, 23 Jan 2025 10:23:22 +0000 (11:23 +0100)
* lisp/progmodes/eglot.el (eglot-server-programs): Mention zig-ts-mode.
(eglot--lsp-interface-alist): Describe 'InsertReplaceEdit'.
(eglot-client-capabilities): Advertise 'insertReplaceSupport'.
(eglot-completion-at-point): Consider 'InsertReplaceEdit'.
(eglot--apply-text-edits): Consider 'InsertReplaceEdit'.

* test/lisp/progmodes/eglot-tests.el
(eglot-test-zig-insert-replace-completion): New test.

Special thanks to kcbanner@gmail.com

(cherry picked from commit 1143cf09a339d57051a4341103c9e342d8876649)

lisp/progmodes/eglot.el
test/lisp/progmodes/eglot-tests.el

index d2b175d3b9ca20629e758c2b70cd90fbe610324b..7f6a686a37100c43d8bc91700080656dce2a9790 100644 (file)
@@ -312,7 +312,7 @@ automatically)."
     ((lua-mode lua-ts-mode) . ,(eglot-alternatives
                                 '("lua-language-server" "lua-lsp")))
     (yang-mode . ("yang-language-server"))
-    (zig-mode . ("zls"))
+    ((zig-mode zig-ts-mode) . ("zls"))
     ((css-mode css-ts-mode)
      . ,(eglot-alternatives '(("vscode-css-language-server" "--stdio")
                               ("css-languageserver" "--stdio"))))
@@ -656,6 +656,7 @@ This can be useful when using docker to run a language server.")
                       (:detail :deprecated :children))
       (TextDocumentEdit (:textDocument :edits) ())
       (TextEdit (:range :newText))
+      (InsertReplaceEdit (:newText :insert :replace))
       (VersionedTextDocumentIdentifier (:uri :version) ())
       (WorkDoneProgress (:kind) (:title :message :percentage :cancellable))
       (WorkspaceEdit () (:changes :documentChanges))
@@ -968,7 +969,8 @@ object."
                                                        ["documentation"
                                                         "details"
                                                         "additionalTextEdits"])
-                                      :tagSupport (:valueSet [1]))
+                                      :tagSupport (:valueSet [1])
+                                      :insertReplaceSupport t)
                                     :contextSupport t)
              :hover              (list :dynamicRegistration :json-false
                                        :contentFormat (eglot--accepted-formats))
@@ -3406,8 +3408,15 @@ for which LSP on-type-formatting should be requested."
                   ;; insertion to potentially cancel an essential
                   ;; resolution request (github#1474).
                   'dont-cancel-on-input)
-               (let ((snippet-fn (and (eql insertTextFormat 2)
-                                      (eglot--snippet-expansion-fn))))
+               (let* ((snippet-fn (and (eql insertTextFormat 2)
+                                       (eglot--snippet-expansion-fn)))
+                      (apply-edit
+                       (lambda (range text)
+                         (pcase-let ((`(,beg . ,end)
+                                      (eglot-range-region range)))
+                           (delete-region beg end)
+                           (goto-char beg)
+                           (funcall (or snippet-fn #'insert) text)))))
                  (cond (textEdit
                         ;; Revert buffer back to state when the edit
                         ;; was obtained from server. If a `proxy'
@@ -3416,12 +3425,11 @@ for which LSP on-type-formatting should be requested."
                         ;; state, _not_ the current "foo.bar".
                         (delete-region orig-pos (point))
                         (insert (substring bounds-string (- orig-pos (car bounds))))
-                        (eglot--dbind ((TextEdit) range newText) textEdit
-                          (pcase-let ((`(,beg . ,end)
-                                       (eglot-range-region range)))
-                            (delete-region beg end)
-                            (goto-char beg)
-                            (funcall (or snippet-fn #'insert) newText))))
+                        (eglot--dcase textEdit
+                          (((TextEdit) range newText)
+                           (funcall apply-edit range newText))
+                          (((InsertReplaceEdit) newText replace)
+                           (funcall apply-edit replace newText))))
                        (snippet-fn
                         ;; A snippet should be inserted, but using plain
                         ;; `insertText'.  This requires us to delete the
@@ -3650,8 +3658,12 @@ If SILENT, don't echo progress in mode-line."
                           (replace-buffer-contents temp)))
                       (when reporter
                         (eglot--reporter-update reporter (cl-incf done))))))))
-            (mapcar (eglot--lambda ((TextEdit) range newText)
-                      (cons newText (eglot-range-region range 'markers)))
+            (mapcar (lambda (edit)
+                      (eglot--dcase edit
+                        (((TextEdit) range newText)
+                         (cons newText (eglot-range-region range 'markers)))
+                        (((InsertReplaceEdit) newText replace)
+                         (cons newText (eglot-range-region replace 'markers)))))
                     (reverse edits)))
       (undo-amalgamate-change-group change-group)
       (when reporter
index 2dc45df7d9e6c4447c4bdfe0e6e1c4f48ca46dad..d54654b84aca7558c5ae53c10c48909b09f2b5c2 100644 (file)
@@ -725,6 +725,21 @@ directory hierarchy."
           "fn test() -> i32 { let v: usize = 1; v.count_ones.1234567890;")
         (buffer-string))))))
 
+(ert-deftest eglot-test-zig-insert-replace-completion ()
+  "Test zls's use of 'InsertReplaceEdit'."
+  (skip-unless (functionp 'zig-ts-mode))
+  (eglot--with-fixture
+      `(("project" .
+         (("main.zig" .
+           ,(concat "const Foo = struct {correct_name: u32,\n};\n"
+                    "fn example(foo: Foo) u32 {return foo.correc_name; }")))))
+    (with-current-buffer
+        (eglot--find-file-noselect "project/main.zig")
+      (should (eglot--tests-connect))
+      (search-forward "foo.correc")
+      (completion-at-point)
+      (should (looking-back "correct_name")))))
+
 (ert-deftest eglot-test-basic-xref ()
   "Test basic xref functionality in a clangd LSP."
   (skip-unless (executable-find "clangd"))