Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement on-type-formatting support #899

Merged
merged 3 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -24,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`
Expand Down Expand Up @@ -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
27 changes: 27 additions & 0 deletions eglot-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is ert-simulate-keys in ert-x.el. A hack it itself seems less hacky than this (and not our responsibility ;-) ) Doesn't it work? If it doesn't then fine to add this substitute.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ert-simulate-keys is not in Emacs 27.1, but more importantly it does something else: it provides user input for a function if it queries the user input (eg. read-from-minibuffer) during its execution. The goal of eglot--simulate-key-event is initiate a command-loop for a given keypress. I think the whole thing can be replaced with (setq last-input-event ?.) (self-insert-command 1 ?.) as that also calls post-self-insert-hooks. But then some part of the implementation becomes part of the test. An earlier iteration of this PR redefined keybindings for the shake of execution speed. eglot--simulate-key-event worked with that implementation as well.

Shall I (a) change the test to use (setq last-input-event ?.) (self-insert-command 1 ?.), or (b) extend the documentation of eglot--simulate-key-event?

"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

Expand Down Expand Up @@ -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"))
Expand Down
24 changes: 20 additions & 4 deletions eglot.el
Original file line number Diff line number Diff line change
Expand Up @@ -1974,8 +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'."
(setq eglot--last-inserted-char last-input-event))
"Set `eglot--last-inserted-char', maybe call on-type-formatting."
(setq eglot--last-inserted-char 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'."
Expand Down Expand Up @@ -2357,14 +2365,22 @@ 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 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
((and beg on-type-format)
`(:textDocument/onTypeFormatting
:documentOnTypeFormattingProvider
,`(:position ,(eglot--pos-to-lsp-position beg)
:ch ,(string on-type-format))))
((and beg end)
`(:textDocument/rangeFormatting
:documentRangeFormattingProvider
Expand Down