emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/eglot 7f4e273 31/69: Merge master into jsonrpc-refactor


From: João Távora
Subject: [elpa] externals/eglot 7f4e273 31/69: Merge master into jsonrpc-refactor (using imerge)
Date: Fri, 22 Jun 2018 11:54:59 -0400 (EDT)

branch: externals/eglot
commit 7f4e27352c37dd2cbfb25d22caab6f4b57f91964
Merge: 9e9dc57 4211e0c
Author: João Távora <address@hidden>
Commit: João Távora <address@hidden>

    Merge master into jsonrpc-refactor (using imerge)
---
 README.md      |   7 +
 eglot-tests.el |  48 +++++--
 eglot.el       | 423 +++++++++++++++++++++++++++++----------------------------
 jsonrpc.el     | 122 +++++++++--------
 4 files changed, 327 insertions(+), 273 deletions(-)

diff --git a/README.md b/README.md
index b87666c..c88f804 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,8 @@ for the language of your choice. Otherwise, it prompts you to 
enter one:
 * Python's [pyls][pyls]
 * Bash's [bash-language-server][bash-language-server]
 * PHP's [php-language-server][php-language-server]
+* [cquery][cquery] for C/C++
+
 
 I'll add to this list as I test more servers. In the meantime you can
 customize `eglot-server-programs`:
@@ -53,6 +55,9 @@ Here's a summary of available commands:
 - `M-x eglot-events-buffer` jumps to the events buffer for debugging
   communication with the server.
 
+- `M-x eglot-stderr-buffer` if the LSP server is printing useful debug
+information in stderr, jumps to a buffer with these contents.
+
 There are *no keybindings* specific to Eglot, but you can bind stuff
 in `eglot-mode-map`, which is active as long as Eglot is managing a
 file in your project. The commands don't need to be Eglot-specific,
@@ -196,5 +201,7 @@ Under the hood:
 [bash-language-server]: https://github.com/mads-hartmann/bash-language-server
 [php-language-server]: https://github.com/felixfbecker/php-language-server
 [company-mode]: https://github.com/company-mode/company-mode
