diff options
Diffstat (limited to 'lisp')
| -rw-r--r-- | lisp/jsonrpc.el | 649 |
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\ | ||
| 77 | a saved DEFERRED `async-request' from BUF, to be sent not later\ | ||
| 78 | than 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. | ||
| 84 | The following initargs are accepted: | ||
| 85 | |||
| 86 | :NAME (mandatory), a string naming the connection | ||
| 87 | |||
| 88 | :REQUEST-DISPATCHER (optional), a function of three | ||
| 89 | arguments (CONN METHOD PARAMS) for handling JSONRPC requests. | ||
| 90 | CONN is a `jsonrpc-connection' object, method is a symbol, and | ||
| 91 | PARAMS is a plist representing a JSON object. The function is | ||
| 92 | expected to return a JSONRPC result, a plist of (:result | ||
| 93 | RESULT) or signal an error of type `jsonrpc-error'. | ||
| 94 | |||
| 95 | :NOTIFICATION-DISPATCHER (optional), a function of three | ||
| 96 | arguments (CONN METHOD PARAMS) for handling JSONRPC | ||
| 97 | notifications. 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. | ||
| 103 | ID, 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. | ||
| 116 | If it isn't, a request which was passed a value to the | ||
| 117 | `:deferred' keyword argument will be deferred to the future. | ||
| 118 | WHAT is whatever was passed the as the value to that argument. | ||
| 119 | |||
| 120 | By default, all connections are ready for sending all requests | ||
| 121 | immediately." | ||
| 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. | ||
| 154 | This function will destructure MESSAGE and call the appropriate | ||
| 155 | dispatcher 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. | ||
| 201 | If invoked inside a dispatcher function, this function is suitable | ||
| 202 | for replying to the remote endpoint with an error message. | ||
| 203 | |||
| 204 | ARGS can be of the form (FORMAT-STRING . MOREARGS) for replying | ||
| 205 | with a -32603 error code and a message formed by formatting | ||
| 206 | FORMAT-STRING with MOREARGS. | ||
| 207 | |||
| 208 | Alternatively ARGS can be plist representing a JSONRPC error | ||
| 209 | object, 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. | ||
| 232 | The JSONRPC request is formed by METHOD, a symbol, and PARAMS a | ||
| 233 | JSON object. | ||
| 234 | |||
| 235 | The caller can expect SUCCESS-FN or ERROR-FN to be called with a | ||
| 236 | JSONRPC `:result' or `:error' object, respectively. If this | ||
| 237 | doesn't happen after TIMEOUT seconds (defaults to | ||
| 238 | `jsonrpc-request-timeout'), the caller can expect TIMEOUT-FN to be | ||
| 239 | called with no arguments. The default values of SUCCESS-FN, | ||
| 240 | ERROR-FN and TIMEOUT-FN simply log the events into | ||
| 241 | `jsonrpc-events-buffer'. | ||
| 242 | |||
| 243 | If DEFERRED is non-nil, maybe defer the request to a future time | ||
| 244 | when the server is thought to be ready according to | ||
| 245 | `jsonrpc-connection-ready-p' (which see). The request might | ||
| 246 | never be sent at all, in case it is overridden in the meantime by | ||
| 247 | a new request with identical DEFERRED and for the same buffer. | ||
| 248 | However, in that situation, the original timeout is kept. | ||
| 249 | |||
| 250 | Returns 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. | ||
| 256 | Like `jsonrpc-async-request' for CONNECTION, METHOD and PARAMS, | ||
| 257 | but synchronous, i.e. this function doesn't exit until anything | ||
| 258 | interesting (success, error or timeout) happens. Furthermore, it | ||
| 259 | only exits locally (returning the JSONRPC result object) if the | ||
| 260 | request is successful, otherwise exit non-locally with an error | ||
| 261 | of type `jsonrpc-error'. | ||
| 262 | |||
| 263 | DEFERRED 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. | ||
| 323 | The following initargs are accepted: | ||
| 324 | |||
| 325 | :PROCESS (mandatory), a live running Emacs process object or a | ||
| 326 | function of no arguments producing one such object. The process | ||
| 327 | represents either a pipe connection to locally running process or | ||
| 328 | a stream connection to a network host. The remote endpoint is | ||
| 329 | expected to understand JSONRPC messages with basic HTTP-style | ||
| 330 | enveloping headers such as \"Content-Length:\". | ||
| 331 | |||
| 332 | :ON-SHUTDOWN (optional), a function of one argument, the | ||
| 333 | connection 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 | |||
| 540 | Return a list (ID TIMER). ID is the new request's ID, or nil if | ||
| 541 | the request was deferred. TIMER is a timer object set (or nil, if | ||
| 542 | TIMEOUT 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. | ||
| 623 | CONNECTION is the current connection. MESSAGE is a JSON-like | ||
| 624 | plist. TYPE is a symbol saying if this is a client or server | ||
| 625 | originated." | ||
| 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 | ||