aboutsummaryrefslogtreecommitdiffstats
path: root/lisp
diff options
context:
space:
mode:
Diffstat (limited to 'lisp')
-rw-r--r--lisp/jsonrpc.el649
1 files changed, 649 insertions, 0 deletions
diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el
new file mode 100644
index 00000000000..8cc853ed5e3
--- /dev/null
+++ b/lisp/jsonrpc.el
@@ -0,0 +1,649 @@
1;;; jsonrpc.el --- JSON-RPC library -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2018 Free Software Foundation, Inc.
4
5;; Author: João Távora <joaotavora@gmail.com>
6;; Maintainer: João Távora <joaotavora@gmail.com>
7;; Keywords: processes, languages, extensions
8
9;; This program is free software; you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation, either version 3 of the License, or
12;; (at your option) any later version.
13
14;; This program is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22;;; Commentary:
23
24;; This library implements the JSONRPC 2.0 specification as described
25;; in http://www.jsonrpc.org/. As the name suggests, JSONRPC is a
26;; generic Remote Procedure Call protocol designed around JSON
27;; objects. To learn how to write JSONRPC programs with this library,
28;; see Info node `(elisp)JSONRPC'."
29;;
30;; This library was originally extracted from eglot.el, an Emacs LSP
31;; client, which you should see for an example usage.
32;;
33;;; Code:
34
35(require 'cl-lib)
36(require 'json)
37(require 'eieio)
38(require 'subr-x)
39(require 'warnings)
40(require 'pcase)
41(require 'ert) ; to escape a `condition-case-unless-debug'
42(require 'array) ; xor
43
44
45;;; Public API
46;;;
47;;;###autoload
48(defclass jsonrpc-connection ()
49 ((name
50 :accessor jsonrpc-name
51 :initarg :name
52 :documentation "A name for the connection")
53 (-request-dispatcher
54 :accessor jsonrpc--request-dispatcher
55 :initform #'ignore
56 :initarg :request-dispatcher
57 :documentation "Dispatcher for remotely invoked requests.")
58 (-notification-dispatcher
59 :accessor jsonrpc--notification-dispatcher
60 :initform #'ignore
61 :initarg :notification-dispatcher
62 :documentation "Dispatcher for remotely invoked notifications.")
63 (last-error
64 :accessor jsonrpc-last-error
65 :documentation "Last JSONRPC error message received from endpoint.")
66 (-request-continuations
67 :initform (make-hash-table)
68 :accessor jsonrpc--request-continuations
69 :documentation "A hash table of request ID to continuation lambdas.")
70 (-events-buffer
71 :accessor jsonrpc--events-buffer
72 :documentation "A buffer pretty-printing the JSON-RPC RPC events")
73 (-deferred-actions
74 :initform (make-hash-table :test #'equal)
75 :accessor jsonrpc--deferred-actions
76 :documentation "Map (DEFERRED BUF) to (FN TIMER ID). FN is\
77a saved DEFERRED `async-request' from BUF, to be sent not later\
78than TIMER as ID.")
79 (-next-request-id
80 :initform 0
81 :accessor jsonrpc--next-request-id
82 :documentation "Next number used for a request"))
83 :documentation "Base class representing a JSONRPC connection.
84The following initargs are accepted:
85
86:NAME (mandatory), a string naming the connection
87
88:REQUEST-DISPATCHER (optional), a function of three
89arguments (CONN METHOD PARAMS) for handling JSONRPC requests.
90CONN is a `jsonrpc-connection' object, method is a symbol, and
91PARAMS is a plist representing a JSON object. The function is
92expected to return a JSONRPC result, a plist of (:result
93RESULT) or signal an error of type `jsonrpc-error'.
94
95:NOTIFICATION-DISPATCHER (optional), a function of three
96arguments (CONN METHOD PARAMS) for handling JSONRPC
97notifications. CONN, METHOD and PARAMS are the same as in
98:REQUEST-DISPATCHER.")
99
100;;; API mandatory
101(cl-defgeneric jsonrpc-connection-send (conn &key id method params result error)
102 "Send a JSONRPC message to connection CONN.
103ID, METHOD, PARAMS, RESULT and ERROR. ")
104
105;;; API optional
106(cl-defgeneric jsonrpc-shutdown (conn)
107 "Shutdown the JSONRPC connection CONN.")
108
109;;; API optional
110(cl-defgeneric jsonrpc-running-p (conn)
111 "Tell if the JSONRPC connection CONN is still running.")
112
113;;; API optional
114(cl-defgeneric jsonrpc-connection-ready-p (connection what)
115 "Tell if CONNECTION is ready for WHAT in current buffer.
116If it isn't, a request which was passed a value to the
117`:deferred' keyword argument will be deferred to the future.
118WHAT is whatever was passed the as the value to that argument.
119
120By default, all connections are ready for sending all requests
121immediately."
122 (:method (_s _what) ;; by default all connections are ready
123 t))
124
125
126;;; Convenience
127;;;
128(cl-defmacro jsonrpc-lambda (cl-lambda-list &body body)
129 (declare (indent 1) (debug (sexp &rest form)))
130 (let ((e (gensym "jsonrpc-lambda-elem")))
131 `(lambda (,e) (apply (cl-function (lambda ,cl-lambda-list ,@body)) ,e))))
132
133(defun jsonrpc-events-buffer (connection)
134 "Get or create JSONRPC events buffer for CONNECTION."
135 (let* ((probe (jsonrpc--events-buffer connection))
136 (buffer (or (and (buffer-live-p probe)
137 probe)
138 (let ((buffer (get-buffer-create
139 (format "*%s events*"
140 (jsonrpc-name connection)))))
141 (with-current-buffer buffer
142 (buffer-disable-undo)
143 (read-only-mode t)
144 (setf (jsonrpc--events-buffer connection) buffer))
145 buffer))))
146 buffer))
147
148(defun jsonrpc-forget-pending-continuations (connection)
149 "Stop waiting for responses from the current JSONRPC CONNECTION."
150 (clrhash (jsonrpc--request-continuations connection)))
151
152(defun jsonrpc-connection-receive (connection message)
153 "Process MESSAGE just received from CONNECTION.
154This function will destructure MESSAGE and call the appropriate
155dispatcher in CONNECTION."
156 (cl-destructuring-bind (&key method id error params result _jsonrpc)
157 message
158 (let (continuations)
159 (jsonrpc--log-event connection message 'server)
160 (setf (jsonrpc-last-error connection) error)
161 (cond
162 (;; A remote request
163 (and method id)
164 (let* ((debug-on-error (and debug-on-error (not (ert-running-test))))
165 (reply
166 (condition-case-unless-debug _ignore
167 (condition-case oops
168 `(:result ,(funcall (jsonrpc--request-dispatcher connection)
169 connection (intern method) params))
170 (jsonrpc-error
171 `(:error
172 (:code
173 ,(or (alist-get 'jsonrpc-error-code (cdr oops)) -32603)
174 :message ,(or (alist-get 'jsonrpc-error-message
175 (cdr oops))
176 "Internal error")))))
177 (error
178 `(:error (:code -32603 :message "Internal error"))))))
179 (apply #'jsonrpc--reply connection id reply)))
180 (;; A remote notification
181 method
182 (funcall (jsonrpc--notification-dispatcher connection)
183 connection (intern method) params))
184 (;; A remote response
185 (setq continuations
186 (and id (gethash id (jsonrpc--request-continuations connection))))
187 (let ((timer (nth 2 continuations)))
188 (when timer (cancel-timer timer)))
189 (remhash id (jsonrpc--request-continuations connection))
190 (if error (funcall (nth 1 continuations) error)
191 (funcall (nth 0 continuations) result)))
192 (;; An abnormal situation
193 id (jsonrpc--warn "No continuation for id %s" id)))
194 (jsonrpc--call-deferred connection))))
195
196
197;;; Contacting the remote endpoint
198;;;
199(defun jsonrpc-error (&rest args)
200 "Error out with FORMAT and ARGS.
201If invoked inside a dispatcher function, this function is suitable
202for replying to the remote endpoint with an error message.
203
204ARGS can be of the form (FORMAT-STRING . MOREARGS) for replying
205with a -32603 error code and a message formed by formatting
206FORMAT-STRING with MOREARGS.
207
208Alternatively ARGS can be plist representing a JSONRPC error
209object, using the keywords `:code', `:message' and `:data'."
210 (if (stringp (car args))
211 (let ((msg
212 (apply #'format-message (car args) (cdr args))))
213 (signal 'jsonrpc-error
214 `(,msg
215 (jsonrpc-error-code . ,32603)
216 (jsonrpc-error-message . ,msg))))
217 (cl-destructuring-bind (&key code message data) args
218 (signal 'jsonrpc-error
219 `(,(format "[jsonrpc] error ")
220 (jsonrpc-error-code . ,code)
221 (jsonrpc-error-message . ,message)
222 (jsonrpc-error-data . ,data))))))
223
224(cl-defun jsonrpc-async-request (connection
225 method
226 params
227 &rest args
228 &key _success-fn _error-fn
229 _timeout-fn
230 _timeout _deferred)
231 "Make a request to CONNECTION, expecting a reply, return immediately.
232The JSONRPC request is formed by METHOD, a symbol, and PARAMS a
233JSON object.
234
235The caller can expect SUCCESS-FN or ERROR-FN to be called with a
236JSONRPC `:result' or `:error' object, respectively. If this
237doesn't happen after TIMEOUT seconds (defaults to
238`jsonrpc-request-timeout'), the caller can expect TIMEOUT-FN to be
239called with no arguments. The default values of SUCCESS-FN,
240ERROR-FN and TIMEOUT-FN simply log the events into
241`jsonrpc-events-buffer'.
242
243If DEFERRED is non-nil, maybe defer the request to a future time
244when the server is thought to be ready according to
245`jsonrpc-connection-ready-p' (which see). The request might
246never be sent at all, in case it is overridden in the meantime by
247a new request with identical DEFERRED and for the same buffer.
248However, in that situation, the original timeout is kept.
249
250Returns nil."
251 (apply #'jsonrpc--async-request-1 connection method params args)
252 nil)
253
254(cl-defun jsonrpc-request (connection method params &key deferred timeout)
255 "Make a request to CONNECTION, wait for a reply.
256Like `jsonrpc-async-request' for CONNECTION, METHOD and PARAMS,
257but synchronous, i.e. this function doesn't exit until anything
258interesting (success, error or timeout) happens. Furthermore, it
259only exits locally (returning the JSONRPC result object) if the
260request is successful, otherwise exit non-locally with an error
261of type `jsonrpc-error'.
262
263DEFERRED is passed to `jsonrpc-async-request', which see."
264 (let* ((tag (cl-gensym "jsonrpc-request-catch-tag")) id-and-timer
265 (retval
266 (unwind-protect ; protect against user-quit, for example
267 (catch tag
268 (setq
269 id-and-timer
270 (jsonrpc--async-request-1
271 connection method params
272 :success-fn (lambda (result) (throw tag `(done ,result)))
273 :error-fn
274 (jsonrpc-lambda
275 (&key code message data)
276 (throw tag `(error (jsonrpc-error-code . ,code)
277 (jsonrpc-error-message . ,message)
278 (jsonrpc-error-data . ,data))))
279 :timeout-fn
280 (lambda ()
281 (throw tag '(error (jsonrpc-error-message . "Timed out"))))
282 :deferred deferred
283 :timeout timeout))
284 (while t (accept-process-output nil 30)))
285 (pcase-let* ((`(,id ,timer) id-and-timer))
286 (remhash id (jsonrpc--request-continuations connection))
287 (remhash (list deferred (current-buffer))
288 (jsonrpc--deferred-actions connection))
289 (when timer (cancel-timer timer))))))
290 (when (eq 'error (car retval))
291 (signal 'jsonrpc-error
292 (cons
293 (format "request id=%s failed:" (car id-and-timer))
294 (cdr retval))))
295 (cadr retval)))
296
297(cl-defun jsonrpc-notify (connection method params)
298 "Notify CONNECTION of something, don't expect a reply."
299 (jsonrpc-connection-send connection
300 :method method
301 :params params))
302
303(defconst jrpc-default-request-timeout 10
304 "Time in seconds before timing out a JSONRPC request.")
305
306
307;;; Specfic to `jsonrpc-process-connection'
308;;;
309;;;###autoload
310(defclass jsonrpc-process-connection (jsonrpc-connection)
311 ((-process
312 :initarg :process :accessor jsonrpc--process
313 :documentation "Process object wrapped by the this connection.")
314 (-expected-bytes
315 :accessor jsonrpc--expected-bytes
316 :documentation "How many bytes declared by server")
317 (-on-shutdown
318 :accessor jsonrpc--on-shutdown
319 :initform #'ignore
320 :initarg :on-shutdown
321 :documentation "Function run when the process dies."))
322 :documentation "A JSONRPC connection over an Emacs process.
323The following initargs are accepted:
324
325:PROCESS (mandatory), a live running Emacs process object or a
326function of no arguments producing one such object. The process
327represents either a pipe connection to locally running process or
328a stream connection to a network host. The remote endpoint is
329expected to understand JSONRPC messages with basic HTTP-style
330enveloping headers such as \"Content-Length:\".
331
332:ON-SHUTDOWN (optional), a function of one argument, the
333connection object, called when the process dies .")
334
335(cl-defmethod initialize-instance ((conn jsonrpc-process-connection) slots)
336 (cl-call-next-method)
337 (let* ((proc (plist-get slots :process))
338 (proc (if (functionp proc) (funcall proc) proc))
339 (buffer (get-buffer-create (format "*%s output*" (process-name proc))))
340 (stderr (get-buffer-create (format "*%s stderr*" (process-name proc)))))
341 (setf (jsonrpc--process conn) proc)
342 (set-process-buffer proc buffer)
343 (process-put proc 'jsonrpc-stderr stderr)
344 (set-process-filter proc #'jsonrpc--process-filter)
345 (set-process-sentinel proc #'jsonrpc--process-sentinel)
346 (with-current-buffer (process-buffer proc)
347 (set-marker (process-mark proc) (point-min))
348 (let ((inhibit-read-only t)) (erase-buffer) (read-only-mode t) proc))
349 (process-put proc 'jsonrpc-connection conn)))
350
351(cl-defmethod jsonrpc-connection-send ((connection jsonrpc-process-connection)
352 &rest args
353 &key
354 _id
355 method
356 _params
357 _result
358 _error
359 _partial)
360 "Send MESSAGE, a JSON object, to CONNECTION."
361 (when method
362 (plist-put args :method
363 (cond ((keywordp method) (substring (symbol-name method) 1))
364 ((and method (symbolp method)) (symbol-name method)))))
365 (let* ( (message `(:jsonrpc "2.0" ,@args))
366 (json (jsonrpc--json-encode message))
367 (headers
368 `(("Content-Length" . ,(format "%d" (string-bytes json)))
369 ;; ("Content-Type" . "application/vscode-jsonrpc; charset=utf-8")
370 )))
371 (process-send-string
372 (jsonrpc--process connection)
373 (cl-loop for (header . value) in headers
374 concat (concat header ": " value "\r\n") into header-section
375 finally return (format "%s\r\n%s" header-section json)))
376 (jsonrpc--log-event connection message 'client)))
377
378(defun jsonrpc-process-type (conn)
379 "Return the `process-type' of JSONRPC connection CONN."
380 (process-type (jsonrpc--process conn)))
381
382(cl-defmethod jsonrpc-running-p ((conn jsonrpc-process-connection))
383 "Return non-nil if JSONRPC connection CONN is running."
384 (process-live-p (jsonrpc--process conn)))
385
386(cl-defmethod jsonrpc-shutdown ((conn jsonrpc-process-connection))
387 "Shutdown the JSONRPC connection CONN."
388 (cl-loop
389 with proc = (jsonrpc--process conn)
390 do
391 (delete-process proc)
392 (accept-process-output nil 0.1)
393 while (not (process-get proc 'jsonrpc-sentinel-done))
394 do (jsonrpc--warn
395 "Sentinel for %s still hasn't run, deleting it!" proc)))
396
397(defun jsonrpc-stderr-buffer (conn)
398 "Get CONN's standard error buffer, if any."
399 (process-get (jsonrpc--process conn) 'jsonrpc-stderr))
400
401
402;;; Private stuff
403;;;
404(define-error 'jsonrpc-error "jsonrpc-error")
405
406(defun jsonrpc--json-read ()
407 "Read JSON object in buffer, move point to end of buffer."
408 ;; TODO: I guess we can make these macros if/when jsonrpc.el
409 ;; goes into Emacs core.
410 (cond ((fboundp 'json-parse-buffer) (json-parse-buffer
411 :object-type 'plist
412 :null-object nil
413 :false-object :json-false))
414 (t (let ((json-object-type 'plist))
415 (json-read)))))
416
417(defun jsonrpc--json-encode (object)
418 "Encode OBJECT into a JSON string."
419 (cond ((fboundp 'json-serialize) (json-serialize
420 object
421 :false-object :json-false
422 :null-object nil))
423 (t (let ((json-false :json-false)
424 (json-null nil))
425 (json-encode object)))))
426
427(cl-defun jsonrpc--reply (connection id &key (result nil result-supplied-p) error)
428 "Reply to CONNECTION's request ID with RESULT or ERROR."
429 (jsonrpc-connection-send connection :id id :result result :error error))
430
431(defun jsonrpc--call-deferred (connection)
432 "Call CONNECTION's deferred actions, who may again defer themselves."
433 (when-let ((actions (hash-table-values (jsonrpc--deferred-actions connection))))
434 (jsonrpc--debug connection `(:maybe-run-deferred ,(mapcar #'caddr actions)))
435 (mapc #'funcall (mapcar #'car actions))))
436
437(defun jsonrpc--process-sentinel (proc change)
438 "Called when PROC undergoes CHANGE."
439 (let ((connection (process-get proc 'jsonrpc-connection)))
440 (jsonrpc--debug connection `(:message "Connection state changed" :change ,change))
441 (when (not (process-live-p proc))
442 (with-current-buffer (jsonrpc-events-buffer connection)
443 (let ((inhibit-read-only t))
444 (insert "\n----------b---y---e---b---y---e----------\n")))
445 ;; Cancel outstanding timers
446 (maphash (lambda (_id triplet)
447 (pcase-let ((`(,_success ,_error ,timeout) triplet))
448 (when timeout (cancel-timer timeout))))
449 (jsonrpc--request-continuations connection))
450 (unwind-protect
451 ;; Call all outstanding error handlers
452 (maphash (lambda (_id triplet)
453 (pcase-let ((`(,_success ,error ,_timeout) triplet))
454 (funcall error `(:code -1 :message "Server died"))))
455 (jsonrpc--request-continuations connection))
456 (jsonrpc--message "Server exited with status %s" (process-exit-status proc))
457 (process-put proc 'jsonrpc-sentinel-done t)
458 (delete-process proc)
459 (funcall (jsonrpc--on-shutdown connection) connection)))))
460
461(defun jsonrpc--process-filter (proc string)
462 "Called when new data STRING has arrived for PROC."
463 (when (buffer-live-p (process-buffer proc))
464 (with-current-buffer (process-buffer proc)
465 (let* ((inhibit-read-only t)
466 (connection (process-get proc 'jsonrpc-connection))
467 (expected-bytes (jsonrpc--expected-bytes connection)))
468 ;; Insert the text, advancing the process marker.
469 ;;
470 (save-excursion
471 (goto-char (process-mark proc))
472 (insert string)
473 (set-marker (process-mark proc) (point)))
474 ;; Loop (more than one message might have arrived)
475 ;;
476 (unwind-protect
477 (let (done)
478 (while (not done)
479 (cond
480 ((not expected-bytes)
481 ;; Starting a new message
482 ;;
483 (setq expected-bytes
484 (and (search-forward-regexp
485 "\\(?:.*: .*\r\n\\)*Content-Length: \
486*\\([[:digit:]]+\\)\r\n\\(?:.*: .*\r\n\\)*\r\n"
487 (+ (point) 100)
488 t)
489 (string-to-number (match-string 1))))
490 (unless expected-bytes
491 (setq done :waiting-for-new-message)))
492 (t
493 ;; Attempt to complete a message body
494 ;;
495 (let ((available-bytes (- (position-bytes (process-mark proc))
496 (position-bytes (point)))))
497 (cond
498 ((>= available-bytes
499 expected-bytes)
500 (let* ((message-end (byte-to-position
501 (+ (position-bytes (point))
502 expected-bytes))))
503 (unwind-protect
504 (save-restriction
505 (narrow-to-region (point) message-end)
506 (let* ((json-message
507 (condition-case-unless-debug oops
508 (jsonrpc--json-read)
509 (error
510 (jsonrpc--warn "Invalid JSON: %s %s"
511 (cdr oops) (buffer-string))
512 nil))))
513 (when json-message
514 ;; Process content in another
515 ;; buffer, shielding proc buffer from
516 ;; tamper
517 (with-temp-buffer
518 (jsonrpc-connection-receive connection
519 json-message)))))
520 (goto-char message-end)
521 (delete-region (point-min) (point))
522 (setq expected-bytes nil))))
523 (t
524 ;; Message is still incomplete
525 ;;
526 (setq done :waiting-for-more-bytes-in-this-message))))))))
527 ;; Saved parsing state for next visit to this filter
528 ;;
529 (setf (jsonrpc--expected-bytes connection) expected-bytes))))))
530
531(cl-defun jsonrpc--async-request-1 (connection
532 method
533 params
534 &rest args
535 &key success-fn error-fn timeout-fn
536 (timeout jrpc-default-request-timeout)
537 (deferred nil))
538 "Does actual work for `jsonrpc-async-request'.
539
540Return a list (ID TIMER). ID is the new request's ID, or nil if
541the request was deferred. TIMER is a timer object set (or nil, if
542TIMEOUT is nil)."
543 (pcase-let* ((buf (current-buffer)) (point (point))
544 (`(,_ ,timer ,old-id)
545 (and deferred (gethash (list deferred buf)
546 (jsonrpc--deferred-actions connection))))
547 (id (or old-id (cl-incf (jsonrpc--next-request-id connection))))
548 (make-timer
549 (lambda ( )
550 (when timeout
551 (run-with-timer
552 timeout nil
553 (lambda ()
554 (remhash id (jsonrpc--request-continuations connection))
555 (remhash (list deferred buf)
556 (jsonrpc--deferred-actions connection))
557 (if timeout-fn (funcall timeout-fn)
558 (jsonrpc--debug
559 connection `(:timed-out ,method :id ,id
560 :params ,params)))))))))
561 (when deferred
562 (if (jsonrpc-connection-ready-p connection deferred)
563 ;; Server is ready, we jump below and send it immediately.
564 (remhash (list deferred buf) (jsonrpc--deferred-actions connection))
565 ;; Otherwise, save in `eglot--deferred-actions' and exit non-locally
566 (unless old-id
567 (jsonrpc--debug connection `(:deferring ,method :id ,id :params
568 ,params)))
569 (puthash (list deferred buf)
570 (list (lambda ()
571 (when (buffer-live-p buf)
572 (with-current-buffer buf
573 (save-excursion (goto-char point)
574 (apply #'jsonrpc-async-request
575 connection
576 method params args)))))
577 (or timer (setq timer (funcall make-timer))) id)
578 (jsonrpc--deferred-actions connection))
579 (cl-return-from jsonrpc--async-request-1 (list id timer))))
580 ;; Really send it
581 ;;
582 (jsonrpc-connection-send connection
583 :id id
584 :method method
585 :params params)
586 (puthash id
587 (list (or success-fn
588 (jsonrpc-lambda (&rest _ignored)
589 (jsonrpc--debug
590 connection (list :message "success ignored"
591 :id id))))
592 (or error-fn
593 (jsonrpc-lambda (&key code message &allow-other-keys)
594 (jsonrpc--debug
595 connection (list
596 :message
597 (format "error ignored, status set (%s)"
598 message)
599 :id id :error code))))
600 (setq timer (funcall make-timer)))
601 (jsonrpc--request-continuations connection))
602 (list id timer)))
603
604(defun jsonrpc--message (format &rest args)
605 "Message out with FORMAT with ARGS."
606 (message "[jsonrpc] %s" (apply #'format format args)))
607
608(defun jsonrpc--debug (server format &rest args)
609 "Debug message for SERVER with FORMAT and ARGS."
610 (jsonrpc--log-event
611 server (if (stringp format)`(:message ,(format format args)) format)))
612
613(defun jsonrpc--warn (format &rest args)
614 "Warning message with FORMAT and ARGS."
615 (apply #'jsonrpc--message (concat "(warning) " format) args)
616 (let ((warning-minimum-level :error))
617 (display-warning 'jsonrpc
618 (apply #'format format args)
619 :warning)))
620
621(defun jsonrpc--log-event (connection message &optional type)
622 "Log a JSONRPC-related event.
623CONNECTION is the current connection. MESSAGE is a JSON-like
624plist. TYPE is a symbol saying if this is a client or server
625originated."
626 (with-current-buffer (jsonrpc-events-buffer connection)
627 (cl-destructuring-bind (&key method id error &allow-other-keys) message
628 (let* ((inhibit-read-only t)
629 (subtype (cond ((and method id) 'request)
630 (method 'notification)
631 (id 'reply)
632 (t 'message)))
633 (type
634 (concat (format "%s" (or type 'internal))
635 (if type
636 (format "-%s" subtype)))))
637 (goto-char (point-max))
638 (let ((msg (format "%s%s%s %s:\n%s\n"
639 type
640 (if id (format " (id:%s)" id) "")
641 (if error " ERROR" "")
642 (current-time-string)
643 (pp-to-string message))))
644 (when error
645 (setq msg (propertize msg 'face 'error)))
646 (insert-before-markers msg))))))
647
648(provide 'jsonrpc)
649;;; jsonrpc.el ends here