+[cquery]: https://github.com/cquery-project/cquery
+
 
    
diff --git a/eglot-tests.el b/eglot-tests.el
index bdb8b21..c9cc3fd 100644
--- a/eglot-tests.el
+++ b/eglot-tests.el
@@ -234,11 +234,11 @@ Pass TIMEOUT to `eglot--with-timeout'."
   (skip-unless (null (getenv "TRAVIS_TESTING")))
   (let ((eglot-autoreconnect 1))
     (eglot--with-dirs-and-files
-        '(("project" . (("coiso.rs" . "bla")
-                        ("merdix.rs" . "bla"))))
+        '(("watch-project" . (("coiso.rs" . "bla")
+                              ("merdix.rs" . "bla"))))
       (eglot--with-timeout 10
         (with-current-buffer
-            (eglot--find-file-noselect "project/coiso.rs")
+            (eglot--find-file-noselect "watch-project/coiso.rs")
           (should (zerop (shell-command "cargo init")))
           (eglot--sniffing (
                             :server-requests s-requests
@@ -265,21 +265,22 @@ Pass TIMEOUT to `eglot--with-timeout'."
                           (= type 3)))))))))))
 
 (ert-deftest rls-basic-diagnostics ()
-  "Hover and highlightChanges are tricky in RLS."
+  "Test basic diagnostics in RLS."
   (skip-unless (executable-find "rls"))
   (skip-unless (executable-find "cargo"))
   (eglot--with-dirs-and-files
-      '(("project" . (("main.rs" . "bla"))))
+      '(("diag-project" . (("main.rs" . "fn main() {\nprintfoo!(\"Hello, 
world!\");\n}"))))
     (eglot--with-timeout 3
       (with-current-buffer
-          (eglot--find-file-noselect "project/main.rs")
+          (eglot--find-file-noselect "diag-project/main.rs")
         (should (zerop (shell-command "cargo init")))
         (eglot--sniffing (:server-notifications s-notifs)
-          (insert "fn main() {\nprintfoo!(\"Hello, world!\");\n}")
           (apply #'eglot (eglot--interactive))
           (eglot--wait-for (s-notifs 1)
               (&key _id method &allow-other-keys)
             (string= method "textDocument/publishDiagnostics"))
+          (flymake-start)
+          (goto-char (point-min))
           (flymake-goto-next-error)
           (should (eq 'flymake-error (face-at-point))))))))
 
@@ -287,11 +288,14 @@ Pass TIMEOUT to `eglot--with-timeout'."
   "Hover and highlightChanges are tricky in RLS."
   (skip-unless (executable-find "rls"))
   (skip-unless (executable-find "cargo"))
+  (skip-unless (null (getenv "TRAVIS_TESTING")))
   (eglot--with-dirs-and-files
-      '(("project" . (("main.rs" . "bla"))))
+      '(("hover-project" .
+         (("main.rs" .
+           "fn test() -> i32 { let test=3; return te; }"))))
     (eglot--with-timeout 3
       (with-current-buffer
-          (eglot--find-file-noselect "project/main.rs")
+          (eglot--find-file-noselect "hover-project/main.rs")
         (should (zerop (shell-command "cargo init")))
         (eglot--sniffing (
                           :server-notifications s-notifs
@@ -301,7 +305,6 @@ Pass TIMEOUT to `eglot--with-timeout'."
                           :client-replies c-replies
                           :client-requests c-reqs
                           )
-          (insert "fn test() -> i32 { let test=3; return te; }")
           (apply #'eglot (eglot--interactive))
           (goto-char (point-min))
           (search-forward "return te")
@@ -320,6 +323,31 @@ Pass TIMEOUT to `eglot--with-timeout'."
                 (&key id &allow-other-keys)
               (eq id pending-id))))))))
 
+(ert-deftest rls-rename ()
+  "Test renaming in RLS."
+  (skip-unless (executable-find "rls"))
+  (skip-unless (executable-find "cargo"))
+  (eglot--with-dirs-and-files
+      '(("rename-project"
+         . (("main.rs" .
+             "fn test() -> i32 { let test=3; return test; }"))))
+    (eglot--with-timeout 3
+      (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
+                          )
+          (apply #'eglot (eglot--interactive))
+          (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"
   (skip-unless (executable-find "pyls"))
diff --git a/eglot.el b/eglot.el
index ccb7b49..31ef081 100644
--- a/eglot.el
+++ b/eglot.el
@@ -2,7 +2,7 @@
 
 ;; Copyright (C) 2018 Free Software Foundation, Inc.
 
-;; Version: 0.4
+;; Version: 0.7
 ;; Author: João Távora <address@hidden>
 ;; Maintainer: João Távora <address@hidden>
 ;; URL: https://github.com/joaotavora/eglot
@@ -61,6 +61,7 @@
 (require 'subr-x)
 (require 'jsonrpc)
 (require 'filenotify)
+(require 'ert)
 
 
 ;;; User tweakable stuff
@@ -73,6 +74,8 @@
                                 (python-mode . ("pyls"))
                                 (js-mode . ("javascript-typescript-stdio"))
                                 (sh-mode . ("bash-language-server" "start"))
+                                (c++-mode . (eglot-cquery "cquery"))
+                                (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.
@@ -112,13 +115,21 @@ lasted more than that many seconds."
 
 ;;; API (WORK-IN-PROGRESS!)
 ;;;
-(defmacro eglot--obj (&rest what)
-  "Make WHAT a JSON object suitable for `json-encode'."
-  (declare (debug (&rest form)))
-  ;; FIXME: not really API. Should it be?
-  ;; FIXME: maybe later actually do something, for now this just fixes
-  ;; the indenting of literal plists.
-  `(list ,@what))
+(cl-defmacro eglot--with-live-buffer (buf &rest body)
+  "Check BUF live, then do BODY in it." (declare (indent 1) (debug t))
+  (let ((b (cl-gensym)))
+    `(let ((,b ,buf)) (if (buffer-live-p ,b) (with-current-buffer ,b 
,@body)))))
+
+(cl-defmacro eglot--lambda (cl-lambda-list &body body)
+  "Make a unary function of ARG, a plist-like JSON object.
+CL-LAMBDA-LIST destructures ARGS before running BODY."
+  (declare (indent 1) (debug (sexp &rest form)))
+  (let ((e (gensym "eglot--lambda-elem")))
+    `(lambda (,e) (apply (cl-function (lambda ,cl-lambda-list ,@body)) ,e))))
+
+(cl-defmacro eglot--widening (&rest body)
+  "Save excursion and restriction. Widen. Then run BODY." (declare (debug t))
+  `(save-excursion (save-restriction (widen) ,@body)))
 
 (cl-defgeneric eglot-handle-request (server method id &rest params)
   "Handle SERVER's METHOD request with ID and PARAMS.")
@@ -133,15 +144,15 @@ lasted more than that many seconds."
 (cl-defgeneric eglot-client-capabilities (server)
   "What the EGLOT LSP client supports for SERVER."
   (:method (_s)
-           (eglot--obj
-            :workspace (eglot--obj
+           (list
+            :workspace (list
                         :applyEdit t
                         :workspaceEdit `(:documentChanges :json-false)
                         :didChangeWatchesFiles `(:dynamicRegistration t)
                         :symbol `(:dynamicRegistration :json-false))
             :textDocument
-            (eglot--obj
-             :synchronization (eglot--obj
+            (list
+             :synchronization (list
                                :dynamicRegistration :json-false
                                :willSave t :willSaveWaitUntil t :didSave t)
              :completion         `(:dynamicRegistration :json-false)
@@ -153,7 +164,7 @@ lasted more than that many seconds."
              :documentHighlight  `(:dynamicRegistration :json-false)
              :rename             `(:dynamicRegistration :json-false)
              :publishDiagnostics `(:relatedInformation :json-false))
-            :experimental (eglot--obj))))
+            :experimental (list))))
 
 (defclass eglot-lsp-server (jsonrpc-process-connection)
   ((project-nickname
@@ -165,9 +176,9 @@ lasted more than that many seconds."
    (capabilities
     :documentation "JSON object containing server capabilities."
     :accessor eglot--capabilities)
-   (moribund
+   (shutdown-requested
     :documentation "Flag set when server is shutting down."
-    :accessor eglot--moribund)
+    :accessor eglot--shutdown-requested)
    (project
     :documentation "Project associated with server."
     :initarg :project :accessor eglot--project)
@@ -204,15 +215,17 @@ function with the server still running."
   (eglot--message "Asking %s politely to terminate" (jsonrpc-name server))
   (unwind-protect
       (progn
-        (setf (eglot--moribund server) t)
+        (setf (eglot--shutdown-requested server) t)
         (jsonrpc-request server :shutdown nil :timeout 3)
         ;; this one is supposed to always fail, hence ignore-errors
         (ignore-errors (jsonrpc-request server :exit nil :timeout 1)))
     ;; Turn off `eglot--managed-mode' where appropriate.
     (dolist (buffer (eglot--managed-buffers server))
       (with-current-buffer buffer (eglot--managed-mode-onoff server -1)))
-    (when (process-live-p (eglot--process server))
-      (eglot--warn "Brutally deleting non-compliant server %s" (jsonrpc-name 
server))
+    (while (progn (accept-process-output nil 0.1)
+                  (not (eq (eglot--shutdown-requested server) :sentinel-done)))
+      (eglot--warn "Sentinel for %s still hasn't run, brutally deleting it!"
+                   (eglot--process server))
       (delete-process (eglot--process server)))))
 
 (defun eglot--on-shutdown (server)
@@ -224,11 +237,12 @@ function with the server still running."
   (maphash (lambda (_id watches)
              (mapcar #'file-notify-rm-watch watches))
            (eglot--file-watches server))
-  ;; Sever the project/process relationship for proc
+  ;; Sever the project/server relationship for `server'
   (setf (gethash (eglot--project server) eglot--servers-by-project)
         (delq server
               (gethash (eglot--project server) eglot--servers-by-project)))
-  (cond ((eglot--moribund server))
+  (cond ((eglot--shutdown-requested server)
+         (setf (eglot--shutdown-requested server) :sentinel-done))
         ((not (eglot--inhibit-autoreconnect server))
          (eglot--warn "Reconnecting after unexpected server exit.")
          (eglot-reconnect server))
@@ -340,7 +354,7 @@ INTERACTIVE is t if called interactively."
              interactive
              (y-or-n-p "[eglot] Live process found, reconnect instead? "))
         (eglot-reconnect current-server interactive)
-      (when live-p (eglot-shutdown current-server))
+      (when live-p (ignore-errors (eglot-shutdown current-server)))
       (let ((server (eglot--connect project
                                     managed-major-mode
                                     (format "%s/%s" nickname 
managed-major-mode)
@@ -357,7 +371,7 @@ managing `%s' buffers in project `%s'."
 INTERACTIVE is t if called interactively."
   (interactive (list (jsonrpc-current-connection-or-lose) t))
   (when (process-live-p (eglot--process server))
-    (eglot-shutdown server interactive))
+    (ignore-errors (eglot-shutdown server interactive)))
   (eglot--connect (eglot--project server)
                   (eglot--major-mode server)
                   (jsonrpc-name server)
@@ -395,22 +409,22 @@ And NICKNAME and CONTACT."
             (jsonrpc-request
              server
              :initialize
-             (jsonrpc-obj :processId (unless (eq (process-type
-                                                  (eglot--process server))
-                                                 'network)
-                                       (emacs-pid))
-                          :rootPath  (expand-file-name
-                                      (car (project-roots project)))
-                          :rootUri  (eglot--path-to-uri
-                                     (car (project-roots project)))
-                          :initializationOptions (eglot-initialization-options 
server)
-                          :capabilities (eglot-client-capabilities server)))
+             (list :processId (unless (eq (process-type
+                                           (eglot--process server))
+                                          'network)
+                                (emacs-pid))
+                   :rootPath  (expand-file-name
+                               (car (project-roots project)))
+                   :rootUri  (eglot--path-to-uri
+                              (car (project-roots project)))
+                   :initializationOptions (eglot-initialization-options server)
+                   :capabilities (eglot-client-capabilities server)))
           (setf (eglot--capabilities server) capabilities)
           (setf (jsonrpc-status server) nil)
           (dolist (buffer (buffer-list))
             (with-current-buffer buffer
               (eglot--maybe-activate-editing-mode server)))
-          (jsonrpc-notify server :initialized (jsonrpc-obj :__dummy__ t))
+          (jsonrpc-notify server :initialized `(:__dummy__ t))
           (setf (eglot--inhibit-autoreconnect server)
                 (cond
                  ((booleanp eglot-autoreconnect) (not eglot-autoreconnect))
@@ -420,12 +434,11 @@ And NICKNAME and CONTACT."
                                     (setf (eglot--inhibit-autoreconnect server)
                                           (null eglot-autoreconnect)))))))
           (setq success server))
-      (unless (or success (not (process-live-p (eglot--process server)))
-                  (eglot--moribund server))
+      (unless (or success (not (process-live-p (eglot--process server))))
         (eglot-shutdown server)))))
 
 
-;;; Helpers
+;;; Helpers (move these to API?)
 ;;;
 (defun eglot--error (format &rest args)
   "Error out with FORMAT with ARGS."
@@ -444,9 +457,9 @@ And NICKNAME and CONTACT."
 (defun eglot--pos-to-lsp-position (&optional pos)
   "Convert point POS to LSP position."
   (save-excursion
-    (jsonrpc-obj :line (1- (line-number-at-pos pos t)) ; F!@&#$CKING OFF-BY-ONE
-                 :character (- (goto-char (or pos (point)))
-                               (line-beginning-position)))))
+    (list :line (1- (line-number-at-pos pos t)) ; F!@&#$CKING OFF-BY-ONE
+          :character (- (goto-char (or pos (point)))
+                        (line-beginning-position)))))
 
 (defun eglot--lsp-position-to-point (pos-plist &optional marker)
   "Convert LSP position POS-PLIST to Emacs point.
@@ -500,23 +513,30 @@ If optional MARKER, return a marker instead"
            finally (cl-return (or probe t))))
 
 (defun eglot--range-region (range &optional markers)
-  "Return region (BEG END) that represents LSP RANGE.
+  "Return region (BEG . END) that represents LSP RANGE.
 If optional MARKERS, make markers."
-  (list (eglot--lsp-position-to-point (plist-get range :start) markers)
-        (eglot--lsp-position-to-point (plist-get range :end) 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))))))
 
 
 ;;; Minor modes
 ;;;
 (defvar eglot-mode-map (make-sparse-keymap))
 
+(defvar-local eglot--current-flymake-report-fn nil
+  "Current flymake report function for this buffer")
+
 (define-minor-mode eglot--managed-mode
   "Mode for source buffers managed by some EGLOT project."
   nil nil eglot-mode-map
   (cond
    (eglot--managed-mode
     (add-hook 'jsonrpc-find-connection-functions 'eglot--find-current-server 
nil t)
-    (add-hook 'jsonrpc-ready-predicates 'eglot--server-ready-p nil t)
     (add-hook 'after-change-functions 'eglot--after-change nil t)
     (add-hook 'before-change-functions 'eglot--before-change nil t)
     (add-hook 'flymake-diagnostic-functions 'eglot-flymake-backend nil t)
@@ -531,7 +551,6 @@ If optional MARKERS, make markers."
     (add-function :around (local imenu-create-index-function) #'eglot-imenu))
    (t
     (remove-hook 'jsonrpc-find-connection-functions 
'eglot--find-current-server t)
-    (remove-hook 'jsonrpc-ready-predicates 'eglot--server-ready-p t)
     (remove-hook 'flymake-diagnostic-functions 'eglot-flymake-backend t)
     (remove-hook 'after-change-functions 'eglot--after-change t)
     (remove-hook 'before-change-functions 'eglot--before-change t)
@@ -543,7 +562,8 @@ If optional MARKERS, make markers."
     (remove-hook 'completion-at-point-functions #'eglot-completion-at-point t)
     (remove-function (local 'eldoc-documentation-function)
                      #'eglot-eldoc-function)
-    (remove-function (local imenu-create-index-function) #'eglot-imenu))))
+    (remove-function (local imenu-create-index-function) #'eglot-imenu)
+    (setq eglot--current-flymake-report-fn nil))))
 
 (defun eglot--managed-mode-onoff (server arg)
   "Proxy for function `eglot--managed-mode' with ARG and SERVER."
@@ -557,9 +577,6 @@ If optional MARKERS, make markers."
 (add-hook 'eglot--managed-mode-hook 'flymake-mode)
 (add-hook 'eglot--managed-mode-hook 'eldoc-mode)
 
-(defvar-local eglot--current-flymake-report-fn nil
-  "Current flymake report function for this buffer")
-
 (defun eglot--find-current-server ()
   "Find the current logical EGLOT server."
   (let* ((probe (or (project-current) `(transient . ,default-directory))))
@@ -575,9 +592,7 @@ that case, also signal textDocument/didOpen."
          (server (or (and (null server) cur) (and server (eq server cur) 
cur))))
     (when server
       (eglot--managed-mode-onoff server 1)
-      (eglot--signal-textDocument/didOpen)
-      (flymake-start)
-      (funcall (or eglot--current-flymake-report-fn #'ignore) nil))))
+      (eglot--signal-textDocument/didOpen))))
 
 (add-hook 'find-file-hook 'eglot--maybe-activate-editing-mode)
 
@@ -622,27 +637,25 @@ Uses THING, FACE, DEFS and PREPEND."
      `(,(eglot--mode-line-props "eglot" 'eglot-mode-line nil))
      (when nick
        `(":" ,(eglot--mode-line-props
-               nick'eglot-mode-line
-               '((mouse-1 eglot-events-buffer "go to events buffer")
+               nick 'eglot-mode-line
+               '((C-mouse-1 jsonrpc-stderr-buffer "go to stderr buffer")
+                 (mouse-1 eglot-events-buffer "go to events buffer")
                  (mouse-2 eglot-shutdown      "quit server")
                  (mouse-3 eglot-reconnect     "reconnect to server")))
          ,@(when serious-p
              `("/" ,(eglot--mode-line-props
                      "error" 'compilation-mode-line-fail
-                     '((mouse-1 eglot-events-buffer "go to events buffer")
-                       (mouse-3 jsonrpc-clear-status  "clear this status"))
+                     '((mouse-3 jsonrpc-clear-status  "clear this status"))
                      (format "An error occured: %s\n" status))))
          ,@(when (and doing (not done-p))
              `("/" ,(eglot--mode-line-props
                      (format "%s%s" doing
                              (if detail (format ":%s" detail) ""))
-                     'compilation-mode-line-run
-                     '((mouse-1 eglot-events-buffer "go to events buffer")))))
+                     'compilation-mode-line-run '())))
          ,@(when (cl-plusp pending)
              `("/" ,(eglot--mode-line-props
                      (format "%d oustanding requests" pending) 'warning
-                     '((mouse-1 eglot-events-buffer "go to events buffer")
-                       (mouse-3 jsonrpc-forget-pending-continuations
+                     '((mouse-3 jsonrpc-forget-pending-continuations
                                 "fahgettaboudit"))))))))))
 
 (add-to-list 'mode-line-misc-info
@@ -674,10 +687,9 @@ Uses THING, FACE, DEFS and PREPEND."
                    '("OK"))
                nil t (plist-get (elt actions 0) :title)))
       (if reply
-          (jsonrpc-reply server id :result (jsonrpc-obj :title reply))
+          (jsonrpc-reply server id :result `(:title ,reply))
         (jsonrpc-reply server id
-                       :error (jsonrpc-obj :code -32800
-                                           :message "User cancelled"))))))
+                       :error `(:code -32800 :message "User cancelled"))))))
 
 (cl-defmethod eglot-handle-notification
   (_server (_method (eql :window/logMessage)) &key _type _message)
@@ -691,7 +703,7 @@ Uses THING, FACE, DEFS and PREPEND."
   "Unreported diagnostics for this buffer.")
 
 (cl-defmethod eglot-handle-notification
-  (_server (_method (eql :textDocument/publishDiagnostics)) &key uri 
diagnostics)
+  (server (_method (eql :textDocument/publishDiagnostics)) &key uri 
diagnostics)
   "Handle notification publishDiagnostics"
   (if-let ((buffer (find-buffer-visiting (eglot--uri-to-path uri))))
       (with-current-buffer buffer
@@ -700,7 +712,7 @@ Uses THING, FACE, DEFS and PREPEND."
          collect (cl-destructuring-bind (&key range severity _group
                                               _code source message)
                      diag-spec
-                   (pcase-let ((`(,beg ,end) (eglot--range-region range)))
+                   (pcase-let ((`(,beg . ,end) (eglot--range-region range)))
                      (flymake-make-diagnostic (current-buffer)
                                               beg end
                                               (cond ((<= severity 1) :error)
@@ -712,8 +724,8 @@ Uses THING, FACE, DEFS and PREPEND."
                         (funcall eglot--current-flymake-report-fn diags)
                         (setq eglot--unreported-diagnostics nil))
                        (t
-                        (setq eglot--unreported-diagnostics diags)))))
-    (eglot--warn "Diagnostics received for unvisited %s" uri)))
+                        (setq eglot--unreported-diagnostics (cons t diags))))))
+    (jsonrpc--debug server "Diagnostics received for unvisited %s" uri)))
 
 (cl-defun eglot--register-unregister (server jsonrpc-id things how)
   "Helper for `registerCapability'.
@@ -729,7 +741,7 @@ THINGS are either registrations or unregisterations."
               (jsonrpc-reply
                server jsonrpc-id
                :error `(:code -32601 :message ,(or (cadr retval) 
"sorry")))))))))
-  (jsonrpc-reply server jsonrpc-id :result (jsonrpc-obj :message "OK")))
+  (jsonrpc-reply server jsonrpc-id :result `(:message "OK")))
 
 (cl-defmethod eglot-handle-request
   (server id (_method (eql :client/registerCapability)) &key registrations)
@@ -750,52 +762,44 @@ THINGS are either registrations or unregisterations."
              (jsonrpc-reply server id :result `(:applied )))
     (error (jsonrpc-reply server id
                           :result `(:applied :json-false)
-                          :error (eglot--obj :code -32001
-                                             :message (format "%s" err))))))
+                          :error `(:code -32001 :message (format "%s" 
,err))))))
 
 (defun eglot--TextDocumentIdentifier ()
   "Compute TextDocumentIdentifier object for current buffer."
-  (jsonrpc-obj :uri (eglot--path-to-uri buffer-file-name)))
+  `(:uri ,(eglot--path-to-uri buffer-file-name)))
 
 (defvar-local eglot--versioned-identifier 0)
 
 (defun eglot--VersionedTextDocumentIdentifier ()
   "Compute VersionedTextDocumentIdentifier object for current buffer."
   (append (eglot--TextDocumentIdentifier)
-          (jsonrpc-obj :version eglot--versioned-identifier)))
+          `(:version ,eglot--versioned-identifier)))
 
 (defun eglot--TextDocumentItem ()
   "Compute TextDocumentItem object for current buffer."
   (append
    (eglot--VersionedTextDocumentIdentifier)
-   (jsonrpc-obj :languageId
-                (if (string-match "\\(.*\\)-mode" (symbol-name major-mode))
-                    (match-string 1 (symbol-name major-mode))
-                  "unknown")
-                :text
-                (save-restriction
-                  (widen)
-                  (buffer-substring-no-properties (point-min) (point-max))))))
+   (list :languageId
+         (if (string-match "\\(.*\\)-mode" (symbol-name major-mode))
+             (match-string 1 (symbol-name major-mode))
+           "unknown")
+         :text
+         (eglot--widening
+          (buffer-substring-no-properties (point-min) (point-max))))))
 
 (defun eglot--TextDocumentPositionParams ()
   "Compute TextDocumentPositionParams."
-  (jsonrpc-obj :textDocument (eglot--TextDocumentIdentifier)
-               :position (eglot--pos-to-lsp-position)))
+  (list :textDocument (eglot--TextDocumentIdentifier)
+        :position (eglot--pos-to-lsp-position)))
 
 (defvar-local eglot--recent-changes nil
   "Recent buffer changes as collected by `eglot--before-change'.")
 
-(defun eglot--outstanding-edits-p ()
-  "Non-nil if there are outstanding edits."
-  (cl-plusp (+ (length (car eglot--recent-changes))
-               (length (cdr eglot--recent-changes)))))
-
 (cl-defmethod jsonrpc-connection-ready-p ((_server eglot-lsp-server) _what)
-  "Tell if SERVER is ready for WHAT in current buffer.
-If it isn't, a deferrable `eglot--async-request' *will* be
-deferred to the future."
-  (and (cl-call-next-method)
-       (not (eglot--outstanding-edits-p))))
+  "Tell if SERVER is ready for WHAT in current buffer."
+  (and (cl-call-next-method) (not eglot--recent-changes)))
+
+(defvar-local eglot--change-idle-timer nil "Idle timer for didChange signals.")
 
 (defun eglot--before-change (start end)
   "Hook onto `before-change-functions'.
@@ -803,23 +807,32 @@ Records START and END, crucially convert them into
 LSP (line/char) positions before that information is
 lost (because the after-change thingy doesn't know if newlines
 were deleted/added)"
-  (setf (car eglot--recent-changes)
-        (vconcat (car eglot--recent-changes)
-                 `[(,(eglot--pos-to-lsp-position start)
-                    ,(eglot--pos-to-lsp-position end))])))
+  (when (listp eglot--recent-changes)
+    (push `(,(eglot--pos-to-lsp-position start)
+            ,(eglot--pos-to-lsp-position end))
+          eglot--recent-changes)))
 
 (defun eglot--after-change (start end pre-change-length)
   "Hook onto `after-change-functions'.
 Records START, END and PRE-CHANGE-LENGTH locally."
   (cl-incf eglot--versioned-identifier)
-  (setf (cdr eglot--recent-changes)
-        (vconcat (cdr eglot--recent-changes)
-                 `[(,pre-change-length
-                    ,(buffer-substring-no-properties start end))])))
+  (if (and (listp eglot--recent-changes)
+           (null (cddr (car eglot--recent-changes))))
+      (setf (cddr (car eglot--recent-changes))
+            `(,pre-change-length ,(buffer-substring-no-properties start end)))
+    (setf eglot--recent-changes :emacs-messup))
+  (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer))
+  (let ((buf (current-buffer)))
+    (setq eglot--change-idle-timer
+          (run-with-idle-timer
+           0.5 nil (lambda () (eglot--with-live-buffer buf
+                                (when eglot--managed-mode
+                                  (eglot--signal-textDocument/didChange)
+                                  (setq eglot--change-idle-timer nil))))))))
 
 ;; HACK! Launching a deferred sync request with outstanding changes is a
 ;; bad idea, since that might lead to the request never having a
-;; chance to run, because `jsonrpc-ready-predicates'.
+;; chance to run, because `jsonrpc-connection-ready-p'.
 (advice-add #'jsonrpc-request :before
             (cl-function (lambda (_proc _method _params &key deferred _timeout)
                            (when (and eglot--managed-mode deferred)
@@ -828,40 +841,30 @@ Records START, END and PRE-CHANGE-LENGTH locally."
 
 (defun eglot--signal-textDocument/didChange ()
   "Send textDocument/didChange to server."
-  (when (eglot--outstanding-edits-p)
+  (when eglot--recent-changes
     (let* ((server (jsonrpc-current-connection-or-lose))
            (sync-kind (eglot--server-capable :textDocumentSync))
-           (emacs-messup (/= (length (car eglot--recent-changes))
-                             (length (cdr eglot--recent-changes))))
-           (full-sync-p (or (eq sync-kind 1) emacs-messup)))
-      (when emacs-messup
-        (eglot--warn "`eglot--recent-changes' messup: %s" 
eglot--recent-changes))
-      (save-restriction
-        (widen)
-        (jsonrpc-notify
-         server :textDocument/didChange
-         (jsonrpc-obj
-          :textDocument
-          (eglot--VersionedTextDocumentIdentifier)
-          :contentChanges
-          (if full-sync-p (vector
-                           (jsonrpc-obj
-                            :text (buffer-substring-no-properties (point-min)
-                                                                  
(point-max))))
-            (cl-loop for (start-pos end-pos) across (car eglot--recent-changes)
-                     for (len after-text) across (cdr eglot--recent-changes)
-                     vconcat `[,(jsonrpc-obj :range (jsonrpc-obj :start 
start-pos
-                                                                 :end end-pos)
-                                             :rangeLength len
-                                             :text after-text)])))))
-      (setq eglot--recent-changes (cons [] []))
+           (full-sync-p (or (eq sync-kind 1)
+                            (eq :emacs-messup eglot--recent-changes))))
+      (jsonrpc-notify
+       server :textDocument/didChange
+       (list
+        :textDocument (eglot--VersionedTextDocumentIdentifier)
+        :contentChanges
+        (if full-sync-p
+            (vector `(:text ,(eglot--widening
+                              (buffer-substring-no-properties (point-min)
+                                                              (point-max)))))
+          (cl-loop for (beg end len text) in (reverse eglot--recent-changes)
+                   vconcat `[,(list :range `(:start ,beg :end ,end)
+                                    :rangeLength len :text text)]))))
+      (setq eglot--recent-changes nil)
       (setf (eglot--spinner server) (list nil :textDocument/didChange t))
-      ;; HACK! perhaps jsonrpc should just call this on every send
       (jsonrpc--call-deferred server))))
 
 (defun eglot--signal-textDocument/didOpen ()
   "Send textDocument/didOpen to server."
-  (setq eglot--recent-changes (cons [] []))
+  (setq eglot--recent-changes nil eglot--versioned-identifier 0)
   (jsonrpc-notify
    (jsonrpc-current-connection-or-lose)
    :textDocument/didOpen `(:textDocument ,(eglot--TextDocumentItem))))
@@ -888,7 +891,7 @@ Records START, END and PRE-CHANGE-LENGTH locally."
   (jsonrpc-notify
    (jsonrpc-current-connection-or-lose)
    :textDocument/didSave
-   (jsonrpc-obj
+   (list
     ;; TODO: Handle TextDocumentSaveRegistrationOptions to control this.
     :text (buffer-substring-no-properties (point-min) (point-max))
     :textDocument (eglot--TextDocumentIdentifier))))
@@ -899,10 +902,8 @@ Calls REPORT-FN maybe if server publishes diagnostics in 
time."
   (setq eglot--current-flymake-report-fn report-fn)
   ;; Report anything unreported
   (when eglot--unreported-diagnostics
-    (funcall report-fn eglot--unreported-diagnostics)
-    (setq eglot--unreported-diagnostics nil))
-  ;; Signal a didChange that might eventually bring new diagnotics
-  (eglot--signal-textDocument/didChange))
+    (funcall report-fn (cdr eglot--unreported-diagnostics))
+    (setq eglot--unreported-diagnostics nil)))
 
 (defun eglot-xref-backend ()
   "EGLOT xref backend."
@@ -938,17 +939,16 @@ DUMMY is ignored"
                     (&key name kind location containerName)
                   (propertize name
                               :textDocumentPositionParams
-                              (jsonrpc-obj :textDocument text-id
-                                           :position (plist-get
-                                                      (plist-get location 
:range)
-                                                      :start))
+                              (list :textDocument text-id
+                                    :position (plist-get
+                                               (plist-get location :range)
+                                               :start))
                               :locations (list location)
                               :kind kind
                               :containerName containerName))
                 (jsonrpc-request server
                                  :textDocument/documentSymbol
-                                 (jsonrpc-obj
-                                  :textDocument text-id))))
+                                 `(:textDocument ,text-id))))
          (all-completions string eglot--xref-known-symbols))))))
 
 (cl-defmethod xref-backend-identifier-at-point ((_backend (eql eglot)))
@@ -987,8 +987,8 @@ DUMMY is ignored"
                       :textDocument/references
                       (append
                        params
-                       (jsonrpc-obj :context
-                                    (jsonrpc-obj :includeDeclaration t)))))))
+                       (list :context
+                             (list :includeDeclaration t)))))))
 
 (cl-defmethod xref-backend-apropos ((_backend (eql eglot)) pattern)
   (when (eglot--server-capable :workspaceSymbolProvider)
@@ -998,7 +998,7 @@ DUMMY is ignored"
          (eglot--xref-make name uri (plist-get range :start))))
      (jsonrpc-request (jsonrpc-current-connection-or-lose)
                       :workspace/symbol
-                      (jsonrpc-obj :query pattern)))))
+                      `(:query ,pattern)))))
 
 (defun eglot-completion-at-point ()
   "EGLOT's `completion-at-point' function."
