2017-12-14 13:39:30 -06:00
|
|
|
(require 'json)
|
2017-12-13 11:27:13 -06:00
|
|
|
(require 'zmq)
|
|
|
|
(eval-when-compile (require 'cl))
|
|
|
|
(require 'jupyter-channels)
|
|
|
|
(require 'jupyter-messages)
|
|
|
|
|
2017-12-14 13:39:30 -06:00
|
|
|
(declare-function string-trim-right "subr-x" (str))
|
|
|
|
|
2017-12-13 11:27:13 -06:00
|
|
|
;;; Kernel client class
|
|
|
|
|
|
|
|
(defclass jupyter-kernel-client ()
|
|
|
|
;; TODO: start local kernel process or populate with kernel connection info
|
|
|
|
((kernel
|
|
|
|
:type (or null process)
|
|
|
|
:initform nil
|
2017-12-15 18:14:28 -06:00
|
|
|
:documentation "The local kernel process or nil if no local
|
|
|
|
kernel was started by this client.")
|
2017-12-16 19:01:45 -06:00
|
|
|
;; TODO: Better name, message-requests?
|
2017-12-13 11:27:13 -06:00
|
|
|
(message-callbacks
|
|
|
|
:type hash-table
|
2017-12-15 22:26:21 -06:00
|
|
|
;; Callbacks are removed once the status for a request is idle so no need
|
|
|
|
;; for a weak table here.
|
|
|
|
;;
|
|
|
|
;; FIXME: Take into account never receiving an idle status message. This
|
|
|
|
;; could happen when messages get dropped or you lose connection to a
|
|
|
|
;; kernel. If a connection is lost, it doesn't mean that we won't receive
|
|
|
|
;; previous requests since the IOPub channel broadcasts messages for every
|
|
|
|
;; client.
|
|
|
|
:initform (make-hash-table :test 'equal)
|
2017-12-13 11:27:13 -06:00
|
|
|
:documentation "A hash table with message ID's as keys. This
|
|
|
|
is used to register callback functions to run once a reply from
|
|
|
|
a previously sent request is received. See
|
2017-12-15 18:14:28 -06:00
|
|
|
`jupyter-add-receive-callback'. Note that this is also used to
|
|
|
|
filter received messages that originated from a previous request
|
|
|
|
by this client. Whenever the client sends a message in which a
|
|
|
|
reply is expected, it sets an entry in this table to represent
|
|
|
|
the fact that the message has been sent. So if there is a
|
|
|
|
non-nil value for a message ID it means that a message has been
|
|
|
|
sent and the client is expecting a reply from the kernel.")
|
2017-12-17 02:39:16 -06:00
|
|
|
(ioloop
|
|
|
|
:type (or null process)
|
2017-12-16 18:54:40 -06:00
|
|
|
:initform nil
|
|
|
|
:documentation "The process which polls for events on all
|
|
|
|
live channels of the client.")
|
2017-12-13 11:27:13 -06:00
|
|
|
(session
|
|
|
|
:type jupyter-session
|
|
|
|
:initarg :session
|
|
|
|
:documentation "The `jupyter-session' object which holds the
|
|
|
|
key for authenticating messages. It also holds the unique
|
|
|
|
session identification for this client.")
|
|
|
|
(shell-channel
|
|
|
|
:type jupyter-shell-channel
|
|
|
|
:initarg :shell-channel
|
|
|
|
:documentation "The shell channel.")
|
|
|
|
(control-channel
|
|
|
|
:type jupyter-control-channel
|
|
|
|
:initarg :control-channel
|
|
|
|
:documentation "The control channel.")
|
|
|
|
(iopub-channel
|
|
|
|
:type jupyter-iopub-channel
|
|
|
|
:initarg :iopub-channel
|
|
|
|
:documentation "The IOPub channel.")
|
|
|
|
(hb-channel
|
|
|
|
:type jupyter-hb-channel
|
|
|
|
:initarg :hb-channel
|
|
|
|
:documentation "The heartbeat channel.")
|
|
|
|
(stdin-channel
|
|
|
|
:type jupyter-stdin-channel
|
|
|
|
:initarg :stdin-channel
|
|
|
|
:documentation "The stdin channel.")))
|
|
|
|
|
|
|
|
(defun jupyter-kernel-client-from-connection-file (file)
|
|
|
|
"Read a connection FILE and return a `jupyter-kernel-client'.
|
|
|
|
The connection file should have the same form as those found in
|
|
|
|
in the jupyter runtime directory."
|
|
|
|
(cl-destructuring-bind
|
|
|
|
(&key shell_port iopub_port stdin_port hb_port control_port ip
|
|
|
|
key transport signature_scheme kernel_name
|
|
|
|
&allow-other-keys)
|
|
|
|
(let ((json-array-type 'list)
|
|
|
|
(json-object-type 'plist))
|
|
|
|
(json-read-file file))
|
|
|
|
(when (and (> (length key) 0)
|
|
|
|
(not (functionp (intern signature_scheme))))
|
|
|
|
(error "Unsupported signature scheme: %s" signature_scheme))
|
|
|
|
(let ((addr (concat transport "://" ip)))
|
|
|
|
(jupyter-kernel-client
|
|
|
|
:session (jupyter-session :key key)
|
|
|
|
:stdin-channel (jupyter-stdin-channel
|
|
|
|
:endpoint (format "%s:%d" addr stdin_port))
|
|
|
|
:shell-channel (jupyter-shell-channel
|
|
|
|
:endpoint (format "%s:%d" addr shell_port))
|
|
|
|
:control-channel (jupyter-control-channel
|
|
|
|
:endpoint (format "%s:%d" addr control_port))
|
|
|
|
:hb-channel (jupyter-hb-channel
|
|
|
|
:endpoint (format "%s:%d" addr hb_port))
|
|
|
|
:iopub-channel (jupyter-iopub-channel
|
|
|
|
:endpoint (format "%s:%d" addr iopub_port))))))
|
|
|
|
|
|
|
|
(defun jupyter-start-kernel (name &rest args)
|
|
|
|
(let ((buf (get-buffer-create (format "*jupyter-kernel-%s*" name))))
|
|
|
|
;; NOTE: `start-file-process' would start the process on a remote host if
|
|
|
|
;; `default-directory' was a remote directory.
|
|
|
|
(apply #'start-process
|
|
|
|
(format "jupyter-kernel-%s" name) buf
|
|
|
|
"jupyter" "console" "--simple-prompt" args)))
|
|
|
|
|
|
|
|
(defun jupyter-kernel-client-using-kernel (name)
|
|
|
|
;; TODO;: kernel existence
|
|
|
|
(let* ((proc (jupyter-start-kernel name "--kernel" name))
|
|
|
|
(path (expand-file-name
|
|
|
|
;; Default connection file name
|
|
|
|
(format "kernel-%d.json" (process-id proc))
|
|
|
|
(string-trim-right
|
|
|
|
(shell-command-to-string "jupyter --runtime-dir")))))
|
|
|
|
(while (not (file-exists-p path))
|
|
|
|
(sleep-for 0 10))
|
2017-12-15 22:37:59 -06:00
|
|
|
;; FIXME: Ensure that the file is done writing
|
|
|
|
(sleep-for 1)
|
2017-12-13 11:27:13 -06:00
|
|
|
(let ((client (jupyter-kernel-client-from-connection-file path)))
|
2017-12-14 13:23:40 -06:00
|
|
|
(set-process-query-on-exit-flag proc nil)
|
2017-12-13 11:27:13 -06:00
|
|
|
(oset client kernel proc)
|
|
|
|
client)))
|
|
|
|
|
2017-12-15 18:21:54 -06:00
|
|
|
;;; Lower level sending/receiving
|
|
|
|
|
|
|
|
(cl-defmethod jupyter--send-encoded ((client jupyter-kernel-client)
|
|
|
|
channel
|
|
|
|
type
|
|
|
|
message
|
|
|
|
&optional flags)
|
|
|
|
"Encode MESSAGE and send it on CLIENT's CHANNEL.
|
|
|
|
The message should have a TYPE as found in the jupyter messaging
|
|
|
|
protocol. Optional variable FLAGS are the flags sent to the
|
|
|
|
underlying `zmq-send-multipart' call using the CHANNEL's socket."
|
|
|
|
(declare (indent 1))
|
2017-12-17 02:39:16 -06:00
|
|
|
(let* ((ioloop (oref client ioloop))
|
|
|
|
(ring (or (process-get ioloop :jupyter-pending-replies)
|
|
|
|
(let ((ring (make-ring 10)))
|
|
|
|
(process-put ioloop :jupyter-pending-replies
|
|
|
|
ring)
|
|
|
|
ring)))
|
|
|
|
(future (cons :jupyter-future nil)))
|
|
|
|
(zmq-subprocess-send (oref client ioloop)
|
|
|
|
(list 'send (oref channel type) type message flags))
|
|
|
|
(ring-insert+extend ring future 'grow)
|
|
|
|
future))
|
2017-12-15 18:21:54 -06:00
|
|
|
|
2017-12-17 02:39:16 -06:00
|
|
|
(defun jupyter--ioloop (client)
|
|
|
|
(let ((iopub-channel (oref client iopub-channel))
|
|
|
|
(shell-channel (oref client shell-channel))
|
|
|
|
(stdin-channel (oref client stdin-channel))
|
|
|
|
(control-channel (oref client control-channel)))
|
|
|
|
`(lambda (ctx)
|
|
|
|
(require 'jupyter-channels ,(locate-library "jupyter-channels"))
|
|
|
|
(require 'jupyter-messages ,(locate-library "jupyter-messages"))
|
|
|
|
;; We can splice the session object because it contains primitive types
|
|
|
|
(let* ((session ,(oref client session))
|
|
|
|
(iopub
|
|
|
|
(let ((sock (jupyter-connect-channel
|
|
|
|
:iopub ,(oref (oref client iopub-channel) endpoint)
|
|
|
|
(jupyter-session-id session))))
|
|
|
|
(zmq-socket-set sock zmq-SUBSCRIBE "")
|
|
|
|
sock))
|
|
|
|
(shell
|
|
|
|
(jupyter-connect-channel
|
|
|
|
:shell ,(oref (oref client shell-channel) endpoint)
|
|
|
|
(jupyter-session-id session)))
|
|
|
|
(stdin
|
|
|
|
(jupyter-connect-channel
|
|
|
|
:stdin ,(oref (oref client stdin-channel) endpoint)
|
|
|
|
(jupyter-session-id session)))
|
|
|
|
(control
|
|
|
|
(jupyter-connect-channel
|
|
|
|
:control ,(oref (oref client control-channel) endpoint)
|
|
|
|
(jupyter-session-id session)))
|
|
|
|
;; NOTE: Order matters here since when multiple events arrive for
|
|
|
|
;; different channels they will be processed in this order.
|
|
|
|
(channels (list (cons control :control)
|
|
|
|
(cons stdin :stdin)
|
|
|
|
(cons shell :shell)
|
|
|
|
(cons iopub :iopub))))
|
2017-12-19 11:49:52 -06:00
|
|
|
(cl-flet ((recv-message
|
|
|
|
(sock ctype)
|
|
|
|
(zmq-prin1
|
|
|
|
(cons 'recvd
|
|
|
|
(cons ctype (jupyter--recv-decoded
|
|
|
|
session sock)))))
|
|
|
|
(send-message
|
|
|
|
(sock ctype rest)
|
|
|
|
(zmq-prin1
|
|
|
|
(cons 'sent
|
|
|
|
(cons ctype (apply #'jupyter--send-encoded session
|
|
|
|
sock rest)))))
|
|
|
|
(start-channel
|
|
|
|
(sock)
|
|
|
|
(zmq-connect
|
|
|
|
sock (zmq-socket-get sock zmq-LAST_ENDPOINT))
|
|
|
|
(zmq-poller-register
|
|
|
|
(current-zmq-poller) sock zmq-POLLIN))
|
|
|
|
(stop-channel
|
|
|
|
(sock)
|
|
|
|
(zmq-poller-unregister (current-zmq-poller) sock)
|
|
|
|
(condition-case err
|
|
|
|
(zmq-disconnect
|
|
|
|
sock (zmq-socket-get sock zmq-LAST_ENDPOINT))
|
|
|
|
(zmq-ENOENT nil)
|
|
|
|
(error (signal (car err) (cdr err))))))
|
|
|
|
(with-zmq-poller
|
|
|
|
;; Also poll for standard-in events to be able to read commands from
|
|
|
|
;; the parent emacs process without blocking
|
|
|
|
(zmq-poller-register (current-zmq-poller) 0 zmq-POLLIN)
|
|
|
|
(mapc (lambda (x) (zmq-poller-register (current-zmq-poller)
|
|
|
|
(car x)
|
|
|
|
zmq-POLLIN))
|
|
|
|
channels)
|
|
|
|
(unwind-protect
|
|
|
|
(while t
|
|
|
|
;; TODO: Dynamic polling period, if the rate of received
|
|
|
|
;; events is high, reduce the period. If the rate of received
|
|
|
|
;; events is low increase it. Sample the rate in a time
|
|
|
|
;; window that spans multiple polling periods. Polling at 10
|
|
|
|
;; ms periods was causing a pretty sizable portion of CPU
|
|
|
|
;; time to be eaten up.
|
|
|
|
(let ((events (zmq-poller-wait-all (current-zmq-poller) 5 20)))
|
|
|
|
(when events
|
|
|
|
(when (alist-get 0 events)
|
|
|
|
(cl-destructuring-bind (cmd . data)
|
|
|
|
(zmq-subprocess-read)
|
|
|
|
(cl-case cmd
|
|
|
|
(start-channel
|
|
|
|
(let* ((ctype data)
|
|
|
|
(sock (car (rassoc ctype channels))))
|
|
|
|
(start-channel sock)))
|
|
|
|
(stop-channel
|
|
|
|
(let* ((ctype data)
|
|
|
|
(sock (car (rassoc ctype channels))))
|
|
|
|
(stop-channel sock)))
|
|
|
|
(send
|
|
|
|
(let* ((ctype (car data))
|
|
|
|
(sock (car (rassoc ctype channels)))
|
|
|
|
(rest (cdr data)))
|
|
|
|
(send-message sock ctype rest))))))
|
|
|
|
(cl-loop for (sock . ctype) in channels
|
|
|
|
when (alist-get sock events)
|
|
|
|
do (recv-message sock ctype)))))
|
|
|
|
(mapc (lambda (s)
|
|
|
|
(zmq-socket-set s zmq-LINGER 0)
|
|
|
|
(zmq-close s))
|
|
|
|
(mapcar #'car channels)))))))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-17 02:39:16 -06:00
|
|
|
(defun jupyter--ioloop-filter (client event)
|
|
|
|
(cl-destructuring-bind (ctype . data) (cdr event)
|
|
|
|
(cl-case (car event)
|
|
|
|
;; data = sent message id
|
|
|
|
(sent
|
|
|
|
(let* ((ring (process-get (oref client ioloop) :jupyter-pending-replies))
|
|
|
|
(future (ring-remove ring)))
|
|
|
|
(setcdr future data)
|
|
|
|
(unless (eq ctype :stdin)
|
|
|
|
;; indicate that this message is expecting a reply
|
|
|
|
(puthash data t (oref client message-callbacks)))))
|
|
|
|
;; data = (idents . msg)
|
|
|
|
(recvd
|
|
|
|
(let* ((channel (cl-find-if
|
|
|
|
(lambda (c) (eq (oref c type) ctype))
|
|
|
|
(mapcar (lambda (x) (eieio-oref client x))
|
|
|
|
'(stdin-channel
|
|
|
|
shell-channel
|
|
|
|
control-channel
|
|
|
|
iopub-channel))))
|
|
|
|
(ring (oref channel recv-queue)))
|
|
|
|
(if (= (ring-length ring) (ring-size ring))
|
|
|
|
;; Try to process at a later time when the recv-queue is full
|
|
|
|
(run-with-timer 0.05 nil #'jupyter--ioloop-filter client event)
|
|
|
|
(ring-insert ring data)
|
|
|
|
(run-with-timer
|
|
|
|
0.01 nil #'jupyter--handle-message client channel)))))))
|
2017-12-13 11:27:13 -06:00
|
|
|
(cl-defmethod jupyter-start-channels ((client jupyter-kernel-client)
|
|
|
|
&key (shell t)
|
|
|
|
(iopub t)
|
|
|
|
(stdin t)
|
2017-12-15 18:14:43 -06:00
|
|
|
(control t)
|
2017-12-13 11:27:13 -06:00
|
|
|
(hb t))
|
2017-12-15 18:14:28 -06:00
|
|
|
"Start the pre-configured channels of CLIENT.
|
|
|
|
This function calls `jupyter-start-channel' for every channel
|
|
|
|
that has a non-nil value passed to this function. All channels
|
|
|
|
are started by default, so to prevent a channel from starting you
|
|
|
|
would have to pass a nil value for the channel's key. As an
|
|
|
|
example, to prevent the control channel from starting you would
|
|
|
|
call this function like so
|
|
|
|
|
|
|
|
(jupyter-start-channels client :control nil)
|
|
|
|
|
|
|
|
In addition to calling `jupyter-start-channel', a subprocess is
|
|
|
|
created for each channel which monitors the channel's socket for
|
|
|
|
input events. Note that this polling subprocess is not created
|
|
|
|
for the heartbeat channel."
|
2017-12-19 11:49:52 -06:00
|
|
|
(when hb (jupyter-start-channel (oref client hb-channel)))
|
2017-12-17 02:39:16 -06:00
|
|
|
(oset client ioloop
|
|
|
|
(zmq-start-process
|
|
|
|
(jupyter--ioloop client)
|
|
|
|
(apply-partially #'jupyter--ioloop-filter client))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-19 11:49:52 -06:00
|
|
|
(cl-defmethod jupyter-stop-channels ((client jupyter-kernel-client))
|
|
|
|
"Stop any running channels of CLIENT."
|
|
|
|
;; TODO: Better cleanup
|
|
|
|
(jupyter-stop-channel (oref client hb-channel))
|
|
|
|
(let ((ioloop (oref client ioloop)))
|
|
|
|
(when ioloop
|
|
|
|
(delete-process (oref client ioloop))
|
|
|
|
(kill-buffer (process-buffer (oref client ioloop)))
|
|
|
|
(oset client ioloop nil))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-15 18:14:28 -06:00
|
|
|
(cl-defmethod jupyter-channels-running-p ((client jupyter-kernel-client))
|
|
|
|
"Are any channels of CLIENT alive?"
|
2017-12-13 11:27:13 -06:00
|
|
|
(cl-loop
|
|
|
|
for channel in (list 'shell-channel
|
|
|
|
'iopub-channel
|
|
|
|
'hb-channel
|
2017-12-14 13:57:14 -06:00
|
|
|
'control-channel
|
2017-12-13 11:27:13 -06:00
|
|
|
'stdin-channel)
|
|
|
|
if (jupyter-channel-alive-p (eieio-oref client channel))
|
|
|
|
return t))
|
|
|
|
|
2017-12-15 22:26:21 -06:00
|
|
|
;;; Message callbacks
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-15 22:26:21 -06:00
|
|
|
(defun jupyter--callback-for-message (client msg)
|
|
|
|
(let* ((message-callbacks (oref client message-callbacks))
|
|
|
|
(pmsg-id (jupyter-message-parent-id msg))
|
|
|
|
(callbacks (gethash pmsg-id message-callbacks))
|
2017-12-17 02:53:31 -06:00
|
|
|
(all-type-cb nil)
|
2017-12-15 22:26:21 -06:00
|
|
|
(cb nil))
|
|
|
|
(when (and callbacks (not (eq callbacks t)))
|
2017-12-17 02:53:31 -06:00
|
|
|
(setq cb (cdr (assoc (jupyter-message-type msg) callbacks))
|
|
|
|
;; TODO: Better name
|
|
|
|
all-type-cb (cdr (assoc t callbacks))))
|
|
|
|
(if (and cb all-type-cb)
|
|
|
|
(setq cb (lexical-let ((f1 cb)
|
|
|
|
(f2 all-type-cb))
|
|
|
|
(lambda (msg)
|
|
|
|
(funcall f1 msg)
|
|
|
|
(funcall f2 msg)
|
|
|
|
msg)))
|
|
|
|
(when all-type-cb
|
|
|
|
(setq cb all-type-cb)))
|
2017-12-15 22:26:21 -06:00
|
|
|
;; Remove callbacks once status is idle for request PMSG-ID
|
|
|
|
;;
|
|
|
|
;; Changed in version 5.0: Busy and idle messages should be sent
|
|
|
|
;; before/after handling every request, not just execution.
|
|
|
|
;; -- http://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-status
|
|
|
|
(when (jupyter-message-status-idle-p msg)
|
|
|
|
(remhash pmsg-id message-callbacks))
|
|
|
|
cb))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-17 02:39:16 -06:00
|
|
|
(defun jupyter-ensure-id (msg-id)
|
|
|
|
(cond
|
|
|
|
((stringp msg-id) msg-id)
|
|
|
|
((and (consp msg-id) (eq (car msg-id) :jupyter-future))
|
|
|
|
(while (null (cdr msg-id))
|
|
|
|
(sleep-for 0 1))
|
|
|
|
(cdr msg-id))
|
|
|
|
(t (error "Invalid message ID %s" msg-id))))
|
|
|
|
|
2017-12-13 11:27:13 -06:00
|
|
|
(defun jupyter-add-receive-callback (client msg-type msg-id function)
|
|
|
|
"Add FUNCTION to run when receiving a message reply.
|
|
|
|
|
2017-12-15 18:14:28 -06:00
|
|
|
The function will be run when CLIENT receives a reply message
|
|
|
|
that has a type of MSG-TYPE and is a reply due to a request that
|
|
|
|
has an ID of MSG-ID. As an example, suppose you want to register
|
|
|
|
a callback when you recieve an `execute-reply' after sending an
|
|
|
|
execute request. This can be done like so:
|
|
|
|
|
|
|
|
(jupyter-add-receive-callback client 'execute-reply
|
|
|
|
(jupyter-request-execute client :code \"y = 1 + 2\")
|
|
|
|
(lambda (msg)
|
|
|
|
(cl-assert (equal (jupyter-message-type msg) \"execute_reply\"))))
|
|
|
|
|
|
|
|
Note that the callback is given the raw decoded message received
|
|
|
|
from the kernel without any processing done to it."
|
2017-12-13 11:27:13 -06:00
|
|
|
(declare (indent 3))
|
|
|
|
(cl-check-type client jupyter-kernel-client)
|
2017-12-15 18:16:53 -06:00
|
|
|
(let ((mt (plist-get jupyter--received-message-types msg-type)))
|
2017-12-14 13:37:39 -06:00
|
|
|
(if mt (setq msg-type mt)
|
2017-12-17 02:53:31 -06:00
|
|
|
;; msg-type = t means to run for every message type associated with
|
|
|
|
;; msg-id
|
|
|
|
(unless (eq msg-type t)
|
|
|
|
(error "Not a valid received message type (`%s')" msg-type))))
|
2017-12-17 02:39:16 -06:00
|
|
|
;; Ensure that the message ID is ready
|
|
|
|
(setq msg-id (jupyter-ensure-id msg-id))
|
2017-12-13 11:27:13 -06:00
|
|
|
(let* ((message-callbacks (oref client message-callbacks))
|
|
|
|
(callbacks (gethash msg-id message-callbacks)))
|
2017-12-15 22:26:21 -06:00
|
|
|
;; If a message is sent with MSG-ID, then its entry in message-callbacks is
|
|
|
|
;; either t or an alist of callbacks.
|
2017-12-16 19:01:45 -06:00
|
|
|
(if (null callbacks) (error "Invalid message ID or message has already been received.")
|
2017-12-15 22:26:21 -06:00
|
|
|
(if (eq callbacks t)
|
|
|
|
(puthash msg-id (list (cons msg-type function)) message-callbacks)
|
|
|
|
(let ((cb-for-type (assoc msg-type callbacks)))
|
|
|
|
(if cb-for-type (setcdr cb-for-type function)
|
|
|
|
(nconc callbacks (list (cons msg-type function)))))))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-15 18:18:41 -06:00
|
|
|
(defun jupyter-wait-until (client msg-type pmsg-id timeout cond)
|
|
|
|
"Wait until COND returns non-nil for a received message.
|
|
|
|
COND is run for every received message that has a type of
|
|
|
|
MSG-TYPE and whose parent header has a message ID of PMSG-ID. If
|
|
|
|
no messages are received that pass these two conditions before
|
|
|
|
TIMEOUT (in seconds), this function returns nil. Otherwise it
|
|
|
|
returns the received message. Note that if TIMEOUT is nil, it
|
|
|
|
defaults to 1 second."
|
|
|
|
(declare (indent 4))
|
|
|
|
(setq timeout (if timeout (* 1000 timeout) 1000))
|
|
|
|
(lexical-let ((msg nil)
|
|
|
|
(cond cond))
|
|
|
|
(jupyter-add-receive-callback client msg-type pmsg-id
|
2017-12-16 19:01:45 -06:00
|
|
|
(lambda (m) (setq msg (if (funcall cond m) m nil))))
|
2017-12-15 18:18:41 -06:00
|
|
|
(let ((time 0))
|
|
|
|
(catch 'timeout
|
|
|
|
(while (null msg)
|
|
|
|
(when (>= time timeout)
|
|
|
|
(throw 'timeout nil))
|
|
|
|
(sleep-for 0 10)
|
|
|
|
(setq time (+ time 10)))
|
|
|
|
msg))))
|
|
|
|
|
|
|
|
(defun jupyter-wait-until-idle (client pmsg-id &optional timeout)
|
|
|
|
"Wait until a status: idle message is received for PMSG-ID.
|
|
|
|
This function waits until TIMEOUT for CLIENT to receive an idle
|
2017-12-16 19:01:45 -06:00
|
|
|
status message for the request associated with PMSG-ID. If
|
|
|
|
TIMEOUT is non-nil, it defaults to 1 second."
|
2017-12-15 18:18:41 -06:00
|
|
|
(jupyter-wait-until client 'status pmsg-id timeout
|
|
|
|
#'jupyter-message-status-idle-p))
|
|
|
|
|
|
|
|
(defun jupyter-wait-until-received (client msg-type pmsg-id &optional timeout)
|
2017-12-14 13:48:39 -06:00
|
|
|
"Wait for a message with MSG-TYPE to be received on CLIENT.
|
|
|
|
This function waits until CLIENT receives a message from the
|
|
|
|
kernel that satisfies the following conditions:
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-14 13:48:39 -06:00
|
|
|
1. The message has a type of MSG-TYPE
|
|
|
|
2. The parent header of the message has a message ID of PMSG-ID
|
|
|
|
|
|
|
|
Note that MSG-TYPE should be one of the keys found in
|
|
|
|
`jupyter--recieved-message-types'. If it is not, an error is
|
|
|
|
raised.
|
|
|
|
|
2017-12-14 14:07:21 -06:00
|
|
|
All of the `jupyter-request-*' functions return a message ID that
|
2017-12-14 13:48:39 -06:00
|
|
|
can be passed to this function as the PMSG-ID. If the message
|
|
|
|
associated with PMSG-ID is not expecting to receive a message
|
|
|
|
with MSG-TYPE, this function will wait forever so be sure that
|
|
|
|
you are expecting to receive a message of a certain type after
|
|
|
|
sending one. For example you would not be expecting an
|
|
|
|
`execute-reply' when you send a kernel info request with
|
2017-12-14 14:07:21 -06:00
|
|
|
`jupyter-request-kernel-info', but you would be expecting a
|
2017-12-14 13:48:39 -06:00
|
|
|
`kernel-info-reply'. See the jupyter messaging specification for
|
|
|
|
more info
|
|
|
|
http://jupyter-client.readthedocs.io/en/latest/messaging.html"
|
2017-12-13 11:27:13 -06:00
|
|
|
(declare (indent 2))
|
2017-12-15 18:18:41 -06:00
|
|
|
(jupyter-wait-until client msg-type pmsg-id timeout
|
|
|
|
#'identity))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-15 22:24:00 -06:00
|
|
|
(defun jupyter--handle-message (client channel)
|
2017-12-13 11:27:13 -06:00
|
|
|
"Process a message on CLIENT's CHANNEL.
|
2017-12-15 22:24:00 -06:00
|
|
|
When a message is received on CLIENT's channel it is decoded and
|
|
|
|
added to the CHANNEL's recv-queue and this function is scheduled
|
|
|
|
to be run at a later time to process the messages in the queue.
|
|
|
|
|
|
|
|
To process a message the following steps are taken:
|
|
|
|
|
|
|
|
1. A message is removed from the recv-queue
|
|
|
|
2. A handler function is found base on CHANNEL's type
|
|
|
|
3. The handler function is called with the CLIENT and the message
|
|
|
|
as arguments
|
|
|
|
4. Any callbacks previously registered for the message are run
|
|
|
|
5. This function is scheduled to process another message of
|
|
|
|
CHANNEL in the future"
|
2017-12-13 11:27:13 -06:00
|
|
|
(let ((ring (oref channel recv-queue)))
|
|
|
|
(unless (ring-empty-p ring)
|
2017-12-15 22:33:56 -06:00
|
|
|
;; Messages are stored like (idents . msg) in the ring
|
|
|
|
(let* ((msg (cdr (ring-remove ring)))
|
|
|
|
(ctype (oref channel type))
|
2017-12-13 11:27:13 -06:00
|
|
|
(handler (cl-case ctype
|
2017-12-14 14:03:58 -06:00
|
|
|
(:stdin #'jupyter--handle-stdin-message)
|
|
|
|
(:iopub #'jupyter--handle-iopub-message)
|
|
|
|
(:shell #'jupyter--handle-shell-message)
|
|
|
|
(:control #'jupyter--handle-control-message)
|
2017-12-15 22:33:56 -06:00
|
|
|
(otherwise (error "Wrong channel type (%s)." ctype)))))
|
2017-12-13 11:27:13 -06:00
|
|
|
(unwind-protect
|
|
|
|
(funcall handler client msg)
|
2017-12-15 22:26:21 -06:00
|
|
|
(unwind-protect
|
|
|
|
(let ((cb (jupyter--callback-for-message client msg)))
|
|
|
|
(when cb (funcall cb msg)))
|
|
|
|
(unless (ring-empty-p ring)
|
|
|
|
(run-with-timer
|
|
|
|
0.01 nil #'jupyter--handle-message client channel))))))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-14 14:11:42 -06:00
|
|
|
;;; Received message handlers
|
2017-12-13 11:27:13 -06:00
|
|
|
|
|
|
|
;;; stdin messages
|
|
|
|
|
2017-12-14 14:03:58 -06:00
|
|
|
(defun jupyter--handle-stdin-message (client msg)
|
2017-12-13 11:27:13 -06:00
|
|
|
(cl-destructuring-bind (&key prompt password &allow-other-keys)
|
|
|
|
(plist-get msg :content)
|
|
|
|
(jupyter-handle-input client prompt password)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-input ((client jupyter-kernel-client) prompt password)
|
2017-12-15 18:14:28 -06:00
|
|
|
"Handle an input request from CLIENT's kernel.
|
|
|
|
PROMPT is the prompt the kernel would like to show the user. If
|
|
|
|
PASSWORD is non-nil, then `read-passwd' is used to get input from
|
|
|
|
the user. Otherwise `read-from-minibuffer' is used."
|
2017-12-13 11:27:13 -06:00
|
|
|
(let ((channel (oref client stdin-channel))
|
|
|
|
(msg (jupyter-input-reply
|
|
|
|
:value (funcall (if password #'read-passwd
|
|
|
|
#'read-from-minibuffer)
|
|
|
|
prompt))))
|
|
|
|
;; TODO: Check for 'allow_stdin'
|
|
|
|
;; http://jupyter-client.readthedocs.io/en/latest/messaging.html#stdin-messages
|
2017-12-14 13:28:58 -06:00
|
|
|
(jupyter--send-encoded client channel "input_reply" msg)))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-14 13:32:29 -06:00
|
|
|
;;; control messages
|
|
|
|
|
2017-12-14 14:03:58 -06:00
|
|
|
(defun jupyter--handle-control-message (client msg)
|
2017-12-14 13:32:29 -06:00
|
|
|
(cl-destructuring-bind (&key msg_type content &allow-other-keys) msg
|
2017-12-19 11:54:10 -06:00
|
|
|
(let ((status (plist-get content :status)))
|
|
|
|
(if (equal status "ok")
|
|
|
|
;; fIXME: An interrupt reply is only sent when interrupt_mode is set
|
|
|
|
;; to message in a kernel's kernelspec.
|
|
|
|
(pcase msg_type
|
|
|
|
("interrupt_reply"
|
|
|
|
(jupyter-handle-interrupt client)))
|
|
|
|
(if (equal status "error")
|
|
|
|
(error "Error (%s): %s"
|
|
|
|
(plist-get content :ename) (plist-get content :evalue))
|
|
|
|
(error "Error: aborted"))))))
|
2017-12-14 13:32:29 -06:00
|
|
|
|
2017-12-14 14:07:21 -06:00
|
|
|
(cl-defmethod jupyter-request-shutdown ((client jupyter-kernel-client) &optional restart)
|
2017-12-15 18:14:28 -06:00
|
|
|
"Request a shutdown of CLIENT's kernel.
|
2017-12-14 13:32:29 -06:00
|
|
|
If RESTART is non-nil, request a restart instead of a complete shutdown."
|
|
|
|
(let ((channel (oref client control-channel))
|
|
|
|
(msg (jupyter-shutdown-request :restart restart)))
|
|
|
|
(jupyter--send-encoded client channel "shutdown_request" msg)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-shutdown ((client jupyter-kernel-client) restart)
|
2017-12-19 11:54:10 -06:00
|
|
|
"Default shutdown reply handler."
|
|
|
|
(unless restart
|
|
|
|
(let ((kernel (oref client kernel)))
|
|
|
|
(when kernel
|
|
|
|
(jupyter-stop-channels client)
|
|
|
|
(delete-process kernel)
|
|
|
|
(kill-buffer (process-buffer kernel))
|
|
|
|
(oset client kernel nil)))))
|
|
|
|
|
|
|
|
;; FIXME: This breaks the convention that all jupyter-request-* functions
|
|
|
|
;; returns a message-id future object.
|
2017-12-14 14:07:21 -06:00
|
|
|
(cl-defmethod jupyter-request-interrupt ((client jupyter-kernel-client))
|
2017-12-19 11:54:10 -06:00
|
|
|
;; TODO: Check for interrupt_mode of the kernel's kernelspec
|
|
|
|
;; http://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-interrupt
|
2017-12-14 13:32:29 -06:00
|
|
|
(let ((channel (oref client control-channel)))
|
|
|
|
(jupyter--send-encoded client channel "interrupt_request" ())))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-interrupt ((client jupyter-kernel-client))
|
|
|
|
"Default interrupt reply handler.")
|
|
|
|
|
2017-12-13 11:27:13 -06:00
|
|
|
;;; shell messages
|
|
|
|
|
|
|
|
;; http://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-shell-router-dealer-channel
|
2017-12-14 14:03:58 -06:00
|
|
|
(defun jupyter--handle-shell-message (client msg)
|
2017-12-13 11:27:13 -06:00
|
|
|
(cl-destructuring-bind (&key msg_type content &allow-other-keys) msg
|
2017-12-14 13:34:03 -06:00
|
|
|
(let ((status (plist-get content :status)))
|
2017-12-19 11:55:07 -06:00
|
|
|
;; We check for error or abort since "is_complete_reply" also contains a
|
|
|
|
;; status field
|
|
|
|
(if (not (member status '("error" "abort")))
|
2017-12-13 11:27:13 -06:00
|
|
|
(pcase msg_type
|
2017-12-14 13:34:03 -06:00
|
|
|
("execute_reply"
|
|
|
|
(cl-destructuring-bind (&key execution_count
|
|
|
|
user_expressions
|
|
|
|
payload
|
|
|
|
&allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-execute
|
|
|
|
client execution_count user_expressions payload)))
|
|
|
|
("inspect_reply"
|
|
|
|
(cl-destructuring-bind (&key found
|
|
|
|
data
|
|
|
|
metadata
|
|
|
|
&allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-inspect
|
|
|
|
client found data metadata)))
|
|
|
|
("complete_reply"
|
|
|
|
(cl-destructuring-bind (&key matches
|
|
|
|
cursor_start
|
|
|
|
cursor_end
|
|
|
|
metadata
|
|
|
|
&allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-complete
|
|
|
|
client matches cursor_start cursor_end metadata)))
|
|
|
|
("history_reply"
|
|
|
|
(cl-destructuring-bind (&key history &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-history client history)))
|
|
|
|
("is_complete_reply"
|
|
|
|
(cl-destructuring-bind (&key status indent &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-is-complete client status indent)))
|
|
|
|
("comm_info_reply"
|
|
|
|
(cl-destructuring-bind (&key comms &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-comm-info client comms)))
|
|
|
|
("kernel_info_reply"
|
|
|
|
(cl-destructuring-bind (&key protocol_version
|
|
|
|
implementation
|
|
|
|
implementation_version
|
|
|
|
language_info
|
|
|
|
banner
|
|
|
|
help_links
|
|
|
|
&allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-kernel-info
|
|
|
|
client protocol_version implementation implementation_version
|
|
|
|
language_info banner help_links)))
|
2017-12-13 11:27:13 -06:00
|
|
|
(_ (error "Message type not handled yet.")))
|
|
|
|
(if (equal status "error")
|
|
|
|
(error "Error (%s): %s"
|
|
|
|
(plist-get content :ename) (plist-get content :evalue))
|
|
|
|
(error "Error: aborted"))))))
|
|
|
|
|
2017-12-14 14:07:21 -06:00
|
|
|
(cl-defmethod jupyter-request-execute ((client jupyter-kernel-client)
|
|
|
|
&key code
|
|
|
|
(silent nil)
|
|
|
|
(store-history t)
|
|
|
|
(user-expressions nil)
|
|
|
|
(allow-stdin t)
|
|
|
|
(stop-on-error nil))
|
2017-12-15 18:14:28 -06:00
|
|
|
"Send an execute request."
|
2017-12-13 11:27:13 -06:00
|
|
|
(declare (indent 1))
|
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-execute-request
|
|
|
|
:code code
|
|
|
|
:silent silent
|
|
|
|
:store-history store-history
|
|
|
|
:user-expressions user-expressions
|
|
|
|
:allow-stdin allow-stdin
|
|
|
|
:stop-on-error stop-on-error)))
|
2017-12-14 13:28:58 -06:00
|
|
|
(jupyter--send-encoded client channel "execute_request" msg)))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-14 13:34:03 -06:00
|
|
|
(cl-defmethod jupyter-handle-execute ((client jupyter-kernel-client)
|
|
|
|
execution-count
|
|
|
|
user-expressions
|
|
|
|
payload)
|
|
|
|
"Default execute reply handler.")
|
|
|
|
|
2017-12-14 14:07:21 -06:00
|
|
|
(cl-defmethod jupyter-request-inspect ((client jupyter-kernel-client)
|
|
|
|
&key code
|
|
|
|
(pos 0)
|
|
|
|
(detail 0))
|
2017-12-15 18:14:28 -06:00
|
|
|
"Send an inspect request."
|
2017-12-14 13:34:03 -06:00
|
|
|
(declare (indent 1))
|
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-inspect-request
|
|
|
|
:code code :pos pos :detail detail)))
|
|
|
|
(jupyter--send-encoded client channel "inspect_request" msg)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-inspect ((client jupyter-kernel-client)
|
|
|
|
found
|
|
|
|
data
|
|
|
|
metadata)
|
|
|
|
"Default inspect reply handler.")
|
|
|
|
|
2017-12-14 14:07:21 -06:00
|
|
|
(cl-defmethod jupyter-request-complete ((client jupyter-kernel-client)
|
|
|
|
&key code
|
|
|
|
(pos 0))
|
2017-12-15 18:14:28 -06:00
|
|
|
"Send a complete request."
|
2017-12-14 13:34:03 -06:00
|
|
|
(declare (indent 1))
|
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-complete-request
|
|
|
|
:code code :pos pos)))
|
|
|
|
(jupyter--send-encoded client channel "complete_request" msg)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-complete ((client jupyter-kernel-client)
|
|
|
|
matches
|
|
|
|
cursor-start
|
|
|
|
cursor-end
|
|
|
|
metadata)
|
|
|
|
"Default complete reply handler.")
|
|
|
|
|
2017-12-14 14:07:21 -06:00
|
|
|
(cl-defmethod jupyter-request-history ((client jupyter-kernel-client)
|
|
|
|
&key
|
|
|
|
output
|
|
|
|
raw
|
|
|
|
hist-access-type
|
|
|
|
session
|
|
|
|
start
|
|
|
|
stop
|
|
|
|
n
|
|
|
|
pattern
|
|
|
|
unique)
|
2017-12-15 18:14:28 -06:00
|
|
|
"Send a history request."
|
2017-12-14 13:34:03 -06:00
|
|
|
(declare (indent 1))
|
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-history-request
|
|
|
|
:output output
|
|
|
|
:raw raw
|
|
|
|
:hist-access-type hist-access-type
|
|
|
|
:session session
|
|
|
|
:start start
|
|
|
|
:stop stop
|
|
|
|
:n n
|
2017-12-17 02:58:11 -06:00
|
|
|
:pattern pattern
|
2017-12-14 13:34:03 -06:00
|
|
|
:unique unique)))
|
|
|
|
(jupyter--send-encoded client channel "history_request" msg)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-history ((client jupyter-kernel-client) history)
|
|
|
|
"Default history reply handler.")
|
|
|
|
|
2017-12-14 14:07:21 -06:00
|
|
|
(cl-defmethod jupyter-request-is-complete ((client jupyter-kernel-client)
|
|
|
|
&key code)
|
2017-12-15 18:14:28 -06:00
|
|
|
"Send an is-complete request."
|
2017-12-14 13:34:03 -06:00
|
|
|
(declare (indent 1))
|
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-is-complete-request
|
|
|
|
:code code)))
|
|
|
|
(jupyter--send-encoded client channel "is_complete_request" msg)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-is-complete
|
|
|
|
((client jupyter-kernel-client) status indent)
|
|
|
|
"Default is complete reply handler.")
|
|
|
|
|
2017-12-14 14:07:21 -06:00
|
|
|
(cl-defmethod jupyter-request-comm-info ((client jupyter-kernel-client)
|
|
|
|
&key target-name)
|
2017-12-15 18:14:28 -06:00
|
|
|
"Send a comm-info request."
|
2017-12-14 13:34:03 -06:00
|
|
|
(declare (indent 1))
|
|
|
|
(let ((channel (oref client shell-channel))
|
2017-12-15 18:14:28 -06:00
|
|
|
(msg (jupyter-comm-info-request
|
2017-12-14 13:34:03 -06:00
|
|
|
:target-name target-name)))
|
|
|
|
(jupyter--send-encoded client channel "comm_info_request" msg)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-comm-info ((client jupyter-kernel-client) comms)
|
|
|
|
"Default comm info. reply handler.")
|
|
|
|
|
2017-12-14 14:07:21 -06:00
|
|
|
(cl-defmethod jupyter-request-kernel-info ((client jupyter-kernel-client))
|
2017-12-15 18:14:28 -06:00
|
|
|
"Send a kernel-info request."
|
2017-12-13 11:27:13 -06:00
|
|
|
(let* ((channel (oref client shell-channel)))
|
2017-12-14 13:34:03 -06:00
|
|
|
(jupyter--send-encoded client channel "kernel_info_request" ())))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-14 13:34:03 -06:00
|
|
|
(cl-defmethod jupyter-handle-kernel-info ((client jupyter-kernel-client)
|
|
|
|
protocol-version
|
|
|
|
implementation
|
|
|
|
implementation-version
|
|
|
|
language-info
|
|
|
|
banner
|
|
|
|
help-links)
|
|
|
|
"Default kernel-info reply handler.")
|
2017-12-13 11:27:13 -06:00
|
|
|
|
|
|
|
;;; iopub messages
|
|
|
|
|
2017-12-14 14:03:58 -06:00
|
|
|
(defun jupyter--handle-iopub-message (client msg)
|
2017-12-13 11:27:13 -06:00
|
|
|
(cl-destructuring-bind (&key msg_type content &allow-other-keys) msg
|
|
|
|
(pcase msg_type
|
2017-12-19 11:54:10 -06:00
|
|
|
("shutdown_reply"
|
|
|
|
(cl-destructuring-bind (&key restart &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-shutdown client restart)))
|
2017-12-14 13:34:03 -06:00
|
|
|
("stream"
|
|
|
|
(cl-destructuring-bind (&key name text &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-stream client name text)))
|
|
|
|
("execute_input"
|
|
|
|
(cl-destructuring-bind (&key code execution_count &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-execute-input client code execution_count)))
|
|
|
|
("execute_result"
|
|
|
|
(cl-destructuring-bind (&key execution_count
|
|
|
|
data
|
|
|
|
metadata
|
|
|
|
&allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-execute-result client execution_count data metadata)))
|
|
|
|
("error"
|
|
|
|
(cl-destructuring-bind (&key ename evalue traceback &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-error client ename evalue traceback)))
|
|
|
|
("status"
|
|
|
|
(cl-destructuring-bind (&key execution_state &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-status client execution_state)))
|
|
|
|
("clear_output"
|
|
|
|
(cl-destructuring-bind (&key wait &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-clear-output client wait)))
|
|
|
|
("display_data"
|
|
|
|
(cl-destructuring-bind (&key data metadata transient &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-display-data client data metadata transient)))
|
|
|
|
("update_display_data"
|
|
|
|
(cl-destructuring-bind (&key data metadata transient &allow-other-keys)
|
|
|
|
content
|
|
|
|
(jupyter-handle-update-display-data client data metadata transient)))
|
2017-12-13 11:27:13 -06:00
|
|
|
(_ (error "Message type not handled yet.")))))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-stream ((client jupyter-kernel-client) name text)
|
2017-12-14 13:57:33 -06:00
|
|
|
"Default stream handler.")
|
2017-12-13 11:27:13 -06:00
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-execute-input ((client jupyter-kernel-client)
|
|
|
|
code
|
|
|
|
execution-count)
|
2017-12-14 13:34:03 -06:00
|
|
|
"Default execute input handler.")
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-execute-result ((client jupyter-kernel-client)
|
|
|
|
execution-count
|
|
|
|
data
|
|
|
|
metadata)
|
|
|
|
"Default execute result handler.")
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-error ((client jupyter-kernel-client)
|
|
|
|
ename
|
|
|
|
evalue
|
|
|
|
traceback)
|
|
|
|
"Default error handler.")
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-status ((client jupyter-kernel-client) execution_state)
|
|
|
|
"Default status handler.")
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-clear-output ((client jupyter-kernel-client) wait)
|
|
|
|
"Default clear output handler.")
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-display-data ((client jupyter-kernel-client)
|
|
|
|
data
|
|
|
|
metadata
|
|
|
|
transient)
|
|
|
|
"Default display data handler.")
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-update-display-data ((client jupyter-kernel-client)
|
|
|
|
data
|
|
|
|
metadata
|
|
|
|
transient)
|
|
|
|
"Default update display data handler.")
|
2017-12-13 11:27:13 -06:00
|
|
|
|
|
|
|
(provide 'jupyter-client)
|