From 6a71bf1a4732e464d93dc24dc647f157622577d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felici=C3=A1n=20N=C3=A9meth?= Date: Wed, 23 Mar 2022 21:47:45 +0100 Subject: [PATCH 1/3] Close #899: Implement on-type-formatting support * eglot.el (eglot-format): Add new optional argument `on-type-format' to request :textDocument/onTypeFormatting, and ... (eglot--post-self-insert-hook): ... call it from here when necessary. * eglot-tests.el (eglot--simulate-key-event): New helper defun. (rust-on-type-formatting): New test. * NEWS.md: mention feature. --- NEWS.md | 3 +++ eglot-tests.el | 27 +++++++++++++++++++++++++++ eglot.el | 27 +++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index ad087134..4cbd17f9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # (upcoming) +##### Support on-type-formatting ([#899][github#899]) + ##### Provide basic workspace-folders support ([#893][github#893]) Eglot now advertises `project-root` and `project-external-roots` as workspace-folders. (Configuring `tags-table-list` sets the external @@ -355,3 +357,4 @@ and now said bunch of references--> [github#810]: https://github.com/joaotavora/eglot/issues/810 [github#813]: https://github.com/joaotavora/eglot/issues/813 [github#893]: https://github.com/joaotavora/eglot/issues/893 +[github#899]: https://github.com/joaotavora/eglot/issues/899 diff --git a/eglot-tests.el b/eglot-tests.el index 55967a49..1432de6d 100644 --- a/eglot-tests.el +++ b/eglot-tests.el @@ -262,6 +262,13 @@ Pass TIMEOUT to `eglot--with-timeout'." (eglot-connect-timeout timeout)) (apply #'eglot--connect (eglot--guess-contact)))) +(defun eglot--simulate-key-event (char) + "Like (execute-kbd-macro (vector char)), but with `call-interactively'." + ;; Also, this is a bit similar to what electric-tests.el does. + (setq last-input-event char) + (setq last-command-event char) + (call-interactively (key-binding (vector char)))) + ;;; Unit tests @@ -670,6 +677,26 @@ pyls prefers autopep over yafp, despite its README stating the contrary." (should (string= (buffer-string) "def a():\n pass\n\n\ndef b():\n pass\n"))))) +(ert-deftest rust-on-type-formatting () + "Test textDocument/onTypeFormatting agains rust-analyzer." + (skip-unless (executable-find "rust-analyzer")) + (skip-unless (executable-find "cargo")) + (eglot--with-fixture + '(("on-type-formatting-project" . + (("main.rs" . + "fn main() -> () {\n foo\n .bar()\n ")))) + (with-current-buffer + (eglot--find-file-noselect "on-type-formatting-project/main.rs") + (let ((eglot-server-programs '((rust-mode . ("rust-analyzer"))))) + (should (zerop (shell-command "cargo init"))) + (eglot--sniffing (:server-notifications s-notifs) + (should (eglot--tests-connect)) + (eglot--wait-for (s-notifs 10) (&key method &allow-other-keys) + (string= method "textDocument/publishDiagnostics"))) + (goto-char (point-max)) + (eglot--simulate-key-event ?.) + (should (looking-back "^ \\.")))))) + (ert-deftest javascript-basic () "Test basic autocompletion in a JavaScript LSP." (skip-unless (executable-find "typescript-language-server")) diff --git a/eglot.el b/eglot.el index 58ad4588..1cf0b7ae 100644 --- a/eglot.el +++ b/eglot.el @@ -1974,8 +1974,18 @@ THINGS are either registrations or unregisterations (sic)." "If non-nil, value of the last inserted character in buffer.") (defun eglot--post-self-insert-hook () - "Set `eglot--last-inserted-char'." - (setq eglot--last-inserted-char last-input-event)) + "Set `eglot--last-inserted-char', call on-type-formatting if necessary." + (setq eglot--last-inserted-char last-input-event) + (when (or (eq last-input-event + (elt (eglot--server-capable + :documentOnTypeFormattingProvider + :firstTriggerCharacter) + 0)) + (seq-find (lambda (elt) (eq last-input-event (elt elt 0))) + (eglot--server-capable + :documentOnTypeFormattingProvider + :moreTriggerCharacter))) + (eglot-format (point) nil (string last-input-event)))) (defun eglot--pre-command-hook () "Reset `eglot--last-inserted-char'." @@ -2357,14 +2367,23 @@ Try to visit the target file for a richer summary line." (interactive) (eglot-format nil nil)) -(defun eglot-format (&optional beg end) +(defun eglot-format (&optional beg end on-type-format) "Format region BEG END. If either BEG or END is nil, format entire buffer. Interactively, format active region, or entire buffer if region -is not active." +is not active. + +If ON-TYPE-FORMAT is non-nil, request on-type-formatting from the +server. The argument should be a one-character-long string that +has just been inserted at BEG." (interactive (and (region-active-p) (list (region-beginning) (region-end)))) (pcase-let ((`(,method ,cap ,args) (cond + ((and beg on-type-format) + `(:textDocument/onTypeFormatting + :documentOnTypeFormattingProvider + ,`(:position ,(eglot--pos-to-lsp-position beg) + :ch ,on-type-format))) ((and beg end) `(:textDocument/rangeFormatting :documentRangeFormattingProvider From 0931a799d9cedb8955f31e93f836d7eed81d4884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Tue, 29 Mar 2022 00:09:34 +0100 Subject: [PATCH 2/3] Per #899: Tweak on-type-formatting code * eglot.el (eglot--post-self-insert-hook): Tweak. (eglot-format): Tweak docstring. --- eglot.el | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/eglot.el b/eglot.el index 1cf0b7ae..d71e5966 100644 --- a/eglot.el +++ b/eglot.el @@ -1974,18 +1974,16 @@ THINGS are either registrations or unregisterations (sic)." "If non-nil, value of the last inserted character in buffer.") (defun eglot--post-self-insert-hook () - "Set `eglot--last-inserted-char', call on-type-formatting if necessary." + "Set `eglot--last-inserted-char', maybe call on-type-formatting." (setq eglot--last-inserted-char last-input-event) - (when (or (eq last-input-event - (elt (eglot--server-capable - :documentOnTypeFormattingProvider - :firstTriggerCharacter) - 0)) - (seq-find (lambda (elt) (eq last-input-event (elt elt 0))) - (eglot--server-capable - :documentOnTypeFormattingProvider - :moreTriggerCharacter))) - (eglot-format (point) nil (string last-input-event)))) + (let ((ot-provider (eglot--server-capable :documentOnTypeFormattingProvider))) + (when (and ot-provider + (or (eq last-input-event + (elt (plist-get ot-provider :firstTriggerCharacter) 0)) + (cl-find last-input-event + (plist-get ot-provider :moreTriggerCharacter) + :key #'seq-first))) + (eglot-format (point) nil last-input-event)))) (defun eglot--pre-command-hook () "Reset `eglot--last-inserted-char'." @@ -2373,9 +2371,8 @@ If either BEG or END is nil, format entire buffer. Interactively, format active region, or entire buffer if region is not active. -If ON-TYPE-FORMAT is non-nil, request on-type-formatting from the -server. The argument should be a one-character-long string that -has just been inserted at BEG." +If non-nil, ON-TYPE-FORMAT is a character just inserted at BEG +for which LSP on-type-formatting should be requested." (interactive (and (region-active-p) (list (region-beginning) (region-end)))) (pcase-let ((`(,method ,cap ,args) (cond @@ -2383,7 +2380,7 @@ has just been inserted at BEG." `(:textDocument/onTypeFormatting :documentOnTypeFormattingProvider ,`(:position ,(eglot--pos-to-lsp-position beg) - :ch ,on-type-format))) + :ch ,(string on-type-format)))) ((and beg end) `(:textDocument/rangeFormatting :documentRangeFormattingProvider From 35588e0f8d50f647d6b2fccb48afcdd1c068b3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Tue, 29 Mar 2022 00:09:16 +0100 Subject: [PATCH 3/3] Fix typo in NEWS.md * NEWS.md: Fix typo. --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 4cbd17f9..b3e6f583 100644 --- a/NEWS.md +++ b/NEWS.md @@ -26,7 +26,7 @@ rendering suggestions in the protocol, we fade out unnecessary code and strike-through deprecated code. ##### The Rust language server is now rust-analyzer by default ([#803][github#803]) -Eglot will now prefer starting "rust-analazyer" to "rls" when it is +Eglot will now prefer starting "rust-analyzer" to "rls" when it is available. The special support code for RLS has been removed. ##### New servers have been added to `eglot-server-programs`