@@ -1055,11 +1055,12 @@ DUMMY is ignored"
 (defvar eglot--highlights nil "Overlays for textDocument/documentHighlight.")
 
 (defun eglot--hover-info (contents &optional range)
-  (concat (and range (pcase-let ((`(,beg ,end) (eglot--range-region range)))
-                       (concat (buffer-substring beg end)  ": ")))
-          (mapconcat #'eglot--format-markup
-                     (append (cond ((vectorp contents) contents)
-                                   (contents (list contents)))) "\n")))
+  (let ((heading (and range (pcase-let ((`(,beg . ,end) (eglot--range-region 
range)))
+                              (concat (buffer-substring beg end)  ": "))))
+        (body (mapconcat #'eglot--format-markup
+                         (append (cond ((vectorp contents) contents)
+                                       ((stringp contents) (list contents)))) 
"\n")))
+    (when (or heading (cl-plusp (length body))) (concat heading body))))
 
 (defun eglot--sig-info (sigs active-sig active-param)
   (cl-loop
@@ -1090,9 +1091,9 @@ DUMMY is ignored"
       (jsonrpc-request (jsonrpc-current-connection-or-lose) :textDocument/hover
                        (eglot--TextDocumentPositionParams))
     (when (seq-empty-p contents) (eglot--error "No hover info here"))
-    (with-help-window "*eglot help*"
-      (with-current-buffer standard-output
-        (insert (eglot--hover-info contents range))))))
+    (let ((blurb (eglot--hover-info contents range)))
+      (with-help-window "*eglot help*"
+        (with-current-buffer standard-output (insert blurb))))))
 
 (defun eglot-eldoc-function ()
   "EGLOT's `eldoc-documentation-function' function.
@@ -1102,8 +1103,9 @@ If SKIP-SIGNATURE, don't try to send 
textDocument/signatureHelp."
          (position-params (eglot--TextDocumentPositionParams))
          sig-showing)
     (cl-macrolet ((when-buffer-window
-                   (&body body) `(when (get-buffer-window buffer)
-                                   (with-current-buffer buffer ,@body))))
+                   (&body body) ; notice the exception when testing with `ert'
+                   `(when (or (get-buffer-window buffer) (ert-running-test))
+                      (with-current-buffer buffer ,@body))))
       (when (eglot--server-capable :signatureHelpProvider)
         (jsonrpc-async-request
          server :textDocument/signatureHelp position-params
@@ -1122,10 +1124,10 @@ If SKIP-SIGNATURE, don't try to send 
textDocument/signatureHelp."
          server :textDocument/hover position-params
          :success-fn (jsonrpc-lambda (&key contents range)
                        (unless sig-showing
-                         ;; for eglot-tests.el's sake, set this unconditionally
-                         (setq eldoc-last-message
-                               (eglot--hover-info contents range))
-                         (when-buffer-window (eldoc-message 
eldoc-last-message))))
+                         (when-buffer-window
+                          (when-let (info (eglot--hover-info contents range))
+                            (eglot--message "OK so info is %S and %S" info 
(null info))
+                            (eldoc-message info)))))
          :deferred :textDocument/hover))
       (when (eglot--server-capable :documentHighlightProvider)
         (jsonrpc-async-request
@@ -1136,7 +1138,7 @@ If SKIP-SIGNATURE, don't try to send 
textDocument/signatureHelp."
            (setq eglot--highlights
                  (when-buffer-window
                   (mapcar
-                   (jsonrpc-lambda (&key range _kind)
+                   (jsonrpc-lambda (&key range _kind _role)
                      (pcase-let ((`(,beg ,end)
                                   (eglot--range-region range)))
                        (let ((ov (make-overlay beg end)))
@@ -1159,8 +1161,7 @@ If SKIP-SIGNATURE, don't try to send 
textDocument/signatureHelp."
                        (plist-get (plist-get location :range) :start))))
               (jsonrpc-request (jsonrpc-current-connection-or-lose)
                                :textDocument/documentSymbol
-                               (jsonrpc-obj
-                                :textDocument 
(eglot--TextDocumentIdentifier))))))
+                               `(:textDocument 
,(eglot--TextDocumentIdentifier))))))
         (append
          (seq-group-by (lambda (e) (get-text-property 0 :kind (car e)))
                        entries)
@@ -1172,51 +1173,40 @@ If SKIP-SIGNATURE, don't try to send 
textDocument/signatureHelp."
   (unless (or (not version) (equal version eglot--versioned-identifier))
     (eglot--error "Edits on `%s' require version %d, you have %d"
                   (current-buffer) version eglot--versioned-identifier))
