[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/eglot 4525eca 43/69: Support json.c. API purely based o
From: |
João Távora |
Subject: |
[elpa] externals/eglot 4525eca 43/69: Support json.c. API purely based on classes |
Date: |
Fri, 22 Jun 2018 11:55:01 -0400 (EDT) |
branch: externals/eglot
commit 4525eca32a10046484b5a6dd1f0647927aae2dd6
Author: João Távora <address@hidden>
Commit: João Távora <address@hidden>
Support json.c. API purely based on classes
No more jsonrpc-connect.
This is a big commit because of a data loss problem. It should be at
least two separate commits (json.c-support and new API)
* eglot.el (eglot-server-programs): Rework docstring.
(eglot-handle-request): Don't take ID param
(eglot-lsp-server): No more initargs.
(eglot--interactive): Return 5 args.
(eglot): Take 5 args.
(eglot-reconnect): Pass 6 args to eglot--connect.
(eglot--dispatch): Remove.
(eglot--connect): Take 6 args. Rework.
(eglot-handle-notification): Change all specializations
to use a non-keyword symbol spec.
(eglot-handle-request): Remove ID param from all
specializations. Don't pass ID to jsonrpc-reply.
(eglot--register-unregister): Don't take JSONRPC-ID arg.
Don't pass ID to jsonrpc-reply.
* jsonrpc-tests.el (returns-3, signals-an--32603-JSONRPC-error)
(times-out, stretching-it-but-works)
(json-el-cant-serialize-this, jsonrpc-connection-ready-p)
(deferred-action-intime, deferred-action-toolate)
(deferred-action-timeout): Pass JSON objects compatible with
json.c
(jsonrpc--test-client, jsonrpc--test-endpoint): New classes
(jsonrpc--with-emacsrpc-fixture): Don't use jsonrpc-connect.
(jsonrpc-connection-ready-p): Update signature.
* jsonrpc.el: Rewrite commentary.
(jsonrpc-connection): Rework class.
(jsonrpc-process-connection): Rework class.
(initialize-instance): New methods..
(jsonrpc--json-read, jsonrpc--json-encode): Reindent.
(jsonrpc-connect): Delete.
(jsonrpc--json-read, jsonrpc--json-encode):
New functions for working with json.c
(jsonrpc--process-filter): Call them.
(jsonrpc--unanswered-request-id): New variable.
(jsonrpc--connection-receive): Use jsonrpc--unanswered-request-id
(jsonrpc-connection-send): Take keyword params to build message
instead of message.
(jsonrpc-notify, jsonrpc--async-request-1): Use new
jsonrpc-connection-send.
(jsonrpc-reply): Simplify.
* eglot-tests.el (rls-watches-files, rls-basic-diagnostics)
(rls-hover-after-edit): Correctly compare using string= and
non-keyword symbols.
---
eglot-tests.el | 8 +-
eglot.el | 171 ++++++++++++----------
jsonrpc-tests.el | 65 +++++----
jsonrpc.el | 429 ++++++++++++++++++++++++-------------------------------
4 files changed, 324 insertions(+), 349 deletions(-)
diff --git a/eglot-tests.el b/eglot-tests.el
index af8cc37..bd9e2f3 100644
--- a/eglot-tests.el
+++ b/eglot-tests.el
@@ -250,7 +250,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))))
@@ -258,7 +258,7 @@ Pass TIMEOUT to `eglot--with-timeout'."
(eglot--wait-for
(c-notifs 3 "waiting for didChangeWatchedFiles notification")
(&key method params &allow-other-keys)
- (and (eq 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)
@@ -278,7 +278,7 @@ Pass TIMEOUT to `eglot--with-timeout'."
(apply #'eglot (eglot--interactive))
(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)
@@ -318,7 +318,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))))))))
diff --git a/eglot.el b/eglot.el
index d5498f9..e1592ab 100644
--- a/eglot.el
+++ b/eglot.el
@@ -79,8 +79,8 @@
(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 . SPEC) pair. MAJOR-MODE is a
-mode symbol. SPEC is
+An association list of (MAJOR-MODE . CONTACT) pair. MAJOR-MODE
+is a mode symbol. CONTACT is:
* In the most common case, a list of strings (PROGRAM [ARGS...]).
PROGRAM is called with ARGS and is expected to serve LSP requests
@@ -91,12 +91,15 @@ a positive integer number for connecting to a server via
TCP.
Remaining ARGS are passed to `open-network-stream' for upgrading
the connection with encryption or other capabilities.
-* A function of no arguments returning a connected process.
-
-* A cons (CLASS-NAME . SPEC) where CLASS-NAME is a symbol
-designating a subclass of symbol `eglot-lsp-server', for
-representing experimental LSP servers. In this case SPEC is
-interpreted as described above this point.")
+* A cons (CLASS-NAME . INITARGS) where CLASS-NAME is a symbol
+designating a subclass of `eglot-lsp-server', for representing
+experimental LSP servers. INITARGS is a keyword-value plist used
+to initialize CLASS-NAME, or a plain list interpreted as the
+previous descriptions of CONTACT, in which case it is converted
+to produce a plist with a suitable :PROCESS initarg to
+CLASS-NAME. The class `eglot-lsp-server' descends
+`jsonrpc-process-connection', which you should see for semantics
+of the mandatory :PROCESS argument.")
(defface eglot-mode-line
'((t (:inherit font-lock-constant-face :weight bold)))
@@ -124,8 +127,8 @@ lasted more than that many seconds."
"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.")
+(cl-defgeneric eglot-handle-request (server method &rest params)
+ "Handle SERVER's METHOD request with PARAMS.")
(cl-defgeneric eglot-handle-notification (server method id &rest params)
"Handle SERVER's METHOD notification with PARAMS.")
@@ -164,10 +167,10 @@ lasted more than that many seconds."
(defclass eglot-lsp-server (jsonrpc-process-connection)
((project-nickname
:documentation "Short nickname for the associated project."
- :initarg :project-nickname :accessor eglot--project-nickname)
+ :accessor eglot--project-nickname)
(major-mode
:documentation "Major mode symbol."
- :initarg :major-mode :accessor eglot--major-mode)
+ :accessor eglot--major-mode)
(capabilities
:documentation "JSON object containing server capabilities."
:accessor eglot--capabilities)
@@ -176,19 +179,22 @@ lasted more than that many seconds."
:accessor eglot--shutdown-requested)
(project
:documentation "Project associated with server."
- :initarg :project :accessor eglot--project)
+ :accessor eglot--project)
(spinner
:documentation "List (ID DOING-WHAT DONE-P) representing server progress."
:initform `(nil nil t) :accessor eglot--spinner)
(inhibit-autoreconnect
:documentation "Generalized boolean inhibiting auto-reconnection if true."
- :initarg :inhibit-autoreconnect :accessor eglot--inhibit-autoreconnect)
+ :accessor eglot--inhibit-autoreconnect)
(file-watches
:documentation "Map ID to list of WATCHES for `didChangeWatchedFiles'."
:initform (make-hash-table :test #'equal) :accessor eglot--file-watches)
(managed-buffers
:documentation "List of buffers managed by server."
- :initarg :managed-buffers :accessor eglot--managed-buffers))
+ :accessor eglot--managed-buffers)
+ (saved-initargs
+ :documentation "Saved initargs for reconnection purposes"
+ :accessor eglot--saved-initargs))
:documentation
"Represents a server. Wraps a process for LSP communication.")
@@ -296,47 +302,35 @@ function with the server still running."
(list (match-string 1 s) (string-to-number (match-string 2
s)))
(split-string-and-unquote s)))
guess)))
- (list managed-mode project (cons class contact) t)))
+ (list managed-mode project class contact t)))
;;;###autoload
-(defun eglot (managed-major-mode project contact &optional interactive)
+(defun eglot (managed-major-mode project class contact &optional interactive)
"Manage a project with a Language Server Protocol (LSP) server.
-The LSP server is started (or contacted) via CONTACT. If this
-operation is successful, current *and future* file buffers of
-MANAGED-MAJOR-MODE inside PROJECT automatically become
+The LSP server of CLASS started (or contacted) via CONTACT. If
+this operation is successful, current *and future* file buffers
+of MANAGED-MAJOR-MODE inside PROJECT automatically become
\"managed\" by the LSP server, meaning information about their
contents is exchanged periodically to provide enhanced
code-analysis via `xref-find-definitions', `flymake-mode',
`eldoc-mode', `completion-at-point', among others.
Interactively, the command attempts to guess MANAGED-MAJOR-MODE
-from current buffer, CONTACT from `eglot-server-programs' and
-PROJECT from `project-current'. If it can't guess, the user is
-prompted. With a single \\[universal-argument] prefix arg, it
-always prompt for COMMAND. With two \\[universal-argument]
-prefix args, also prompts for MANAGED-MAJOR-MODE.
+from current buffer, CLASS and CONTACT from
+`eglot-server-programs' and PROJECT from `project-current'. If
+it can't guess, the user is prompted. With a single
+\\[universal-argument] prefix arg, it always prompt for COMMAND.
+With two \\[universal-argument] prefix args, also prompts for
+MANAGED-MAJOR-MODE.
PROJECT is a project instance as returned by `project-current'.
-CONTACT specifies how to contact the server. It can be:
-
-* a list of strings (COMMAND [ARGS...]) specifying how
-to start a server subprocess to connect to.
-
-* A list with a string as the first element and an integer number
-as the second list is interpreted as (HOST PORT [PARAMETERS...])
-and connects to an existing server via TCP, with the remaining
-PARAMETERS being given as `open-network-stream's optional
-arguments.
+CLASS is a subclass of symbol `eglot-lsp-server'.
-* A list (CLASS-SYM CONTACT...) where CLASS-SYM names the
-subclass of `eglot-server' used to create the server object. The
-remaining arguments are processed as described in the previous
-paragraphs.
-
-* A function of arguments returning arguments compatible with the
-previous description.
+CONTACT specifies how to contact the server. It is a
+keyword-value plist used to initialize CLASS or a plain list as
+described in `eglot-server-programs', which see.
INTERACTIVE is t if called interactively."
(interactive (eglot--interactive))
@@ -354,6 +348,7 @@ INTERACTIVE is t if called interactively."
managed-major-mode
(format "%s/%s" nickname
managed-major-mode)
nickname
+ class
contact)))
(eglot--message "Connected! Process `%s' now \
managing `%s' buffers in project `%s'."
@@ -371,29 +366,51 @@ INTERACTIVE is t if called interactively."
(eglot--major-mode server)
(jsonrpc-name server)
(eglot--project-nickname server)
- (jsonrpc-contact server))
+ (eieio-object-class-name server)
+ (eglot--saved-initargs server))
(eglot--message "Reconnected!"))
(defalias 'eglot-events-buffer 'jsonrpc-events-buffer)
(defvar eglot-connect-hook nil "Hook run after connecting in
`eglot--connect'.")
-(defun eglot--dispatch (server method id params)
- "Dispatcher passed to `jsonrpc-connect'.
-Calls a function on SERVER, METHOD ID and PARAMS."
- (let ((method (intern (format ":%s" method))))
- (if id
- (apply #'eglot-handle-request server id method params)
- (apply #'eglot-handle-notification server method params)
- (force-mode-line-update t))))
-
-(defun eglot--connect (project managed-major-mode name nickname contact)
+(defun eglot--connect (project managed-major-mode name nickname
+ class contact)
"Connect to PROJECT, MANAGED-MAJOR-MODE, NAME.
-And NICKNAME and CONTACT."
- (let* ((contact (if (functionp contact) (funcall contact) contact))
+And don't forget NICKNAME and CLASS, CONTACT. This docstring
+appeases checkdoc, that's all."
+ (let* ((readable-name (format "EGLOT (%s/%s)" nickname managed-major-mode))
+ (initargs
+ (cond ((keywordp (car contact)) contact)
+ ((integerp (cadr contact))
+ `(:process ,(lambda ()
+ (apply #'open-network-stream
+ readable-name nil
+ (car contact) (cadr contact)
+ (cddr contact)))))
+ ((stringp (car contact))
+ `(:process ,(lambda ()
+ (make-process
+ :name readable-name
+ :command contact
+ :connection-type 'pipe
+ :coding 'utf-8-emacs-unix
+ :stderr (get-buffer-create
+ (format "*%s stderr*"
readable-name))))))))
+ (spread
+ (lambda (fn)
+ (lambda (&rest args)
+ (apply fn (append (butlast args) (car (last args)))))))
(server
- (jsonrpc-connect name contact #'eglot--dispatch
#'eglot--on-shutdown))
+ (apply
+ #'make-instance class
+ :name name
+ :notification-dispatcher (funcall spread
#'eglot-handle-notification)
+ :request-dispatcher (funcall spread #'eglot-handle-request)
+ :on-shutdown #'eglot--on-shutdown
+ initargs))
success)
+ (setf (eglot--saved-initargs server) initargs)
(setf (eglot--project server) project)
(setf (eglot--project-nickname server) nickname)
(setf (eglot--major-mode server) managed-major-mode)
@@ -688,14 +705,14 @@ Uses THING, FACE, DEFS and PREPEND."
;;; Protocol implementation (Requests, notifications, etc)
;;;
(cl-defmethod eglot-handle-notification
- (_server (_method (eql :window/showMessage)) &key type message)
+ (_server (_method (eql window/showMessage)) &key type message)
"Handle notification window/showMessage"
(eglot--message (propertize "Server reports (type=%s): %s"
'face (if (<= type 1) 'error))
type message))
(cl-defmethod eglot-handle-request
- (server id (_method (eql :window/showMessageRequest)) &key type message
actions)
+ (server (_method (eql window/showMessageRequest)) &key type message actions)
"Handle server request window/showMessageRequest"
(let (reply)
(unwind-protect
@@ -710,23 +727,23 @@ Uses THING, FACE, DEFS and PREPEND."
'("OK"))
nil t (plist-get (elt actions 0) :title)))
(if reply
- (jsonrpc-reply server id :result `(:title ,reply))
- (jsonrpc-reply server id
+ (jsonrpc-reply server :result `(:title ,reply))
+ (jsonrpc-reply server
:error `(:code -32800 :message "User cancelled"))))))
(cl-defmethod eglot-handle-notification
- (_server (_method (eql :window/logMessage)) &key _type _message)
+ (_server (_method (eql window/logMessage)) &key _type _message)
"Handle notification window/logMessage") ;; noop, use events buffer
(cl-defmethod eglot-handle-notification
- (_server (_method (eql :telemetry/event)) &rest _any)
+ (_server (_method (eql telemetry/event)) &rest _any)
"Handle notification telemetry/event") ;; noop, use events buffer
(defvar-local eglot--unreported-diagnostics nil
"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
@@ -750,7 +767,7 @@ Uses THING, FACE, DEFS and PREPEND."
(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)
+(cl-defun eglot--register-unregister (server things how)
"Helper for `registerCapability'.
THINGS are either registrations or unregisterations."
(dolist (thing (cl-coerce things 'list))
@@ -762,28 +779,28 @@ THINGS are either registrations or unregisterations."
(unless (eq t (car retval))
(cl-return-from eglot--register-unregister
(jsonrpc-reply
- server jsonrpc-id
+ server
:error `(:code -32601 :message ,(or (cadr retval)
"sorry")))))))))
- (jsonrpc-reply server jsonrpc-id :result `(:message "OK")))
+ (jsonrpc-reply server :result `(:message "OK")))
(cl-defmethod eglot-handle-request
- (server id (_method (eql :client/registerCapability)) &key registrations)
+ (server (_method (eql client/registerCapability)) &key registrations)
"Handle server request client/registerCapability"
- (eglot--register-unregister server id registrations 'register))
+ (eglot--register-unregister server registrations 'register))
(cl-defmethod eglot-handle-request
- (server id (_method (eql :client/unregisterCapability))
+ (server (_method (eql client/unregisterCapability))
&key unregisterations) ;; XXX: "unregisterations" (sic)
"Handle server request client/unregisterCapability"
- (eglot--register-unregister server id unregisterations 'unregister))
+ (eglot--register-unregister server unregisterations 'unregister))
(cl-defmethod eglot-handle-request
- (server id (_method (eql :workspace/applyEdit)) &key _label edit)
+ (server (_method (eql workspace/applyEdit)) &key _label edit)
"Handle server request workspace/applyEdit"
(condition-case err
(progn (eglot--apply-workspace-edit edit 'confirm)
- (jsonrpc-reply server id :result `(:applied )))
- (error (jsonrpc-reply server id
+ (jsonrpc-reply server :result `(:applied )))
+ (error (jsonrpc-reply server
:result `(:applied :json-false)
:error `(:code -32001 :message (format "%s"
,err))))))
@@ -1348,7 +1365,7 @@ If SKIP-SIGNATURE, don't try to send
textDocument/signatureHelp."
(and (equal "Indexing" what) done)))))
(cl-defmethod eglot-handle-notification
- ((server eglot-rls) (_method (eql :window/progress))
+ ((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)))
@@ -1367,17 +1384,17 @@ If SKIP-SIGNATURE, don't try to send
textDocument/signatureHelp."
:progressReportFrequencyMs -1)))
(cl-defmethod eglot-handle-notification
- ((_server eglot-cquery) (_method (eql :$cquery/progress))
+ ((_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))
+ ((_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))
+ ((_server eglot-cquery) (_method (eql $cquery/publishSemanticHighlighting))
&key _uri _symbols &allow-other-keys)
"No-op for unsupported $cquery/publishSemanticHighlighting extension")
diff --git a/jsonrpc-tests.el b/jsonrpc-tests.el
index 33a7ae5..d59f1a0 100644
--- a/jsonrpc-tests.el
+++ b/jsonrpc-tests.el
@@ -30,9 +30,11 @@
(require 'jsonrpc)
(require 'eieio)
-(defclass jsonrpc-test-conn (jsonrpc-process-connection)
- ((hold-deferred :initform t :accessor jsonrpc--hold-deferred)
- (shutdown-complete-p :initform nil :accessor jsonrpc--shutdown-complete-p)))
+(defclass jsonrpc--test-endpoint (jsonrpc-process-connection)
+ ((scp :accessor jsonrpc--shutdown-complete-p)))
+
+(defclass jsonrpc--test-client (jsonrpc--test-endpoint)
+ ((hold-deferred :initform t :accessor jsonrpc--hold-deferred)))
(cl-defmacro jsonrpc--with-emacsrpc-fixture ((endpoint-sym) &body body)
(declare (indent 1) (debug t))
@@ -44,25 +46,30 @@
:service 44444
:log (lambda (_server client _message)
(setq ,server
- (jsonrpc-connect
- (process-name client)
- (make-instance 'jsonrpc-test-conn :process client)
- (lambda (endpoint method id params)
+ (make-instance
+ 'jsonrpc--test-endpoint
+ :name (process-name client)
+ :process client
+ :request-dispatcher
+ (lambda (endpoint method params)
(unless (memq method '(+ - * / vconcat append
sit-for ignore))
(signal 'jsonrpc-error
`((jsonrpc-error-message
. "Sorry, this isn't allowed")
(jsonrpc-error-code . -32601))))
- (jsonrpc-reply endpoint id :result
- (apply method (append params
nil))))
+ (let ((result (apply method (append params
nil))))
+ (jsonrpc-reply endpoint :result result)))
+ :on-shutdown
(lambda (conn)
(setf (jsonrpc--shutdown-complete-p conn)
t)))))))
- (,endpoint-sym (jsonrpc-connect
+ (,endpoint-sym (make-instance
+ 'jsonrpc--test-client
"Emacs RPC client"
- '(jsonrpc-test-conn "localhost" 44444)
- (lambda (_endpoint method _id &rest _params)
- (message "server wants to %s" method))
+ :process
+ (open-network-stream "JSONRPC test tcp endpoint"
+ nil "localhost" 44444)
+ :on-shutdown
(lambda (conn)
(setf (jsonrpc--shutdown-complete-p conn) t)))))
(unwind-protect
@@ -93,7 +100,7 @@
(ert-deftest returns-3 ()
"returns 3"
(jsonrpc--with-emacsrpc-fixture (conn)
- (should (= 3 (jsonrpc-request conn '+ '(1 2))))))
+ (should (= 3 (jsonrpc-request conn '+ [1 2])))))
(ert-deftest errors-with--32601 ()
"errors with -32601"
@@ -110,7 +117,7 @@
(jsonrpc--with-emacsrpc-fixture (conn)
(condition-case err
(progn
- (jsonrpc-request conn '+ '(a 2))
+ (jsonrpc-request conn '+ ["a" 2])
(ert-fail "A `jsonrpc-error' should have been signalled!"))
(jsonrpc-error
(should (= -32603 (cdr (assoc 'jsonrpc-error-code (cdr err)))))))))
@@ -119,23 +126,23 @@
"times out"
(jsonrpc--with-emacsrpc-fixture (conn)
(should-error
- (jsonrpc-request conn 'sit-for '(5) :timeout 2))))
+ (jsonrpc-request conn 'sit-for [5] :timeout 2))))
(ert-deftest stretching-it-but-works ()
"stretching it, but works"
(jsonrpc--with-emacsrpc-fixture (conn)
(should (equal
[1 2 3 3 4 5]
- (jsonrpc-request conn 'vconcat '([1 2 3] [3 4 5]))))))
+ (jsonrpc-request conn 'vconcat [[1 2 3] [3 4 5]])))))
(ert-deftest json-el-cant-serialize-this ()
- "json.el can't serialize this, json.el errors and request isn't sent"
+ "json.el can't serialize the response."
(jsonrpc--with-emacsrpc-fixture (conn)
(should-error
- (jsonrpc-request conn 'append '((1 2 3) (3 4 5))))))
+ (jsonrpc-request conn 'append [[1 2 3] [3 4 5]]))))
(cl-defmethod jsonrpc-connection-ready-p
- ((conn jsonrpc-test-conn) what)
+ ((conn jsonrpc--test-client) what)
(and (cl-call-next-method)
(or (not (string-match "deferred" what))
(not (jsonrpc--hold-deferred conn)))))
@@ -146,14 +153,14 @@
;; success fun which sets the flag only runs after some time.
(jsonrpc--with-emacsrpc-fixture (conn)
(jsonrpc-async-request conn
- 'sit-for '(0.5)
+ 'sit-for [0.5]
:success-fn
(lambda (_result)
(setf (jsonrpc--hold-deferred conn) nil)))
;; Now wait for an answer to this request, which should be sent as
;; soon as the previous one is answered.
(should
- (= 3 (jsonrpc-request conn '+ '(1 2)
+ (= 3 (jsonrpc-request conn '+ [1 2]
:deferred "deferred"
:timeout 1)))))
@@ -165,26 +172,26 @@
(let (n-deferred-1 n-deferred-2)
(jsonrpc-async-request
conn
- 'sit-for '(0.1)
+ 'sit-for [0.1]
:success-fn
(lambda (_result)
(setq n-deferred-1 (hash-table-count (jsonrpc--deferred-actions
conn)))))
(should-error
- (jsonrpc-request conn 'ignore '("first deferred")
+ (jsonrpc-request conn 'ignore ["first deferred"]
:deferred "first deferred"
:timeout 0.5)
:type 'jsonrpc-error)
(jsonrpc-async-request
conn
- 'sit-for '(0.1)
+ 'sit-for [0.1]
:success-fn
(lambda (_result)
(setq n-deferred-2 (hash-table-count (jsonrpc--deferred-actions
conn)))
(setf (jsonrpc--hold-deferred conn) nil)))
- (jsonrpc-async-request conn 'ignore '("second deferred")
+ (jsonrpc-async-request conn 'ignore ["second deferred"]
:deferred "second deferred"
:timeout 1)
- (jsonrpc-request conn 'ignore '("third deferred")
+ (jsonrpc-request conn 'ignore ["third deferred"]
:deferred "third deferred"
:timeout 1)
(should (eq 1 n-deferred-1))
@@ -195,11 +202,11 @@
"Deferred request fails because noone clears the flag."
(jsonrpc--with-emacsrpc-fixture (conn)
(should-error
- (jsonrpc-request conn '+ '(1 2)
+ (jsonrpc-request conn '+ [1 2]
:deferred "deferred-testing" :timeout 0.5)
:type 'jsonrpc-error)
(should
- (= 3 (jsonrpc-request conn '+ '(1 2)
+ (= 3 (jsonrpc-request conn '+ [1 2]
:timeout 0.5)))))
(provide 'jsonrpc-tests)
diff --git a/jsonrpc.el b/jsonrpc.el
index 55d4dba..d3aed36 100644
--- a/jsonrpc.el
+++ b/jsonrpc.el
@@ -34,46 +34,45 @@
;; http, or in many various message passing environments."
;;
;; To approach this agnosticism, jsonrpc.el uses objects derived from
-;; an abstract class, `jsonrpc-connection' to represent the connection
-;; to the remote JSON endpoint. Abstract operations such as sending
-;; and receiving are modelled as generic functions, so that users of
-;; JSONRPC working in complicated transport infrastructures can
+;; a base `jsonrpc-connection' class, which is "abstract" or "virtual"
+;; (in modern OO parlance) and represents the connection to the remote
+;; JSON endpoint. Equally abstract operations such as sending and
+;; receiving are modelled as generic functions, so JSONRPC
+;; applications operating over arbitrary transport infrastructures can
;; specify a subclass of `jsonrpc-connection' and write specific
-;; methods for it. Nevertheless, jsonrpc.el comes built-in with
-;; `jsonrpc-process-connection' class that works both with local
-;; subprocesses (through stdin/stdout) and TCP hosts (using
-;; sockets). This uses some simple HTTP-style envelopping for JSON
-;; objects travelling through the wire.
+;; methods for it.
;;
-;; The entry point `jsonrpc-connect', returns one of these objects by
-;; default. It is passed a name identifying the connection and a
-;; "contact", which will determine the connection type to make. This
-;; contact can a list of strings (a command and arguments for creating
-;; subprocesses) or a list of the form (HOST PORT-NUMBER PARAMS...)
-;; for connecting via TCP. For the providing the aforementioned
-;; flexibility, it can also be a any object of a subclass of
-;; `jsonrpc-connection'.
+;; The `jsonrpc-connection' constructor is the most generic entry
+;; point for these uses. However, for convenience, jsonrpc.el comes
+;; built-in with `jsonrpc-process-connection' class for talking to
+;; local subprocesses (through stdin/stdout) and TCP hosts using
+;; sockets. This uses some basic HTTP-style enveloping headers for
+;; JSON objects sent over the wire. For an example of an application
+;; using this transport scheme on top of JSONRPC, see for example the
+;; Language Server Protocol
+;; (https://microsoft.github.io/language-server-protocol/specification).
;;
-;; `jsonrpc-connect' returns a connection upon connection. This value
-;; should be saved to be later given to `jsonrpc-notify',
-;; `jsonrpc-reply', `jsonrpc-request' and `jsonrpc-async-request' as a
-;; way of contacting the connected remote endpoint.
+;; Whatever the method used to obtain a `jsonrpc-connection', it is
+;; given to `jsonrpc-notify', `jsonrpc-request' and
+;; `jsonrpc-async-request' as a way of contacting the connected remote
+;; endpoint.
;;
-;; `jsonrpc-connect' is also passed a dispatcher function for handling
-;; handling the contacts asynchronously initiated by the remote
-;; endpoint's, as well as a optional function for cleaning up after
-;; the tear-down of the JSONRPC connection.
+;; For handling remotely initiated contacts, `jsonrpc-connection'
+;; objects hold dispatcher functions that the application should pass
+;; to object's constructor if it is interested in those messages.
;;
;; The JSON objects are passed to the dispatcher after being read by
-;; `json-read' of Emacs's json.el library. They are read as plists,
-;; and, likewise, json.el-compatible plists should be given to
-;; `jsonrpc-notify', `jsonrpc-request', etc...
+;; `jsonrpc--json-read', which may use either the longstanding json.el
+;; library or a newer and faster json.c library if it is available.
;;
-;; To facilitate handling of key-value plists, this library make
-;; liberal use of cl-lib.el and suggests (but doesn't force) its
-;; clients to do the same. A macro `jsonrpc-lambda' can be used to
-;; create a lambda for destructuring a JSON-object like in this
-;; example:
+;; JSON objects are exchanged as plists: plists are handed to the
+;; dispatcher functions and, likewise, plists should be given to
+;; `jsonrpc-notify', `jsonrpc-request' and `jsonrpc-async-request'.
+;;
+;; To facilitate handling plists, this library make liberal use of
+;; cl-lib.el and suggests (but doesn't force) its clients to do the
+;; same. A macro `jsonrpc-lambda' can be used to create a lambda for
+;; destructuring a JSON-object like in this example:
;;
;; (jsonrpc-async-request
;; myproc :frobnicate `(:foo "trix")
@@ -84,47 +83,6 @@
;; (message "Sadly, server reports %s: %s"
;; code message)))
;;
-;;;; Usage example:
-;;
-;; Finally, here's an example Emacs JSONRPC server that offers a (very
-;; small) subset of Elisp for remote calling:
-;;
-;; (defvar server-server) (defvar server-endpoint)
-;; (defvar server-allowed-functions '(+ - * / vconcat append sit-for))
-;;
-;; (setq server-server
-;; (make-network-process
-;; :name "Emacs RPC server" :server t :host "localhost" :service 44444
-;; :log (lambda (_server client _message)
-;; (jsonrpc-connect
-;; (process-name client)
-;; (make-instance 'jsonrpc-process-connection :process client)
-;; (lambda (endpoint method id params)
-;; (unless (memq method '(+ - * / vconcat append sit-for))
-;; (signal 'jsonrpc-error `((jsonrpc-error-message
-;; . "Sorry, this isn't allowed")
-;; (jsonrpc-error-code . -32601))))
-;; (jsonrpc-reply endpoint id :result
-;; (apply method (append params nil))))))))
-;;
-;; (setq server-endpoint (jsonrpc-connect
-;; "Emacs RPC client" '("localhost" 9393)
-;; (lambda (endpoint method id &rest params)
-;; (message "server wants to %s" method))))
-;;
-;; ;; returns 3
-;; (jsonrpc-request server-endpoint '+ '(1 2))
-;; ;; errors with -32601
-;; (jsonrpc-request server-endpoint 'delete-directory "~/tmp")
-;; ;; signals an -32603 JSONRPC error
-;; (jsonrpc-request server-endpoint '+ '(a 2))
-;; ;; times out
-;; (jsonrpc-request server-endpoint 'sit-for '(5))
-;; ;; stretching it, but works
-;; (jsonrpc-request server-endpoint 'vconcat '([1 2 3] [3 4 5]))
-;; ;; json.el can't serialize this, json.el errors and request isn't sent
-;; (jsonrpc-request server-endpoint 'append '((1 2 3) (3 4 5)))
-;;
;;; Code:
(require 'cl-lib)
@@ -175,13 +133,22 @@ FORMAT as the message."
(apply #'format format args)
:warning)))
+;;;###autoload
(defclass jsonrpc-connection ()
((name
:accessor jsonrpc-name
+ :initarg :name
:documentation "A name for the connection")
- (-dispatcher
- :accessor jsonrpc--dispatcher
- :documentation "Emacs-lisp function for server-invoked methods.")
+ (-request-dispatcher
+ :accessor jsonrpc--request-dispatcher
+ :initform #'ignore
+ :initarg :request-dispatcher
+ :documentation "Dispatcher for remotely invoked requests.")
+ (-notification-dispatcher
+ :accessor jsonrpc--notification-dispatcher
+ :initform #'ignore
+ :initarg :notification-dispatcher
+ :documentation "Dispatcher for remotely invoked notifications.")
(status
:initform `(:unknown nil) :accessor jsonrpc-status
:documentation "Status (WHAT SERIOUS-P) as declared by the server.")
@@ -189,18 +156,9 @@ FORMAT as the message."
:initform (make-hash-table)
:accessor jsonrpc--request-continuations
:documentation "A hash table of request ID to continuation lambdas.")
- (-server-request-ids
- :accessor jsonrpc--server-request-ids
- :documentation "Server-initiated request ids that client hasn't replied
to.")
(-events-buffer
:accessor jsonrpc--events-buffer
:documentation "A buffer pretty-printing the JSON-RPC RPC events")
- (contact
- :accessor jsonrpc-contact
- :documentation "Method used to contact a server.")
- (-on-shutdown
- :accessor jsonrpc--on-shutdown
- :documentation "Function run when JSONRPC server is dying.")
(-deferred-actions
:initform (make-hash-table :test #'equal)
:accessor jsonrpc--deferred-actions
@@ -210,16 +168,65 @@ than TIMER as ID.")
(-next-request-id
:initform 0
:accessor jsonrpc--next-request-id
- :documentation "Next number used for a request")))
+ :documentation "Next number used for a request"))
+ :documentation "Base class representing a JSONRPC connection.
+The following initargs are accepted:
+
+:NAME (mandatory), a string naming the connection
+
+:REQUEST-DISPATCHER (optional), a function of three
+arguments (CONN METHOD PARAMS) for handling JSONRPC requests.
+CONN is a `jsonrpc-connection' object, method is a symbol, and
+PARAMS is a plist representing a JSON object. The function is
+expected to call `jsonrpc-reply' or signal an error of type
+`jsonrpc-error'.
+:NOTIFICATION-DISPATCHER (optional), a function of three
+arguments (CONN METHOD PARAMS) for handling JSONRPC
+notifications. CONN, METHOD and PARAMS are the same as in
+:REQUEST-DISPATCHER.")
+
+;;;###autoload
(defclass jsonrpc-process-connection (jsonrpc-connection)
((-process
:initarg :process :accessor jsonrpc--process
- ;; :initform (error "`:process' is a required initarg") ; doesn't work
:documentation "Process object wrapped by the this connection.")
(-expected-bytes
:accessor jsonrpc--expected-bytes
- :documentation "How many bytes declared by server")))
+ :documentation "How many bytes declared by server")
+ (-on-shutdown
+ :accessor jsonrpc--on-shutdown
+ :initform #'ignore
+ :initarg :on-shutdown
+ :documentation "Function run when the process dies."))
+ :documentation "A JSONRPC connection over an Emacs process.
+The following initargs are accepted:
+
+:PROCESS (mandatory), a live running Emacs process object or a
+function of no arguments producing one such object. The process
+represents either a pipe connection to locally running process or
+a stream connection to a network host. The remote endpoint is
+expected to understand JSONRPC messages with basic HTTP-style
+enveloping headers such as \"Content-Length:\".
+
+:ON-SHUTDOWN (optional), a function of one argument, the
+connection object, called when the process dies .")
+
+(cl-defmethod initialize-instance ((conn jsonrpc-process-connection) slots)
+ (cl-call-next-method)
+ (let* ((proc (plist-get slots :process))
+ (proc (if (functionp proc) (funcall proc) proc))
+ (buffer (get-buffer-create (format "*%s output*" (process-name
proc))))
+ (stderr (get-buffer-create (format "*%s stderr*" (process-name
proc)))))
+ (setf (jsonrpc--process conn) proc)
+ (set-process-buffer proc buffer)
+ (process-put proc 'jsonrpc-stderr stderr)
+ (set-process-filter proc #'jsonrpc--process-filter)
+ (set-process-sentinel proc #'jsonrpc--process-sentinel)
+ (with-current-buffer (process-buffer proc)
+ (set-marker (process-mark proc) (point-min))
+ (let ((inhibit-read-only t)) (erase-buffer) (read-only-mode t) proc))
+ (process-put proc 'jsonrpc-connection conn)))
(defmacro jsonrpc-obj (&rest what)
"Make WHAT a suitable argument for `json-encode'."
@@ -228,96 +235,26 @@ than TIMER as ID.")
;; the indenting of literal plists, i.e. is basically `list'
`(list ,@what))
-;;;###autoload
-(cl-defun jsonrpc-connect (name contact dispatcher &optional on-shutdown)
- "Connect to JSONRPC endpoint NAME through CONTACT.
-
-This function creates an object (subprocess or network
-connection) wrapped in a `jsonrpc-process-connection' object.
-
-NAME is a string naming the connection.
-
-In the most common case CONTACT is a list of strings (COMMAND
-ARGS...) specifying how to locally start a server subprocess to
-talk to via JSONRPC. If the second element in the list is an
-integer number instead of a string, the list is interpreted
-as (HOST PORT PARAMETERS...) and an attempt is made to contact
-HOST on PORT, with the remaining PARAMETERS are given to
-`open-network-stream's optional arguments.
-
-Moreover, if in either of these cases the first element in the
-list is a symbol, that symbol is taken to name a subclass of
-`jsonrpc-process-connection' which is used to create the object
-returned by this function. The remaining arguments are processed
-as described in the previous paragraph.
-
-CONTACT can also be a an object of the type
-`jsonrpc-process-connection' (or a subclass thereof) containing a
-pre-connected process object. In that case the processes buffer,
-filter and sentinel are henceforth overwritten and managed by
-`jsonrpc-connect'.
-
-ON-SHUTDOWN, if non-nil, is a function called on server exit and
-passed the moribund connection object as a single argument.
-
-DISPATCHER specifies how the server-invoked methods find their
-Elisp counterpart. It is a function passed (PROC METHOD ID PARAMS
-as arguments. PROC is the connection object returned by this
-function. ID is the server identifier for a server request, or
-nil for a server notification. METHOD is a symbol. PARAMS
-contains the method parameters as JSON data.
-
-If ID is non-nil, DISPATCHER is expected to reply to the
-request. If it doesn't, or if it signals an error before doing
-so, jsonrpc.el will automatically reply with an error. If DISPATCHER
-signals an error with alist elements `jsonrpc-error-message' and
-`jsonrpc-error-code' in its DATA, the corresponding elements are
-used for the automated error reply.
-
-If successful, `jsonrpc-connect' returns a
-`jsonrpc-process-connection' object representing the remote
-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
- ((cl-typep contact 'jsonrpc-process-connection)
- (unless (process-live-p (jsonrpc--process contact))
- (error "%s doesn't have a live process" contact))
- contact)
- ((listp contact)
- (make-instance
- (if (symbolp (car contact))
- (prog1 (car contact) (setq contact (cdr contact)))
- 'jsonrpc-process-connection)
- :process
- (cond ((integerp (cadr contact))
- (apply #'open-network-stream readable-name buffer contact))
- (t
- (make-process :name readable-name
- :command contact
- :connection-type 'pipe
- :coding 'no-conversion
- :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)
- (with-current-buffer buffer
- (let ((inhibit-read-only t)) (erase-buffer) (read-only-mode t) proc))
- (process-put proc 'jsonrpc-connection connection)
- (setf (jsonrpc--process connection) proc
- (jsonrpc-contact connection) original-contact
- (jsonrpc-name connection) name
- (jsonrpc--dispatcher connection) dispatcher
- (jsonrpc--on-shutdown connection) (or on-shutdown #'ignore))
- connection))
+(defun jsonrpc--json-read ()
+ "Read JSON object in buffer, move point to end of buffer."
+ ;; TODO: I guess we can make these macros if/when jsonrpc.el
+ ;; goes into Emacs core.
+ (cond ((fboundp 'json-parse-buffer) (json-parse-buffer
+ :object-type 'plist
+ :null-object nil
+ :false-object :json-false))
+ (t (let ((json-object-type 'plist))
+ (json-read)))))
+
+(defun jsonrpc--json-encode (object)
+ "Encode OBJECT into a JSON string."
+ (cond ((fboundp 'json-serialize) (json-serialize
+ object
+ :false-object :json-false
+ :null-object nil))
+ (t (let ((json-false :json-false)
+ (json-null nil))
+ (json-encode object)))))
(defun jsonrpc--process-sentinel (proc change)
"Called when PROC undergoes CHANGE."
@@ -387,10 +324,9 @@ endpoint."
(unwind-protect
(save-restriction
(narrow-to-region (point) message-end)
- (let* ((json-object-type 'plist)
- (json-message
+ (let* ((json-message
(condition-case-unless-debug oops
- (json-read)
+ (jsonrpc--json-read)
(error
(jsonrpc-warn "Invalid JSON: %s %s"
(cdr oops)
(buffer-string))
@@ -464,56 +400,71 @@ originated."
(setq msg (propertize msg 'face 'error)))
(insert-before-markers msg))))))
+(defvar jsonrpc--unanswered-request-id)
+
(defun jsonrpc--connection-receive (connection message)
"Connection MESSAGE from CONNECTION."
- (pcase-let ((`(,method ,id ,error ,params ,result)
+ (cl-destructuring-bind
+ (&key method id error params result _jsonrpc)
+ message
+ (pcase-let* ((continuations)
+ (lisp-err)
+ (jsonrpc--unanswered-request-id id))
+ (jsonrpc-log-event connection message 'server)
+ (when error (setf (jsonrpc-status connection) `(,error t)))
+ (cond (method
+ (let ((debug-on-error
+ (and debug-on-error
+ (not (ert-running-test)))))
(condition-case-unless-debug oops
- (cl-destructuring-bind
- (&rest args &key method id error params result _jsonrpc)
- message (list method id error params result))
- (error (jsonrpc-warn "Invalid JSONRPC message %s: %s" message
- (cdr oops))
- nil)))
- (continuations)
- (lisp-err))
- (jsonrpc-log-event connection message 'server)
- (when error (setf (jsonrpc-status connection) `(,error t)))
- (cond (method
- (let ((debug-on-error
- (and debug-on-error
- (not (ert-running-test)))))
- (condition-case-unless-debug oops
- (funcall (jsonrpc--dispatcher connection)
- connection (intern method) id params)
- (error (setq lisp-err oops))))
- (unless (or (member id (jsonrpc--server-request-ids connection))
- (not (or id lisp-err)))
- (jsonrpc-reply
- connection id
- :error (jsonrpc-obj
- :code (or (alist-get 'jsonrpc-error-code (cdr lisp-err))
- -32603)
- :message (or (alist-get 'jsonrpc-error-message
- (cdr lisp-err))
- "Internal error"))))
- (setf (jsonrpc--server-request-ids connection)
- (delete id (jsonrpc--server-request-ids connection))))
- ((setq continuations
- (and id (gethash id (jsonrpc--request-continuations
connection))))
- (let ((timer (nth 2 continuations)))
- (when timer (cancel-timer timer)))
- (remhash id (jsonrpc--request-continuations connection))
- (if error (funcall (nth 1 continuations) error)
- (funcall (nth 0 continuations) result)))
- (id
- (jsonrpc-warn "No continuation for id %s" id)))
- (jsonrpc--call-deferred connection)))
+ (funcall (if id
+ (jsonrpc--request-dispatcher connection)
+ (jsonrpc--notification-dispatcher connection))
+ connection (intern method) params)
+ (error
+ (setq lisp-err oops))))
+ (unless (or (not jsonrpc--unanswered-request-id)
+ (not lisp-err))
+ (jsonrpc-reply
+ connection
+ :error (jsonrpc-obj
+ :code (or (alist-get 'jsonrpc-error-code (cdr
lisp-err))
+ -32603)
+ :message (or (alist-get 'jsonrpc-error-message
+ (cdr lisp-err))
+ "Internal error")))))
+ ((setq continuations
+ (and id (gethash id (jsonrpc--request-continuations
connection))))
+ (let ((timer (nth 2 continuations)))
+ (when timer (cancel-timer timer)))
+ (remhash id (jsonrpc--request-continuations connection))
+ (if error (funcall (nth 1 continuations) error)
+ (funcall (nth 0 continuations) result)))
+ (id
+ (jsonrpc-warn "No continuation for id %s" id)))
+ (jsonrpc--call-deferred connection))))
(cl-defmethod jsonrpc-connection-send ((connection jsonrpc-process-connection)
- message)
+ &rest args
+ &key
+ id
+ method
+ params
+ result
+ error)
"Send MESSAGE, a JSON object, to CONNECTION."
- (let ((json-object-type 'plist)
- (json (json-encode message)))
+ (let* ((method
+ (cond ((keywordp method)
+ (substring (symbol-name method) 1))
+ ((and method (symbolp method)) (symbol-name method))
+ (t method)))
+ (message `(:jsonrpc "2.0"
+ ,@(when method `(:method ,method))
+ ,@(when id `(:id ,id))
+ ,@(when params `(:params ,params))
+ ,@(when result `(:result ,result))
+ ,@(when error `(:error ,error))))
+ (json (jsonrpc--json-encode message)))
(process-send-string (jsonrpc--process connection)
(format "Content-Length: %d\r\n\r\n%s"
(string-bytes json)
@@ -632,10 +583,10 @@ TIMEOUT is nil)."
(cl-return-from jsonrpc--async-request-1 (list id timer))))
;; Really send it
;;
- (jsonrpc-connection-send connection (jsonrpc-obj :jsonrpc "2.0"
- :id id
- :method method
- :params params))
+ (jsonrpc-connection-send connection
+ :id id
+ :method method
+ :params params)
(puthash id
(list (or success-fn
(jsonrpc-lambda (&rest _ignored)
@@ -695,20 +646,20 @@ DEFERRED is passed to `jsonrpc-async-request', which see."
(cl-defun jsonrpc-notify (connection method params)
"Notify CONNECTION of something, don't expect a reply.e"
- (jsonrpc-connection-send connection (jsonrpc-obj :jsonrpc "2.0"
- :method method
- :params params)))
+ (jsonrpc-connection-send connection
+ :method method
+ :params params))
-(cl-defun jsonrpc-reply (connection id &key (result nil result-supplied-p)
error)
+(cl-defun jsonrpc-reply (connection &key (result nil result-supplied-p) error)
"Reply to CONNECTION's request ID with RESULT or ERROR."
- (unless id (jsonrpc-error "Need a non-nil ID"))
(unless (xor result-supplied-p error)
(jsonrpc-error "Can't pass both RESULT and ERROR!"))
- (push id (jsonrpc--server-request-ids connection))
(jsonrpc-connection-send
- connection `(:jsonrpc "2.0" :id ,id
- ,@(when result `(:result ,result))
- ,@(when error `(:error ,error)))))
+ connection
+ :id jsonrpc--unanswered-request-id
+ :result result
+ :error error)
+ (setq jsonrpc--unanswered-request-id nil))
(provide 'jsonrpc)
;;; jsonrpc.el ends here
- [elpa] externals/eglot 59cc3fb 61/69: jsonrpc-connection-receive is now a public convenience function, (continued)
- [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, 2018/06/22
- [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 <=
- [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