[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/eglot 8fda30c 67/69: Merge master into jsonrpc-refactor
From: |
João Távora |
Subject: |
[elpa] externals/eglot 8fda30c 67/69: Merge master into jsonrpc-refactor (using imerge) |
Date: |
Fri, 22 Jun 2018 11:55:05 -0400 (EDT) |
branch: externals/eglot
commit 8fda30c8e2d4e44dfb40e91109893277ca683d25
Merge: 61d1276 04ef055
Author: João Távora <address@hidden>
Commit: João Távora <address@hidden>
Merge master into jsonrpc-refactor (using imerge)
---
README.md | 18 +++++--
eglot-tests.el | 84 +++++++++++++++++++++++++--------
eglot.el | 145 +++++++++++++++++++++++++++++++++++++++------------------
3 files changed, 179 insertions(+), 68 deletions(-)
diff --git a/README.md b/README.md
index 2ced833..a56d750 100644
--- a/README.md
+++ b/README.md
@@ -35,13 +35,22 @@ I'll add to this list as I test more servers. In the
meantime you can
customize `eglot-server-programs`:
```lisp
-(add-to-list 'eglot-server-programs '(fancy-mode . ("fancy-language-server"
"--args"")))
+(add-to-list 'eglot-server-programs '(foo-mode . ("foo-language-server"
"--args"")))
```
Let me know how well it works and we can add it to the list. You can
also enter a `server:port` pattern to connect to an LSP server. To
skip the guess and always be prompted use `C-u M-x eglot`.
+You can also do:
+
+```lisp
+ (add-hook 'foo-mode-hook 'eglot-ensure)
+```
+
+To attempt to start an eglot session automatically everytime a
+`foo-mode` buffer is visited.
+
# Commands and keybindings
Here's a summary of available commands:
@@ -52,7 +61,10 @@ Here's a summary of available commands:
- `M-x eglot-shutdown` says bye-bye to the server;
-- `M-x eglot-rename` asks the server to rename the symbol at point;
+- `M-x eglot-rename` ask the server to rename the symbol at point;
+
+- `M-x eglot-format-buffer` ask the server to reformat the current
+ buffer.
- `M-x eglot-code-actions` asks the server for any code actions at
point. These may tipically be simple fixes, like deleting an unused
@@ -155,7 +167,7 @@ eglot-shutdown`.
- [ ] documentLink/resolve
- [ ] textDocument/documentColor
- [ ] textDocument/colorPresentation (3.6.0)
-- [ ] textDocument/formatting
+- [x] textDocument/formatting
- [ ] textDocument/rangeFormatting
- [ ] textDocument/onTypeFormatting
- [x] textDocument/rename
diff --git a/eglot-tests.el b/eglot-tests.el
index b57b949..048b1d3 100644
--- a/eglot-tests.el
+++ b/eglot-tests.el
@@ -158,6 +158,12 @@ Pass TIMEOUT to `eglot--with-timeout'."
(format "waiting for:\n%s"
(pp-to-string body))))
(let ((event
(cl-loop thereis (cl-loop for json in ,events-sym
+ for method = (plist-get json :method)
+ when (keywordp method)
+ do (plist-put json :method
+ (substring
+ (symbol-name method)
+ 1))
when (funcall
(jsonrpc-lambda ,args ,@body) json)
return (cons json before)
@@ -252,7 +258,7 @@ Pass TIMEOUT to `eglot--with-timeout'."
(eglot--wait-for (s-requests 1)
(&key id method &allow-other-keys)
(setq register-id id)
- (string= method 'client/registerCapability))
+ (string= method "client/registerCapability"))
(eglot--wait-for (c-replies 1)
(&key id error &allow-other-keys)
(and (eq id register-id) (null error))))
@@ -260,7 +266,7 @@ Pass TIMEOUT to `eglot--with-timeout'."
(eglot--wait-for
(c-notifs 3 "waiting for didChangeWatchedFiles notification")
(&key method params &allow-other-keys)
- (and (string= method 'workspace/didChangeWatchedFiles)
+ (and (string= method "workspace/didChangeWatchedFiles")
(cl-destructuring-bind (&key uri type)
(elt (plist-get params :changes) 0)
(and (string= (eglot--path-to-uri "Cargo.toml") uri)
@@ -280,7 +286,7 @@ Pass TIMEOUT to `eglot--with-timeout'."
(eglot--tests-connect)
(eglot--wait-for (s-notifs 1)
(&key _id method &allow-other-keys)
- (string= method 'textDocument/publishDiagnostics))
+ (string= method "textDocument/publishDiagnostics"))
(flymake-start)
(goto-char (point-min))
(flymake-goto-next-error 1 '() t)
@@ -300,11 +306,7 @@ Pass TIMEOUT to `eglot--with-timeout'."
(eglot--find-file-noselect "hover-project/main.rs")
(should (zerop (shell-command "cargo init")))
(eglot--sniffing (
- :server-notifications s-notifs
- :server-requests s-requests
:server-replies s-replies
- :client-notifications c-notifs
- :client-replies c-replies
:client-requests c-reqs
)
(eglot--tests-connect)
@@ -320,7 +322,7 @@ Pass TIMEOUT to `eglot--with-timeout'."
(eglot--wait-for (c-reqs)
(&key id method &allow-other-keys)
(setq pending-id id)
- (string= method 'textDocument/documentHighlight))
+ (string= method "textDocument/documentHighlight"))
(eglot--wait-for (s-replies)
(&key id &allow-other-keys)
(eq id pending-id))))))))
@@ -337,18 +339,10 @@ Pass TIMEOUT to `eglot--with-timeout'."
(with-current-buffer
(eglot--find-file-noselect "rename-project/main.rs")
(should (zerop (shell-command "cargo init")))
- (eglot--sniffing (
- :server-notifications s-notifs
- :server-requests s-requests
- :server-replies s-replies
- :client-notifications c-notifs
- :client-replies c-replies
- :client-requests c-reqs
- )
- (eglot--tests-connect)
- (goto-char (point-min)) (search-forward "return te")
- (eglot-rename "bla")
- (should (equal (buffer-string) "fn test() -> i32 { let bla=3; return
bla; }")))))))
+ (eglot--tests-connect)
+ (goto-char (point-min)) (search-forward "return te")
+ (eglot-rename "bla")
+ (should (equal (buffer-string) "fn test() -> i32 { let bla=3; return
bla; }"))))))
(ert-deftest basic-completions ()
"Test basic autocompletion in a python LSP"
@@ -379,6 +373,56 @@ Pass TIMEOUT to `eglot--with-timeout'."
(while (not eldoc-last-message) (accept-process-output nil 0.1))
(should (string-match "^exit" eldoc-last-message))))))
+(ert-deftest formatting ()
+ "Test document formatting in a python LSP"
+ (skip-unless (and (executable-find "pyls")
+ (or (executable-find "yapf")
+ (executable-find "autopep8"))))
+ (eglot--with-dirs-and-files
+ '(("project" . (("something.py" . "def foo():pass"))))
+ (eglot--with-timeout 4
+ (with-current-buffer
+ (eglot--find-file-noselect "project/something.py")
+ (should (eglot--tests-connect))
+ (search-forward ":pa")
+ (eglot-format-buffer)
+ (should (looking-at "ss"))
+ (should (or
+ ;; yapf
+ (string= (buffer-string) "def foo():\n pass\n")
+ ;; autopep8
+ (string= (buffer-string) "def foo(): pass\n")))))))
+
+(ert-deftest javascript-basic ()
+ "Test basic autocompletion in a python LSP"
+ (skip-unless (executable-find "~/.yarn/bin/javascript-typescript-stdio"))
+ (eglot--with-dirs-and-files
+ '(("project" . (("hello.js" . "console.log('Hello world!');"))))
+ (eglot--with-timeout 4
+ (with-current-buffer
+ (eglot--find-file-noselect "project/hello.js")
+ (let ((eglot-server-programs
+ '((js-mode . ("~/.yarn/bin/javascript-typescript-stdio")))))
+ (goto-char (point-max))
+ (eglot--sniffing (:server-notifications
+ s-notifs
+ :client-notifications
+ c-notifs)
+ (should (eglot--tests-connect))
+ (eglot--wait-for (s-notifs 1) (&key method &allow-other-keys)
+ (string= method "textDocument/publishDiagnostics"))
+ (should (not (eq 'flymake-error (face-at-point))))
+ (insert "{")
+ (eglot--signal-textDocument/didChange)
+ (eglot--wait-for (c-notifs 1) (&key method &allow-other-keys)
+ (string= method "textDocument/didChange"))
+ (eglot--wait-for (s-notifs 1) (&key params method
&allow-other-keys)
+ (and (string= method "textDocument/publishDiagnostics")
+ (cl-destructuring-bind (&key _uri diagnostics) params
+ (cl-find-if (jsonrpc-lambda (&key severity
&allow-other-keys)
+ (= severity 1))
+ diagnostics))))))))))
+
(provide 'eglot-tests)
;;; eglot-tests.el ends here
diff --git a/eglot.el b/eglot.el
index 0a060a5..290e80b 100644
--- a/eglot.el
+++ b/eglot.el
@@ -2,7 +2,7 @@
;; Copyright (C) 2018 Free Software Foundation, Inc.
-;; Version: 0.8
+;; Version: 0.10
;; Author: João Távora <address@hidden>
;; Maintainer: João Távora <address@hidden>
;; URL: https://github.com/joaotavora/eglot
@@ -79,15 +79,19 @@
(defvar eglot-server-programs '((rust-mode . (eglot-rls "rls"))
(python-mode . ("pyls"))
- (js-mode . ("javascript-typescript-stdio"))
+ ((js-mode
+ js2-mode
+ rjsx-mode) . ("javascript-typescript-stdio"))
(sh-mode . ("bash-language-server" "start"))
- (c++-mode . (eglot-cquery "cquery"))
- (c-mode . (eglot-cquery "cquery"))
+ ((c++-mode
+ c-mode) . (eglot-cquery "cquery"))
(php-mode . ("php" "vendor/felixfbecker/\
language-server/bin/php-language-server.php")))
"How the command `eglot' guesses the server to start.
-An association list of (MAJOR-MODE . CONTACT) pair. MAJOR-MODE
-is a mode symbol. CONTACT is:
+An association list of (MAJOR-MODE . CONTACT) pairs. MAJOR-MODE
+is a mode symbol, or a list of mode symbols. The associated
+CONTACT specifies how to start a server for managing buffers of
+those modes. CONTACT can be:
* In the most common case, a list of strings (PROGRAM [ARGS...]).
PROGRAM is called with ARGS and is expected to serve LSP requests
@@ -151,7 +155,6 @@ lasted more than that many seconds."
:workspace (list
:applyEdit t
:executeCommand `(:dynamicRegistration :json-false)
- :codeAction `(:dynamicRegistration :json-false)
:workspaceEdit `(:documentChanges :json-false)
:didChangeWatchesFiles `(:dynamicRegistration t)
:symbol `(:dynamicRegistration :json-false))
@@ -167,6 +170,8 @@ lasted more than that many seconds."
:definition `(:dynamicRegistration :json-false)
:documentSymbol `(:dynamicRegistration :json-false)
:documentHighlight `(:dynamicRegistration :json-false)
+ :codeAction `(:dynamicRegistration :json-false)
+ :formatting `(:dynamicRegistration :json-false)
:rename `(:dynamicRegistration :json-false)
:publishDiagnostics `(:relatedInformation :json-false))
:experimental (list))))
@@ -210,16 +215,16 @@ lasted more than that many seconds."
(defvar eglot--servers-by-project (make-hash-table :test #'equal)
"Keys are projects. Values are lists of processes.")
-(defun eglot-shutdown (server &optional _interactive)
+(defun eglot-shutdown (server &optional _interactive timeout)
"Politely ask SERVER to quit.
-Forcefully quit it if it doesn't respond. Don't leave this
-function with the server still running."
+Forcefully quit it if it doesn't respond within TIMEOUT seconds.
+Don't leave this function with the server still running."
(interactive (list (eglot--current-server-or-lose) t))
(eglot--message "Asking %s politely to terminate" (jsonrpc-name server))
(unwind-protect
(progn
(setf (eglot--shutdown-requested server) t)
- (jsonrpc-request server :shutdown nil :timeout 3)
+ (jsonrpc-request server :shutdown nil :timeout (or timeout 1.5))
;; this one is supposed to always fail, because it asks the
;; server to exit itself. Hence ignore-errors.
(ignore-errors (jsonrpc-request server :exit nil :timeout 1)))
@@ -264,24 +269,31 @@ function with the server still running."
(defun eglot--guess-contact (&optional interactive)
"Helper for `eglot'.
-Return (MANAGED-MODE PROJECT CONTACT CLASS).
-If INTERACTIVE, maybe prompt user."
+Return (MANAGED-MODE PROJECT CLASS CONTACT). If INTERACTIVE is
+non-nil, maybe prompt user, else error as soon as something can't
+be guessed."
(let* ((guessed-mode (if buffer-file-name major-mode))
(managed-mode
(cond
- ((or (>= (prefix-numeric-value current-prefix-arg) 16)
- (not guessed-mode))
+ ((and interactive
+ (or (>= (prefix-numeric-value current-prefix-arg) 16)
+ (not guessed-mode)))
(intern
(completing-read
"[eglot] Start a server to manage buffers of what major mode? "
(mapcar #'symbol-name (eglot--all-major-modes)) nil t
(symbol-name guessed-mode) nil (symbol-name guessed-mode) nil)))
+ ((not guessed-mode)
+ (eglot--error "Can't guess mode to manage for `%s'"
(current-buffer)))
(t guessed-mode)))
(project (or (project-current) `(transient . ,default-directory)))
- (guess (cdr (assoc managed-mode eglot-server-programs)))
- (class (if (and (consp guess) (symbolp (car guess)))
- (prog1 (car guess) (setq guess (cdr guess)))
- 'eglot-lsp-server))
+ (guess (cdr (assoc managed-mode eglot-server-programs
+ (lambda (m1 m2)
+ (or (eq m1 m2)
+ (and (listp m1) (memq m2 m1)))))))
+ (class (or (and (consp guess) (symbolp (car guess))
+ (prog1 (car guess) (setq guess (cdr guess))))
+ 'eglot-lsp-server))
(program (and (listp guess) (stringp (car guess)) (car guess)))
(base-prompt
(and interactive
@@ -298,16 +310,18 @@ If INTERACTIVE, maybe prompt user."
(format ", but I can't find `%s' in PATH!"
program)
"\n" base-prompt)))))
(contact
- (if prompt
- (let ((s (read-shell-command
- prompt
- (if program (combine-and-quote-strings guess))
- 'eglot-command-history)))
- (if (string-match "^\\([^\s\t]+\\):\\([[:digit:]]+\\)$"
- (string-trim s))
- (list (match-string 1 s) (string-to-number (match-string 2
s)))
- (split-string-and-unquote s)))
- guess)))
+ (or (and prompt
+ (let ((s (read-shell-command
+ prompt
+ (if program (combine-and-quote-strings guess))
+ 'eglot-command-history)))
+ (if (string-match "^\\([^\s\t]+\\):\\([[:digit:]]+\\)$"
+ (string-trim s))
+ (list (match-string 1 s)
+ (string-to-number (match-string 2 s)))
+ (split-string-and-unquote s))))
+ guess
+ (eglot--error "Couldn't guess for `%s'!" managed-mode))))
(list managed-mode project class contact)))
;;;###autoload
@@ -389,7 +403,8 @@ INTERACTIVE is t if called interactively."
(eglot--project-nickname server)
major-mode
(eglot--project-nickname server)))))))
- (add-hook 'post-command-hook #'maybe-connect 'append nil))))
+ (when buffer-file-name
+ (add-hook 'post-command-hook #'maybe-connect 'append nil)))))
(defun eglot-events-buffer (server)
"Display events buffer for SERVER."
@@ -537,7 +552,7 @@ If optional MARKER, return a marker instead"
"Format MARKUP according to LSP's spec."
(pcase-let ((`(,string ,mode)
(if (stringp markup) (list (string-trim markup)
- (intern "markdown-mode"))
+ (intern "gfm-mode"))
(list (plist-get markup :value)
(intern (concat (plist-get markup :language) "-mode"
))))))
(with-temp-buffer
@@ -561,8 +576,8 @@ under cursor."
for feat in feats
for probe = (plist-member caps feat)
if (not probe) do (cl-return nil)
- if (eq (cadr probe) t) do (cl-return t)
if (eq (cadr probe) :json-false) do (cl-return nil)
+ if (not (listp (cadr probe))) do (cl-return (cadr probe))
finally (cl-return (or probe t)))))
(defun eglot--range-region (range &optional markers)
@@ -571,10 +586,7 @@ If optional MARKERS, make markers."
(let* ((st (plist-get range :start))
(beg (eglot--lsp-position-to-point st markers))
(end (eglot--lsp-position-to-point (plist-get range :end) markers)))
- ;; Fallback to `flymake-diag-region' if server botched the range
- (if (/= beg end) (cons beg end) (flymake-diag-region
- (current-buffer) (plist-get st :line)
- (1- (plist-get st :character))))))
+ (cons beg end)))
;;; Minor modes
@@ -788,7 +800,18 @@ Uses THING, FACE, DEFS and PREPEND."
_code source message)
diag-spec
(setq message (concat source ": " message))
- (pcase-let ((`(,beg . ,end) (eglot--range-region range)))
+ (pcase-let
+ ((`(,beg . ,end) (eglot--range-region range)))
+ ;; Fallback to `flymake-diag-region' if server
+ ;; botched the range
+ (if (= beg end)
+ (let* ((st (plist-get range :start))
+ (diag-region
+ (flymake-diag-region
+ (current-buffer) (plist-get st :line)
+ (1- (plist-get st :character)))))
+ (setq beg (car diag-region)
+ end (cdr diag-region))))
(eglot--make-diag (current-buffer) beg end
(cond ((<= sev 1) 'eglot-error)
((= sev 2) 'eglot-warning)
@@ -1065,6 +1088,21 @@ DUMMY is ignored."
:workspace/symbol
`(:query ,pattern)))))
+(defun eglot-format-buffer ()
+ "Format contents of current buffer."
+ (interactive)
+ (unless (eglot--server-capable :documentFormattingProvider)
+ (eglot--error "Server can't format!"))
+ (eglot--apply-text-edits
+ (jsonrpc-request
+ (eglot--current-server-or-lose)
+ :textDocument/formatting
+ (list :textDocument (eglot--TextDocumentIdentifier)
+ :options (list :tabSize tab-width
+ :insertSpaces
+ (if indent-tabs-mode :json-false t)))
+ :deferred :textDocument/formatting)))
+
(defun eglot-completion-at-point ()
"EGLOT's `completion-at-point' function."
(let ((bounds (bounds-of-thing-at-point 'symbol))
@@ -1239,15 +1277,32 @@ If SKIP-SIGNATURE, don't try to send
textDocument/signatureHelp."
(defun eglot--apply-text-edits (edits &optional version)
"Apply EDITS for current buffer if at VERSION, or if it's nil."
(unless (or (not version) (equal version eglot--versioned-identifier))
- (jsonrpc-error "Edits on `%s' require version %d, we have %d"
+ (jsonrpc-error "Edits on `%s' require version %d, you have %d"
(current-buffer) version eglot--versioned-identifier))
- (eglot--widening
- (mapc (pcase-lambda (`(,newText ,beg . ,end))
- (goto-char beg) (delete-region beg end) (insert newText))
- (mapcar (jsonrpc-lambda (&key range newText)
- (cons newText (eglot--range-region range 'markers)))
- edits)))
- (eglot--message "%s: Performed %s edits" (current-buffer) (length edits)))
+ (atomic-change-group
+ (let* ((change-group (prepare-change-group))
+ (howmany (length edits))
+ (reporter (make-progress-reporter
+ (format "[eglot] applying %s edits to `%s'..."
+ howmany (current-buffer))
+ 0 howmany))
+ (done 0))
+ (mapc (pcase-lambda (`(,newText ,beg . ,end))
+ (let ((source (current-buffer)))
+ (with-temp-buffer
+ (insert newText)
+ (let ((temp (current-buffer)))
+ (with-current-buffer source
+ (save-excursion
+ (save-restriction
+ (narrow-to-region beg end)
+ (replace-buffer-contents temp)))
+ (progress-reporter-update reporter (cl-incf done)))))))
+ (mapcar (jsonrpc-lambda (&key range newText)
+ (cons newText (eglot--range-region range 'markers)))
+ edits))
+ (undo-amalgamate-change-group change-group)
+ (progress-reporter-done reporter))))
(defun eglot--apply-workspace-edit (wedit &optional confirm)
"Apply the workspace edit WEDIT. If CONFIRM, ask user first."
- [elpa] externals/eglot 7371f68 57/69: * jsonrpc.el: Rewrite commentary., (continued)
- [elpa] externals/eglot 7371f68 57/69: * jsonrpc.el: Rewrite commentary., João Távora, 2018/06/22
- [elpa] externals/eglot 6531c8b 58/69: Merge branch 'master' into jsonrpc-refactor, João Távora, 2018/06/22
- [elpa] externals/eglot 59cc3fb 61/69: jsonrpc-connection-receive is now a public convenience function, João Távora, 2018/06/22
- [elpa] externals/eglot d371f05 49/69: Request dispatcher's return value determines response, João Távora, 2018/06/22
- [elpa] externals/eglot 0f20fdf 68/69: Tiny README.md change, João Távora, 2018/06/22
- [elpa] externals/eglot cef3c29 22/69: Heroically merge master into jsonrpc-refactor (using imerge), João Távora, 2018/06/22
- [elpa] externals/eglot a4441c6 37/69: Merge master into jsonrpc-refactor (using imerge), João Távora, 2018/06/22
- [elpa] externals/eglot 0e44b27 27/69: jsonrpc.el uses classes and generic functions, João Távora, 2018/06/22
- [elpa] externals/eglot 856a224 62/69: Simplify jsonrpc-connection-send, João Távora, 2018/06/22
- [elpa] externals/eglot 1f09fd3 59/69: Review commentary section before another review cycle, João Távora, 2018/06/22
- [elpa] externals/eglot 8fda30c 67/69: Merge master into jsonrpc-refactor (using imerge),
João Távora <=
- [elpa] externals/eglot 7f4e273 31/69: Merge master into jsonrpc-refactor (using imerge), João Távora, 2018/06/22
- [elpa] externals/eglot 4525eca 43/69: Support json.c. API purely based on classes, João Távora, 2018/06/22
- [elpa] externals/eglot bb60c0c 21/69: Rename jrpc.el to jsonrpc.el, João Távora, 2018/06/22
- [elpa] externals/eglot 46e6107 54/69: Reshuffle definitions inside jsonrpc.el, João Távora, 2018/06/22
- [elpa] externals/eglot 6f1ecc6 28/69: Merge branch use-eieio-server-defclass into jsonrpc-refactor, João Távora, 2018/06/22
- [elpa] externals/eglot 10559a5 56/69: Shuffle definitions around again, João Távora, 2018/06/22
- [elpa] externals/eglot b3c8b59 02/69: Refactor JSON-RPC lib jrpc.el from eglot.el, João Távora, 2018/06/22
- [elpa] externals/eglot 1ec47fb 51/69: Remove connection grabbing antics from jsonrpc.el, João Távora, 2018/06/22
- [elpa] externals/eglot f385d9c 69/69: Merge branch 'jsonrpc-refactor', bump version to 1.0, João Távora, 2018/06/22