-  (save-restriction
-    (widen)
-    (save-excursion
-      (mapc (jsonrpc-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--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)))
 
 (defun eglot--apply-workspace-edit (wedit &optional confirm)
   "Apply the workspace edit WEDIT.  If CONFIRM, ask user first."
-  (let (prepared)
-    (cl-destructuring-bind (&key changes documentChanges)
-        wedit
-      (cl-loop
-       for change on documentChanges
-       do (push (cl-destructuring-bind (&key textDocument edits) change
-                  (cl-destructuring-bind (&key uri version) textDocument
-                    (list (eglot--uri-to-path uri) edits version)))
-                prepared))
+  (cl-destructuring-bind (&key changes documentChanges) wedit
+    (let ((prepared
+           (mapcar (jsonrpc-lambda (&key textDocument edits)
+                     (cl-destructuring-bind (&key uri version) textDocument
+                       (list (eglot--uri-to-path uri) edits version)))
+                   documentChanges)))
       (cl-loop for (uri edits) on changes by #'cddr
-               do (push (list (eglot--uri-to-path uri) edits) prepared)))
-    (if (or confirm
-            (cl-notevery #'find-buffer-visiting
-                         (mapcar #'car prepared)))
-        (unless (y-or-n-p
-                 (format "[eglot] Server requests to edit %s files.\n  %s\n\
-Proceed? "
-                         (length prepared)
-                         (mapconcat #'identity
-                                    (mapcar #'car prepared)
-                                    "\n  ")))
-          (eglot--error "User cancelled server edit")))
-    (unwind-protect
-        (let (edit)
-          (while (setq edit (car prepared))
-            (cl-destructuring-bind (path edits &optional version) edit
-              (with-current-buffer (find-file-noselect path)
-                (eglot--apply-text-edits edits version))
-              (pop prepared))))
-      (if prepared
-          (eglot--warn "Caution: edits of files %s failed."
-                       (mapcar #'car prepared))
-        (eglot--message "Edit successful!")))))
+               do (push (list (eglot--uri-to-path uri) edits) prepared))
+      (if (or confirm
+              (cl-notevery #'find-buffer-visiting
+                           (mapcar #'car prepared)))
+          (unless (y-or-n-p
+                   (format "[eglot] Server wants to edit:\n  %s\n Proceed? "
+                           (mapconcat #'identity (mapcar #'car prepared) "\n  
")))
+            (eglot--error "User cancelled server edit")))
+      (unwind-protect
+          (let (edit) (while (setq edit (car prepared))
+                        (cl-destructuring-bind (path edits &optional version) 
edit
+                          (with-current-buffer (find-file-noselect path)
+                            (eglot--apply-text-edits edits version))
+                          (pop prepared))))
+        (if prepared (eglot--warn "Caution: edits of files %s failed."
+                                  (mapcar #'car prepared))
+          (eglot--message "Edit successful!"))))))
 
 (defun eglot-rename (newname)
   "Rename the current symbol to NEWNAME."
@@ -1227,7 +1217,7 @@ Proceed? "
   (eglot--apply-workspace-edit
    (jsonrpc-request (jsonrpc-current-connection-or-lose)
                     :textDocument/rename 
`(,@(eglot--TextDocumentPositionParams)
-                                           ,@(jsonrpc-obj :newName newname)))
+                                           :newName ,newname))
    current-prefix-arg))
 
 
@@ -1290,12 +1280,35 @@ Proceed? "
   ((server eglot-rls) (_method (eql :window/progress))
    &key id done title message &allow-other-keys)
   "Handle notification window/progress"
-  (setf (eglot--spinner server) (list id title done message))
-  (when (and (equal "Indexing" title) done)
-    (dolist (buffer (eglot--managed-buffers server))
-      (with-current-buffer buffer
-        (funcall (or eglot--current-flymake-report-fn #'ignore)
-                 eglot--unreported-diagnostics)))))
+  (setf (eglot--spinner server) (list id title done message)))
+
+
+;;; cquery-specific
+;;;
+(defclass eglot-cquery (eglot-lsp-server) ()
+  :documentation "cquery's C/C++ langserver.")
+
+(cl-defmethod eglot-initialization-options ((server eglot-cquery))
+  "Passes through required cquery initialization options"
+  (let* ((root (car (project-roots (eglot--project server))))
+         (cache (expand-file-name ".cquery_cached_index/" root)))
+    (vector :cacheDirectory (file-name-as-directory cache)
+            :progressReportFrequencyMs -1)))
+
+(cl-defmethod eglot-handle-notification
+  ((_server eglot-cquery) (_method (eql :$cquery/progress))
+   &rest counts &key _activeThreads &allow-other-keys)
+  "No-op for noisy $cquery/progress extension")
+
+(cl-defmethod eglot-handle-notification
+  ((_server eglot-cquery) (_method (eql :$cquery/setInactiveRegions))
+   &key _uri _inactiveRegions &allow-other-keys)
+  "No-op for unsupported $cquery/setInactiveRegions extension")
+
+(cl-defmethod eglot-handle-notification
+  ((_server eglot-cquery) (_method (eql :$cquery/publishSemanticHighlighting))
+   &key _uri _symbols &allow-other-keys)
+  "No-op for unsupported $cquery/publishSemanticHighlighting extension")
 
 (provide 'eglot)
 ;;; eglot.el ends here
diff --git a/jsonrpc.el b/jsonrpc.el
index 5a869aa..2bcff2e 100644
--- a/jsonrpc.el
+++ b/jsonrpc.el
@@ -162,6 +162,11 @@ FORMAT as the message."
   "Message out with FORMAT with ARGS."
   (message "[jsonrpc] %s" (concat "[jsonrpc] %s" (apply #'format format 
args))))
 
+(defun jsonrpc--debug (server format &rest args)
+  "Debug message for SERVER with FORMAT and ARGS."
+  (jsonrpc-log-event
+   server (if (stringp format)`(:message ,(format format args)) format)))
+
 (defun jsonrpc-warn (format &rest args)
   "Warning message with FORMAT and ARGS."
   (apply #'jsonrpc-message (concat "(warning) " format) args)
@@ -199,7 +204,9 @@ FORMAT as the message."
    (-deferred-actions
     :initform (make-hash-table :test #'equal)
     :accessor jsonrpc--deferred-actions
-    :documentation "Actions deferred to when server is thought to be ready.")))
+    :documentation "Map (DEFERRED BUF) to (FN TIMER ID).  FN is\
+a saved DEFERRED `async-request' from BUF, to be sent not later\
+than TIMER as ID.")))
 
 (defclass jsonrpc-process-connection (jsonrpc-connection)
   ((-process
@@ -268,6 +275,7 @@ If successful, `jsonrpc-connect' returns a
 endpoint."
   (let* ((readable-name (format "JSON-RPC server (%s)" name))
          (buffer (get-buffer-create (format "*%s output*" readable-name)))
+         (stderr)
          (original-contact contact)
          (connection
           (cond
@@ -288,10 +296,12 @@ endpoint."
                                   :command contact
                                   :connection-type 'pipe
                                   :coding 'no-conversion
-                                  :stderr (get-buffer-create
-                                           (format "*%s stderr*" name)))))))))
+                                  :stderr (setq stderr
+                                                (get-buffer-create
+                                                 (format "*%s stderr*" 
name))))))))))
          (proc (jsonrpc--process connection)))
     (set-process-buffer proc buffer)
+    (process-put proc 'jsonrpc-stderr stderr)
     (set-marker (process-mark proc) (with-current-buffer buffer (point-min)))
     (set-process-filter proc #'jsonrpc--process-filter)
     (set-process-sentinel proc #'jsonrpc--process-sentinel)
@@ -308,7 +318,7 @@ endpoint."
 (defun jsonrpc--process-sentinel (proc change)
   "Called when PROC undergoes CHANGE."
   (let ((connection (process-get proc 'jsonrpc-connection)))
-    (jsonrpc-log-event connection `(:message "Connection state changed" 
:change ,change))
+    (jsonrpc--debug connection `(:message "Connection state changed" :change 
,change))
     (when (not (process-live-p proc))
       (with-current-buffer (jsonrpc-events-buffer connection)
         (let ((inhibit-read-only t))
@@ -325,12 +335,8 @@ endpoint."
                        (funcall error `(:code -1 :message "Server died"))))
                    (jsonrpc--request-continuations connection))
         (jsonrpc-message "Server exited with status %s" (process-exit-status 
proc))
-        (unwind-protect
-            (funcall (jsonrpc--on-shutdown connection) connection))
-        (when (process-live-p proc)
-          (jsonrpc-warn "Brutally deleting non-compliant %s"
-                        (jsonrpc-name connection))
-          (delete-process proc))))))
+        (delete-process proc)
+        (funcall (jsonrpc--on-shutdown connection) connection)))))
 
 (defun jsonrpc--process-filter (proc string)
   "Called when new data STRING has arrived for PROC."
@@ -421,6 +427,12 @@ INTERACTIVE is t if called interactively."
     (when interactive (display-buffer buffer))
     buffer))
 
+(defun jsonrpc-stderr-buffer (connection)
+  "Pop to stderr of CONNECTION, if it exists, else error."
+  (interactive (list (jsonrpc-current-connection-or-lose)))
+  (if-let ((b (process-get (jsonrpc--process connection) 'jsonrpc-stderr)))
+      (pop-to-buffer b) (user-error "[eglot] No stderr buffer!")))
+
 (defun jsonrpc-log-event (connection message &optional type)
   "Log an jsonrpc-related event.
 CONNECTION is the current connection.  MESSAGE is a JSON-like
@@ -501,12 +513,6 @@ originated."
                                  json))
     (jsonrpc-log-event connection message 'client)))
 
-(defvar jsonrpc--next-request-id 0)
-
-(defun jsonrpc--next-request-id ()
-  "Compute the next id for a client request."
-  (setq jsonrpc--next-request-id (1+ jsonrpc--next-request-id)))
-
 (defun jsonrpc-forget-pending-continuations (connection)
   "Stop waiting for responses from the current JSONRPC CONNECTION."
   (interactive (list (jsonrpc-current-connection-or-lose)))
@@ -520,7 +526,7 @@ originated."
 (defun jsonrpc--call-deferred (connection)
   "Call CONNECTION's deferred actions, who may again defer themselves."
   (when-let ((actions (hash-table-values (jsonrpc--deferred-actions 
connection))))
-    (jsonrpc-log-event connection `(:running-deferred ,(length actions)))
+    (jsonrpc--debug connection `(:maybe-run-deferred ,(mapcar #'caddr 
actions)))
     (mapc #'funcall (mapcar #'car actions))))
 
 (cl-defgeneric jsonrpc-connection-ready-p (connection what) ;; API
@@ -538,6 +544,9 @@ for sending requests immediately."
 (defconst jrpc-default-request-timeout 10
   "Time in seconds before timing out a JSONRPC request.")
 
+(defvar-local jsonrpc--next-request-id 0)
+
+
 (cl-defun jsonrpc-async-request (connection
                                  method
                                  params
@@ -559,9 +568,9 @@ ERROR-FN and TIMEOUT-FN simply log the events into
 
 If DEFERRED is non-nil, maybe defer the request to a future time
 when the server is thought to be ready according to
-`jsonrpc-ready-predicates' (which see).  The request might never be
-sent at all, in case it is overridden in the meantime by a new
-request with identical DEFERRED and for the same buffer.
+`jsonrpc-connection-ready-p' (which see).  The request might
+never be sent at all, in case it is overridden in the meantime by
+a new request with identical DEFERRED and for the same buffer.
 However, in that situation, the original timeout is kept.
 
 Returns nil."
@@ -580,43 +589,40 @@ Returns nil."
 Return a list (ID TIMER). ID is the new request's ID, or nil if
 the request was deferred. TIMER is a timer object set (or nil, if
 TIMEOUT is nil)."
-  (let* ((id (jsonrpc--next-request-id))
-         (timer nil)
-         (make-timer
-          (lambda ( )
-            (or timer
-                (when timeout
-                  (run-with-timer
-                   timeout nil
-                   (lambda ()
-                     (remhash id (jsonrpc--request-continuations connection))
-                     (funcall (or timeout-fn
-                                  (lambda ()
-                                    (jsonrpc-log-event
-                                     connection `(:timed-out ,method :id ,id
-                                                             :params 
,params))))))))))))
+  (pcase-let* ((buf (current-buffer)) (point (point))
+               (`(,_ ,timer ,old-id)
+                (and deferred (gethash (list deferred buf)
+                                       (jsonrpc--deferred-actions 
connection))))
+               (id (or old-id (cl-incf jsonrpc--next-request-id)))
+               (make-timer
+                (lambda ( )
+                  (when timeout
+                    (run-with-timer
+                     timeout nil
+                     (lambda ()
+                       (remhash id (jsonrpc--request-continuations connection))
+                       (if timeout-fn (funcall timeout-fn)
+                         (jsonrpc--debug
+                          connection `(:timed-out ,method :id ,id
+                                                  :params ,params)))))))))
     (when deferred
-      (let* ((buf (current-buffer))
-             (existing (gethash (list deferred buf)
-                                (jsonrpc--deferred-actions connection))))
-        (when existing (setq timer (cadr existing)))
-        (if (jsonrpc-connection-ready-p connection deferred)
-            (remhash (list deferred buf) (jsonrpc--deferred-actions 
connection))
-          (jsonrpc-log-event connection `(:deferring ,method :id ,id :params
-                                                     ,params))
-          (let* ((buf (current-buffer)) (point (point))
-                 (later (lambda ()
-                          (when (buffer-live-p buf)
-                            (with-current-buffer buf
-                              (save-excursion (goto-char point)
-                                              (apply #'jsonrpc-async-request
-                                                     connection
-                                                     method params args)))))))
-            (puthash (list deferred buf)
-                     (list later (setq timer (funcall make-timer)))
-                     (jsonrpc--deferred-actions connection))
-            ;; Non-local exit!
-            (cl-return-from jsonrpc--async-request-1 (list nil timer))))))
+      (if (jsonrpc-connection-ready-p connection deferred)
+          ;; Server is ready, we jump below and send it immediately.
+          (remhash (list deferred buf) (jsonrpc--deferred-actions connection))
+        ;; Otherwise, save in `eglot--deferred-actions' and exit non-locally
+        (unless old-id
+          (jsonrpc--debug connection `(:deferring ,method :id ,id :params
+                                                  ,params)))
+        (puthash (list deferred buf)
+                 (list (lambda ()
+                         (when (buffer-live-p buf)
+                           (with-current-buffer buf
+                             (save-excursion (goto-char point)
+                                             (apply #'jsonrpc-async-request
+                                                    connection
+                                                    method params args)))))
+                       (or timer (funcall make-timer)) id)
+                 (jsonrpc--deferred-actions connection))))
     ;; Really send it
     ;;
     (jsonrpc-connection-send connection (jsonrpc-obj :jsonrpc "2.0"
@@ -626,12 +632,12 @@ TIMEOUT is nil)."
     (puthash id
              (list (or success-fn
                        (jsonrpc-lambda (&rest _ignored)
-                         (jsonrpc-log-event
+                         (jsonrpc--debug
                           connection (jsonrpc-obj :message "success ignored" 
:id id))))
                    (or error-fn
                        (jsonrpc-lambda (&key code message &allow-other-keys)
                          (setf (jsonrpc-status connection) `(,message t))
-                         (jsonrpc-log-event
+                         (jsonrpc--debug
                           connection (jsonrpc-obj :message "error ignored, 
status set"
                                                   :id id :error code))))
                    (setq timer (funcall make-timer)))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]