2018-01-08 21:38:32 -06:00
|
|
|
;;; jupyter-client.el --- A Jupyter kernel client -*- lexical-binding: t -*-
|
|
|
|
|
|
|
|
;; Copyright (C) 2018 Nathaniel Nicandro
|
|
|
|
|
|
|
|
;; Author: Nathaniel Nicandro <nathanielnicandro@gmail.com>
|
|
|
|
;; Created: 06 Jan 2018
|
|
|
|
;; Version: 0.0.1
|
|
|
|
|
|
|
|
;; This program is free software; you can redistribute it and/or
|
|
|
|
;; modify it under the terms of the GNU General Public License as
|
|
|
|
;; published by the Free Software Foundation; either version 2, or (at
|
|
|
|
;; your option) any later version.
|
|
|
|
|
|
|
|
;; This program is distributed in the hope that it will be useful, but
|
|
|
|
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
;; General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
|
|
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
|
|
;; Boston, MA 02111-1307, USA.
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
;;
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
2018-01-12 17:51:16 -06:00
|
|
|
(defgroup jupyter-client nil
|
|
|
|
"A Jupyter client."
|
|
|
|
:group 'jupyter)
|
|
|
|
|
2018-01-04 23:03:18 -06:00
|
|
|
(require 'jupyter-base)
|
2017-12-13 11:27:13 -06:00
|
|
|
(require 'jupyter-channels)
|
|
|
|
(require 'jupyter-messages)
|
2018-01-22 19:12:47 -06:00
|
|
|
|
|
|
|
(declare-function hash-table-values "subr-x" (hash-table))
|
2018-01-04 20:58:28 -06:00
|
|
|
|
|
|
|
(defvar jupyter--debug nil
|
|
|
|
"Set to non-nil to emit sent and received messages to *Messages*.")
|
|
|
|
|
2018-05-20 12:09:00 -05:00
|
|
|
(defvar jupyter--clients nil
|
|
|
|
"A list of all live clients.
|
|
|
|
Clients are removed from this list when their `destructor' is called.")
|
|
|
|
|
2018-09-16 23:01:54 -05:00
|
|
|
;; This is mainly used by the REPL code, but is also set by
|
|
|
|
;; the `org-mode' client whenever `point' is inside a code
|
|
|
|
;; block.
|
|
|
|
(defvar jupyter-current-client nil
|
|
|
|
"The `jupyter-kernel-client' for the `current-buffer'.")
|
|
|
|
|
|
|
|
(put 'jupyter-current-client 'permanent-local t)
|
|
|
|
(make-variable-buffer-local 'jupyter-current-client)
|
|
|
|
|
2018-01-04 20:58:28 -06:00
|
|
|
(defvar jupyter-default-timeout 1
|
|
|
|
"The default timeout in seconds for `jupyter-wait-until'.")
|
2017-12-14 13:39:30 -06:00
|
|
|
|
2018-01-17 20:28:46 -06:00
|
|
|
(defvar jupyter-inhibit-handlers nil
|
2018-08-27 20:46:58 -05:00
|
|
|
"Whether or not new requests inhibit client handlers.
|
|
|
|
If set to t, prevent new requests from running any of the client
|
|
|
|
handler methods. If set to a list of `jupyter-message-types',
|
|
|
|
prevent handler methods from running only for those message
|
|
|
|
types.
|
|
|
|
|
|
|
|
For example to prevent a client from calling its :execute-reply
|
|
|
|
handler:
|
2018-02-08 13:38:27 -06:00
|
|
|
|
|
|
|
(let ((jupyter-inhibit-handlers '(:execute-reply)))
|
2018-05-15 23:40:09 -05:00
|
|
|
(jupyter-send-execute-request client ...))
|
2018-02-08 13:38:27 -06:00
|
|
|
|
2018-08-27 20:46:58 -05:00
|
|
|
Do not set this variable directly, let bind it around specific
|
|
|
|
requests like the above example.")
|
2018-01-12 17:54:15 -06:00
|
|
|
|
2018-02-03 00:02:33 -06:00
|
|
|
;; Define channel classes for method dispatching based on the channel type
|
|
|
|
|
|
|
|
(defclass jupyter-shell-channel (jupyter-async-channel)
|
|
|
|
((type
|
|
|
|
:initform :shell)))
|
|
|
|
|
|
|
|
(defclass jupyter-iopub-channel (jupyter-async-channel)
|
|
|
|
((type
|
|
|
|
:initform :iopub)))
|
|
|
|
|
|
|
|
(defclass jupyter-stdin-channel (jupyter-async-channel)
|
|
|
|
((type
|
|
|
|
:initform :stdin)))
|
|
|
|
|
2018-05-20 12:09:00 -05:00
|
|
|
(defclass jupyter-kernel-client (eieio-instance-tracker)
|
|
|
|
((tracking-symbol :initform 'jupyter--clients)
|
|
|
|
(requests
|
2017-12-13 11:27:13 -06:00
|
|
|
:type hash-table
|
2017-12-15 22:26:21 -06:00
|
|
|
:initform (make-hash-table :test 'equal)
|
2018-09-09 21:33:05 -05: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
|
|
|
|
`jupyter-add-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.")
|
2018-09-16 21:17:10 -05:00
|
|
|
(kernel-info
|
|
|
|
:type json-plist
|
|
|
|
:initform nil
|
|
|
|
:documentation "The saved kernel info created when first
|
|
|
|
initializing this client. When `jupyter-start-channels' is
|
|
|
|
called, this will be set to the kernel info plist returned
|
|
|
|
from an initial `:kernel-info-request'.")
|
2017-12-17 02:39:16 -06:00
|
|
|
(ioloop
|
|
|
|
:type (or null process)
|
2017-12-16 18:54:40 -06:00
|
|
|
:initform nil
|
2018-09-09 21:33:05 -05:00
|
|
|
:documentation "The process which receives events from channels.")
|
2018-05-06 23:38:09 -05:00
|
|
|
(session
|
|
|
|
:type jupyter-session
|
|
|
|
:documentation "The session for this client.")
|
2018-05-26 20:01:29 -05:00
|
|
|
(comms
|
|
|
|
:type hash-table
|
|
|
|
:initform (make-hash-table :test 'equal)
|
2018-09-09 21:33:05 -05:00
|
|
|
:documentation "A hash table with comm ID's as keys.
|
|
|
|
Contains all of the open comms. Each value is a cons cell (REQ .
|
|
|
|
DATA) which contains the generating `jupyter-request' that caused
|
|
|
|
the comm to open and the initial DATA passed to the comm for
|
2018-05-26 20:01:29 -05:00
|
|
|
initialization.")
|
2018-05-06 23:38:09 -05:00
|
|
|
(manager
|
|
|
|
:initform nil
|
|
|
|
:documentation "If this client was initialized using a
|
|
|
|
`jupyter-kernel-manager' this slot will hold the manager which
|
|
|
|
initialized the client.")
|
2018-01-16 12:05:53 -06:00
|
|
|
(-buffer
|
|
|
|
:type buffer
|
|
|
|
:documentation "An internal buffer used to store client local
|
2018-01-18 23:10:16 -06:00
|
|
|
variables and intermediate ioloop process output. When the ioloop
|
|
|
|
slot is non-nil, its `process-buffer' will be `eq' to this
|
|
|
|
buffer.")
|
2017-12-13 11:27:13 -06:00
|
|
|
(shell-channel
|
2017-12-21 18:12:25 -06:00
|
|
|
:type (or null jupyter-shell-channel)
|
2018-01-08 22:33:07 -06:00
|
|
|
:initform nil
|
2017-12-13 11:27:13 -06:00
|
|
|
:initarg :shell-channel
|
|
|
|
:documentation "The shell channel.")
|
|
|
|
(iopub-channel
|
2017-12-21 18:12:25 -06:00
|
|
|
:type (or null jupyter-iopub-channel)
|
|
|
|
:initform nil
|
2017-12-13 11:27:13 -06:00
|
|
|
:initarg :iopub-channel
|
|
|
|
:documentation "The IOPub channel.")
|
|
|
|
(stdin-channel
|
2017-12-21 18:12:25 -06:00
|
|
|
:type (or null jupyter-stdin-channel)
|
|
|
|
:initform nil
|
2017-12-13 11:27:13 -06:00
|
|
|
:initarg :stdin-channel
|
2018-02-03 00:02:33 -06:00
|
|
|
:documentation "The stdin channel.")
|
|
|
|
(hb-channel
|
|
|
|
:type (or null jupyter-hb-channel)
|
|
|
|
:initform nil
|
|
|
|
:initarg :hb-channel
|
|
|
|
:documentation "The heartbeat channel.")))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-10-01 23:11:30 -05:00
|
|
|
;;; `jupyter-current-client' language method specializer
|
|
|
|
|
|
|
|
(defvar jupyter--generic-lang-used (make-hash-table :test #'eql))
|
|
|
|
|
|
|
|
(cl-generic-define-generalizer jupyter--generic-lang-generalizer
|
|
|
|
50 (lambda (name &rest _)
|
|
|
|
`(when (object-of-class-p ,name 'jupyter-kernel-client)
|
|
|
|
;; TODO: Make `jupyter-kernel-language' a symbol
|
|
|
|
;; to avoid interning a constant string.
|
|
|
|
(gethash (intern (jupyter-kernel-language ,name))
|
|
|
|
jupyter--generic-lang-used)))
|
|
|
|
(lambda (tag &rest _)
|
|
|
|
(and (eq (car-safe tag) 'jupyter-lang)
|
|
|
|
(list tag))))
|
|
|
|
|
2018-09-17 19:15:28 -05:00
|
|
|
(cl-generic-define-context-rewriter jupyter-lang (lang)
|
2018-10-01 23:11:30 -05:00
|
|
|
`(jupyter-current-client (jupyter-lang ,lang)))
|
|
|
|
|
|
|
|
(cl-defmethod cl-generic-generalizers ((specializer (head jupyter-lang)))
|
|
|
|
"Support for (jupyter-lang LANG) specializers.
|
|
|
|
Matches if the kernel language of the `jupyter-kernel-client'
|
|
|
|
passed as the argument has a language of LANG."
|
|
|
|
(puthash (cadr specializer) specializer jupyter--generic-lang-used)
|
|
|
|
(list jupyter--generic-lang-generalizer))
|
2018-09-17 19:15:28 -05:00
|
|
|
|
2018-01-18 23:03:24 -06:00
|
|
|
(cl-defmethod initialize-instance ((client jupyter-kernel-client) &rest _slots)
|
2018-01-16 12:05:53 -06:00
|
|
|
(cl-call-next-method)
|
2018-05-20 12:09:00 -05:00
|
|
|
(push client jupyter--clients)
|
2018-01-16 12:05:53 -06:00
|
|
|
(oset client -buffer (generate-new-buffer " *jupyter-kernel-client*")))
|
|
|
|
|
2018-01-18 16:06:22 -06:00
|
|
|
(cl-defmethod destructor ((client jupyter-kernel-client) &rest _params)
|
|
|
|
"Close CLIENT's channels and cleanup internal resources."
|
|
|
|
(jupyter-stop-channels client)
|
2018-05-20 12:09:00 -05:00
|
|
|
(delete-instance client)
|
2018-01-18 16:06:22 -06:00
|
|
|
(when (buffer-live-p (oref client -buffer))
|
2018-09-09 20:10:58 -05:00
|
|
|
;; Don't ask if the buffer should be killed, this is needed because of the
|
|
|
|
;; lock file mechanism for channel subprocesses.
|
|
|
|
(with-current-buffer (oref client -buffer)
|
|
|
|
(set-buffer-modified-p nil))
|
2018-01-18 16:06:22 -06:00
|
|
|
(kill-buffer (oref client -buffer))))
|
|
|
|
|
2018-05-20 12:09:00 -05:00
|
|
|
(defun jupyter-find-client-for-session (session-id)
|
|
|
|
"Return the `jupyter-kernel-client' for SESSION-ID."
|
2018-09-09 20:38:32 -05:00
|
|
|
(or (catch 'found
|
|
|
|
(dolist (client jupyter--clients)
|
|
|
|
(when (string= (jupyter-session-id (oref client session)) session-id)
|
|
|
|
(throw 'found client))))
|
2018-05-20 12:09:00 -05:00
|
|
|
(error "No client found for session (%s)" session-id)))
|
|
|
|
|
2018-05-06 23:38:09 -05:00
|
|
|
(defun jupyter-initialize-connection (client info-or-session)
|
2018-05-15 19:25:03 -05:00
|
|
|
"Initialize CLIENT with connection INFO-OR-SESSION.
|
2018-09-09 21:33:05 -05:00
|
|
|
INFO-OR-SESSION can be a file name, a plist, or a
|
|
|
|
`jupyter-session' object that will be used to initialize CLIENT's
|
|
|
|
connection. When INFO-OR-SESSION is a file name, read the
|
|
|
|
contents of the file as a JSON plist and create a new
|
|
|
|
`jupyter-session' from it. For remote files, create a new
|
|
|
|
`jupyter-session' based on the plist returned from
|
|
|
|
`jupyter-tunnel-connection'. When INFO-OR-SESSION is a plist, use
|
|
|
|
it to create a new `jupyter-session'. Finally, when
|
|
|
|
INFO-OR-SESSION is a `jupyter-session' it is used as the session
|
|
|
|
for client. The session object used to initialize the connection
|
|
|
|
will be set as the session slot of CLIENT.
|
2018-05-06 23:38:09 -05:00
|
|
|
|
|
|
|
The necessary keys and values to initialize a connection can be
|
|
|
|
found at
|
2018-01-06 16:37:18 -06:00
|
|
|
http://jupyter-client.readthedocs.io/en/latest/kernels.html#connection-files.
|
|
|
|
|
|
|
|
As a side effect, if CLIENT is already connected to a kernel its
|
2018-02-12 11:03:41 -06:00
|
|
|
connection is terminated before initializing a new one."
|
2018-01-17 20:15:50 -06:00
|
|
|
(cl-check-type client jupyter-kernel-client)
|
2018-05-06 23:38:09 -05:00
|
|
|
(let* ((session nil)
|
|
|
|
(conn-info
|
|
|
|
(cond
|
|
|
|
((jupyter-session-p info-or-session)
|
|
|
|
(setq session info-or-session)
|
|
|
|
(jupyter-session-conn-info session))
|
|
|
|
((json-plist-p info-or-session)
|
|
|
|
info-or-session)
|
|
|
|
((stringp info-or-session)
|
2018-05-15 19:25:03 -05:00
|
|
|
(if (file-remote-p info-or-session)
|
|
|
|
;; TODO: Don't tunnel if a tunnel already exists
|
|
|
|
(jupyter-tunnel-connection info-or-session)
|
|
|
|
(unless (file-exists-p info-or-session)
|
|
|
|
(error "File does not exist (%s)" info-or-session))
|
|
|
|
(jupyter-read-plist info-or-session)))
|
2018-05-06 23:38:09 -05:00
|
|
|
(t (signal 'wrong-type-argument
|
|
|
|
(list info-or-session
|
|
|
|
'(or jupyter-session-p json-plist-p stringp)))))))
|
2017-12-22 00:50:56 -06:00
|
|
|
(cl-destructuring-bind
|
2018-01-07 19:54:52 -06:00
|
|
|
(&key shell_port iopub_port stdin_port hb_port ip
|
|
|
|
key transport signature_scheme
|
2017-12-22 00:50:56 -06:00
|
|
|
&allow-other-keys)
|
|
|
|
conn-info
|
|
|
|
(when (and (> (length key) 0)
|
2018-05-22 21:57:38 -05:00
|
|
|
(not (functionp
|
|
|
|
(intern (concat "jupyter-" signature_scheme)))))
|
2017-12-22 00:50:56 -06:00
|
|
|
(error "Unsupported signature scheme: %s" signature_scheme))
|
|
|
|
;; Stop the channels if connected to some other kernel
|
|
|
|
(jupyter-stop-channels client)
|
2018-05-06 23:38:09 -05:00
|
|
|
;; Initialize the channels
|
|
|
|
(unless session
|
|
|
|
(setq session (jupyter-session :key key :conn-info conn-info)))
|
|
|
|
(oset client session session)
|
2018-02-03 00:02:33 -06:00
|
|
|
(let ((addr (lambda (port) (format "%s://%s:%d" transport ip port))))
|
|
|
|
(oset client hb-channel (make-instance
|
|
|
|
'jupyter-hb-channel
|
2018-05-06 23:38:09 -05:00
|
|
|
:session session
|
2018-02-03 00:02:33 -06:00
|
|
|
:endpoint (funcall addr hb_port)))
|
|
|
|
(cl-loop
|
|
|
|
for (channel . port) in `((stdin-channel . ,stdin_port)
|
|
|
|
(shell-channel . ,shell_port)
|
|
|
|
(iopub-channel . ,iopub_port))
|
|
|
|
do (setf (slot-value client channel)
|
|
|
|
(make-instance
|
|
|
|
(cl-case channel
|
|
|
|
(stdin-channel 'jupyter-stdin-channel)
|
|
|
|
(shell-channel 'jupyter-shell-channel)
|
|
|
|
(iopub-channel 'jupyter-iopub-channel)
|
|
|
|
(otherwise (error "Wrong channel type")))
|
|
|
|
;; So channels have access to the client's session
|
|
|
|
;;
|
2018-05-12 14:52:35 -05:00
|
|
|
;; See `jupyter-start-channels' for when the :ioloop slot of
|
|
|
|
;; a channel is set
|
2018-05-06 23:38:09 -05:00
|
|
|
:session session
|
2018-02-03 00:02:33 -06:00
|
|
|
:endpoint (funcall addr port))))))))
|
2017-12-22 00:50:56 -06:00
|
|
|
|
2018-01-16 12:05:53 -06:00
|
|
|
;;; Client local variables
|
2018-01-13 22:51:27 -06:00
|
|
|
|
2018-10-02 22:09:59 -05:00
|
|
|
(defmacro jupyter-with-client-buffer (client &rest body)
|
2018-01-18 23:10:16 -06:00
|
|
|
"Run a form inside CLIENT's IOloop subprocess buffer.
|
|
|
|
BODY is run with the current buffer set to CLIENT's IOloop
|
|
|
|
subprocess buffer."
|
2018-01-12 17:47:06 -06:00
|
|
|
(declare (indent 1))
|
|
|
|
`(progn
|
2018-01-13 13:34:02 -06:00
|
|
|
(cl-check-type ,client jupyter-kernel-client)
|
2018-01-16 12:05:53 -06:00
|
|
|
;; NOTE: -buffer will be set as the IOLoop process buffer, see
|
2018-01-22 19:03:34 -06:00
|
|
|
;; `jupyter-start-channels', but before the IOLoop process is started we
|
|
|
|
;; would like to have a buffer available so that client local variables
|
|
|
|
;; can be set on the buffer. This is why we create our own buffer when a
|
|
|
|
;; client is initialized.
|
2018-01-16 12:05:53 -06:00
|
|
|
(with-current-buffer (oref ,client -buffer)
|
2018-01-12 17:47:06 -06:00
|
|
|
,@body)))
|
|
|
|
|
|
|
|
(defun jupyter-set (client symbol newval)
|
|
|
|
"Set CLIENT's local value for SYMBOL to NEWVAL."
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-client-buffer client
|
2018-01-12 17:47:06 -06:00
|
|
|
(set (make-local-variable symbol) newval)))
|
|
|
|
|
|
|
|
(defun jupyter-get (client symbol)
|
|
|
|
"Get CLIENT's local value of SYMBOL."
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-client-buffer client
|
2018-01-12 17:47:06 -06:00
|
|
|
(symbol-value symbol)))
|
|
|
|
|
2018-01-16 12:05:53 -06:00
|
|
|
;;; Hooks
|
|
|
|
|
2018-01-12 17:54:15 -06:00
|
|
|
(defun jupyter-add-hook (client hook function &optional append)
|
|
|
|
"Add to the CLIENT value of HOOK the function FUNCTION.
|
|
|
|
APPEND has the same meaning as in `add-hook' and FUNCTION is
|
|
|
|
added to HOOK using `add-hook', but local only to CLIENT. Note
|
|
|
|
that the CLIENT should have its channels already started before
|
|
|
|
this is called."
|
2018-05-13 11:40:08 -05:00
|
|
|
(declare (indent 2))
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-client-buffer client
|
2018-01-12 17:54:15 -06:00
|
|
|
(add-hook hook function append t)))
|
|
|
|
|
2018-05-25 21:12:54 -05:00
|
|
|
(defun jupyter-run-hook-with-args-until-success (client hook &rest args)
|
2018-01-12 17:54:15 -06:00
|
|
|
"Run CLIENT's value for HOOK with the arguments ARGS."
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-client-buffer client
|
2018-01-18 23:09:10 -06:00
|
|
|
(when jupyter--debug
|
|
|
|
(message "RUN-HOOK: %s" hook))
|
2018-05-25 21:12:54 -05:00
|
|
|
(apply #'run-hook-with-args-until-success hook args)))
|
2018-01-12 17:54:15 -06:00
|
|
|
|
|
|
|
(defun jupyter-remove-hook (client hook function)
|
|
|
|
"Remove from CLIENT's value of HOOK the function FUNCTION."
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-client-buffer client
|
2018-01-12 17:54:15 -06:00
|
|
|
(remove-hook hook function t)))
|
2017-12-15 18:21:54 -06:00
|
|
|
|
2018-01-13 22:51:27 -06:00
|
|
|
;;; Sending messages
|
|
|
|
|
2018-08-27 20:46:58 -05:00
|
|
|
(cl-defgeneric jupyter-generate-request ((_client jupyter-kernel-client) _msg)
|
2018-06-03 22:04:21 -05:00
|
|
|
"Generate a `jupyter-request' object for MSG.
|
|
|
|
This method gives an opportunity for subclasses to initialize a
|
2018-08-27 20:46:58 -05:00
|
|
|
`jupyter-request' based on the current context.
|
2018-06-03 22:04:21 -05:00
|
|
|
|
|
|
|
The default implementation returns a new `jupyter-request' with
|
2018-10-01 23:13:07 -05:00
|
|
|
the default value for all slots. Note, the `:id' and
|
|
|
|
`:inhibited-handlers' slots are overwritten by the caller of this
|
|
|
|
method."
|
2018-06-03 22:04:21 -05:00
|
|
|
(make-jupyter-request))
|
|
|
|
|
2017-12-31 15:22:48 -06:00
|
|
|
(cl-defmethod jupyter-send ((client jupyter-kernel-client)
|
|
|
|
channel
|
|
|
|
type
|
2018-05-25 02:07:47 -05:00
|
|
|
message
|
|
|
|
&optional msg-id)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Send a message on CLIENT's CHANNEL.
|
|
|
|
Return a `jupyter-request' representing the sent message. CHANNEL
|
2018-05-12 14:52:35 -05:00
|
|
|
is one of the channel's of CLIENT. TYPE is one of the
|
|
|
|
`jupyter-message-types'. MESSAGE is the message sent on CHANNEL.
|
|
|
|
|
|
|
|
Note that you can manipulate how to handle messages received in
|
|
|
|
response to the sent message, see `jupyter-add-callback' and
|
2018-02-08 13:38:27 -06:00
|
|
|
`jupyter-request-inhibited-handlers'."
|
2017-12-15 18:21:54 -06:00
|
|
|
(declare (indent 1))
|
2018-01-08 22:33:46 -06:00
|
|
|
(let ((ioloop (oref client ioloop)))
|
|
|
|
(unless ioloop
|
|
|
|
(signal 'wrong-type-argument (list 'process ioloop 'ioloop)))
|
2018-05-16 20:08:27 -05:00
|
|
|
(or (eq jupyter-inhibit-handlers t)
|
|
|
|
(cl-loop
|
|
|
|
for msg-type in jupyter-inhibit-handlers
|
|
|
|
unless (plist-member jupyter-message-types msg-type)
|
|
|
|
do (error "Not a valid message type (`%s')" msg-type)))
|
2018-01-18 23:09:10 -06:00
|
|
|
(when jupyter--debug
|
|
|
|
(message "SENDING: %s %s" type message))
|
2018-05-25 02:07:47 -05:00
|
|
|
(let ((msg-id (or msg-id (jupyter-new-uuid))))
|
|
|
|
(jupyter-send channel type message msg-id)
|
|
|
|
;; Anything sent to stdin is a reply not a request so don't add it to
|
|
|
|
;; `:pending-requests'.
|
|
|
|
(unless (eq (oref channel type) :stdin)
|
2018-06-03 22:04:21 -05:00
|
|
|
(let ((req (jupyter-generate-request client message)))
|
2018-09-07 04:29:29 -05:00
|
|
|
(setf (jupyter-request-id req) msg-id)
|
2018-06-03 22:04:21 -05:00
|
|
|
(setf (jupyter-request-inhibited-handlers req) jupyter-inhibit-handlers)
|
2018-05-25 02:07:47 -05:00
|
|
|
(jupyter--ioloop-push-request client req)
|
|
|
|
req)))))
|
2017-12-15 18:21:54 -06:00
|
|
|
|
2018-01-13 22:51:27 -06:00
|
|
|
;;; Channel subprocess (receiving messages)
|
2018-01-07 23:21:53 -06:00
|
|
|
|
2018-02-03 00:02:33 -06:00
|
|
|
(defmacro jupyter--ioloop-do-command (poller channels)
|
2018-01-07 23:21:53 -06:00
|
|
|
"Read and execute a command from stdin.
|
2018-05-20 10:58:13 -05:00
|
|
|
POLLER is a variable bound to a `zmq-poller' object. and CHANNELS
|
|
|
|
is a variable bound to an alist of (SOCK . CTYPE) pairs where
|
|
|
|
SOCK is a `zmq-socket' representing a `jupyter-channel' that has
|
|
|
|
a channel type of CTYPE.
|
2018-01-07 23:21:53 -06:00
|
|
|
|
|
|
|
The parent Emacs process should send the ioloop subprocess cons
|
|
|
|
cells of the form:
|
|
|
|
|
|
|
|
(CMD . DATA)
|
|
|
|
|
|
|
|
where CMD is a symbol representing the command to perform and
|
|
|
|
DATA is any information needed to run the command. The available
|
|
|
|
commands that an ioloop subprocess can perform are:
|
|
|
|
|
|
|
|
- '(quit) :: Terminate the ioloop.
|
|
|
|
|
|
|
|
- '(start-channel . CTYPE) :: Re-connect the socket in CHANNELS
|
|
|
|
that represents a `jupyter-channel' whose type is CTYPE.
|
|
|
|
|
|
|
|
- '(stop-channel . CTYPE) :: Disconnect the socket that
|
|
|
|
corresponds to a channel with type CTYPE in CHANNELS.
|
|
|
|
|
|
|
|
- '(send CTYPE . ARGS) :: Call `jupyter-send' with arguments
|
|
|
|
SESSION, the socket corresponding to CTYPE, and ARGS. Where
|
|
|
|
ARGS is a list of the remaining arguments necessary for the
|
|
|
|
`jupyter-send' call.
|
|
|
|
|
|
|
|
Any other command sent to the subprocess will be ignored."
|
|
|
|
`(cl-destructuring-bind (cmd . args)
|
|
|
|
(zmq-subprocess-read)
|
|
|
|
(cl-case cmd
|
|
|
|
(send
|
|
|
|
(cl-destructuring-bind (ctype . args) args
|
2018-02-03 00:02:33 -06:00
|
|
|
(let ((channel (cdr (assoc ctype ,channels))))
|
|
|
|
(zmq-prin1 (list 'sent ctype (apply #'jupyter-send channel args))))))
|
2018-01-07 23:21:53 -06:00
|
|
|
(start-channel
|
2018-05-12 14:49:38 -05:00
|
|
|
(cl-destructuring-bind (ctype endpoint identity) args
|
2018-02-03 00:02:33 -06:00
|
|
|
(let ((channel (cdr (assoc ctype ,channels))))
|
2018-05-12 14:49:38 -05:00
|
|
|
(oset channel endpoint endpoint)
|
|
|
|
(jupyter-start-channel channel :identity identity)
|
2018-05-05 23:42:10 -05:00
|
|
|
(zmq-poller-add ,poller (oref channel socket) zmq-POLLIN)
|
2018-02-07 12:04:29 -06:00
|
|
|
;; Let the channel start. This avoids problems with the initial
|
|
|
|
;; startup message for the python kernel. Sometimes we arent fast
|
|
|
|
;; enough to get this message.
|
|
|
|
(sleep-for 0.1)
|
2018-02-03 00:02:33 -06:00
|
|
|
(zmq-prin1 (list 'start-channel ctype)))))
|
2018-01-07 23:21:53 -06:00
|
|
|
(stop-channel
|
2018-02-03 00:02:33 -06:00
|
|
|
(cl-destructuring-bind (ctype) args
|
|
|
|
(let ((channel (cdr (assoc ctype ,channels))))
|
2018-05-05 23:42:10 -05:00
|
|
|
(zmq-poller-remove ,poller (oref channel socket))
|
2018-02-03 00:02:33 -06:00
|
|
|
(jupyter-stop-channel channel)
|
|
|
|
(zmq-prin1 (list 'stop-channel ctype)))))
|
2018-01-07 23:21:53 -06:00
|
|
|
(quit
|
|
|
|
(signal 'quit nil))
|
|
|
|
(otherwise (error "Unhandled command (%s)" cmd)))))
|
|
|
|
|
2018-09-09 20:10:58 -05:00
|
|
|
(defmacro jupyter--ioloop-with-lock-file (client &rest body)
|
|
|
|
"In CLIENT's IOLoop buffer run BODY, ensuring the lock file mechanism works.
|
|
|
|
This makes sure that when `set-buffer-modified-p' is called, it
|
|
|
|
properly locks or unlocks the associated lock file for the IOLoop
|
|
|
|
process."
|
|
|
|
(declare (indent 1))
|
|
|
|
(let ((lock (make-symbol "--lock")))
|
|
|
|
`(with-current-buffer (oref ,client -buffer)
|
|
|
|
(let* ((create-lockfiles t)
|
|
|
|
(,lock (concat "jupyter-lock" (jupyter-session-id (oref ,client session))))
|
|
|
|
(buffer-file-name (expand-file-name ,lock temporary-file-directory))
|
|
|
|
(buffer-file-truename (file-truename buffer-file-name)))
|
|
|
|
,@body))))
|
|
|
|
|
|
|
|
(defun jupyter--ioloop-unlock (client)
|
|
|
|
"Unlock CLIENT's file lock."
|
|
|
|
(jupyter--ioloop-with-lock-file client
|
|
|
|
(set-buffer-modified-p nil)))
|
|
|
|
|
|
|
|
(defun jupyter--ioloop-lock (client)
|
|
|
|
"Return the file name of CLIENT's file lock.
|
|
|
|
Acquire the lock first. Unlock the lock if one exists. The lock
|
|
|
|
file serves as a proxy for the case when the parent Emacs process
|
|
|
|
crashes without properly cleaning up its child processes."
|
|
|
|
(jupyter--ioloop-with-lock-file client
|
|
|
|
(jupyter--ioloop-unlock client)
|
|
|
|
(set-buffer-modified-p t)
|
|
|
|
(buffer-file-name)))
|
|
|
|
|
2017-12-17 02:39:16 -06:00
|
|
|
(defun jupyter--ioloop (client)
|
2018-01-07 23:21:53 -06:00
|
|
|
"Return the function used for communicating with CLIENT's kernel."
|
2018-05-12 14:49:38 -05:00
|
|
|
(let ((sid (jupyter-session-id (oref client session)))
|
2018-09-09 20:10:58 -05:00
|
|
|
(skey (jupyter-session-key (oref client session)))
|
|
|
|
(lock (jupyter--ioloop-lock client)))
|
2017-12-17 02:39:16 -06:00
|
|
|
`(lambda (ctx)
|
2018-01-07 23:21:53 -06:00
|
|
|
(push ,(file-name-directory (locate-library "jupyter-base")) load-path)
|
2018-05-12 14:49:38 -05:00
|
|
|
(require 'jupyter-base)
|
2018-01-07 23:21:53 -06:00
|
|
|
(require 'jupyter-channels)
|
|
|
|
(require 'jupyter-messages)
|
2018-01-11 12:04:42 -06:00
|
|
|
(let* ((session (jupyter-session :id ,sid :key ,skey))
|
2018-05-12 14:49:38 -05:00
|
|
|
(iopub (jupyter-sync-channel :type :iopub :session session))
|
|
|
|
(shell (jupyter-sync-channel :type :shell :session session))
|
|
|
|
(stdin (jupyter-sync-channel :type :stdin :session session))
|
2018-02-03 00:02:33 -06:00
|
|
|
(channels `((:stdin . ,stdin)
|
|
|
|
(:shell . ,shell)
|
|
|
|
(:iopub . ,iopub)))
|
2018-09-09 20:06:01 -05:00
|
|
|
(messages nil)
|
|
|
|
(poller nil)
|
2018-10-10 00:10:16 -05:00
|
|
|
(events nil)
|
2018-09-09 20:06:01 -05:00
|
|
|
(read-command-from-parent
|
|
|
|
(lambda ()
|
|
|
|
(when (alist-get 0 events)
|
|
|
|
(setf (alist-get 0 events nil 'remove) nil)
|
|
|
|
(jupyter--ioloop-do-command poller channels))))
|
|
|
|
(queue-messages
|
|
|
|
(lambda ()
|
|
|
|
(dolist (type-channel channels)
|
|
|
|
(cl-destructuring-bind (type . channel) type-channel
|
|
|
|
(when (zmq-assoc (oref channel socket) events)
|
|
|
|
(push (cons type (jupyter-recv channel)) messages))))))
|
|
|
|
(send-messages-to-parent
|
|
|
|
(lambda ()
|
|
|
|
;; TODO: Throttle messages if they are coming in too hot
|
|
|
|
(when messages
|
|
|
|
(setq messages (nreverse messages))
|
|
|
|
(while messages
|
|
|
|
(prin1 (cons 'recvd (pop messages))))
|
|
|
|
(zmq-flush 'stdout)))))
|
2018-01-11 12:04:42 -06:00
|
|
|
(condition-case nil
|
2018-09-09 20:06:01 -05:00
|
|
|
(progn
|
|
|
|
(setq poller (zmq-poller))
|
2018-01-07 23:21:53 -06:00
|
|
|
;; Poll for stdin messages
|
2018-05-05 23:42:10 -05:00
|
|
|
(zmq-poller-add poller 0 zmq-POLLIN)
|
2018-02-04 17:55:47 -06:00
|
|
|
(zmq-prin1 '(start))
|
2018-01-07 23:21:53 -06:00
|
|
|
(while t
|
2018-10-10 00:10:16 -05:00
|
|
|
(setq events (condition-case nil
|
|
|
|
(zmq-poller-wait-all
|
|
|
|
poller (1+ (length channels)) 5000)
|
|
|
|
((zmq-EAGAIN zmq-EINTR zmq-ETIMEDOUT) nil)))
|
|
|
|
(unless (or events (file-locked-p ,lock))
|
|
|
|
;; TODO: The parent process probably
|
|
|
|
;; crashed, cleanup the kernel
|
|
|
|
;; connection file if there is one.
|
|
|
|
;; Since the parent Emacs crashed, all
|
|
|
|
;; of the kernel processes are gone to.
|
|
|
|
(signal 'quit nil))
|
|
|
|
(funcall read-command-from-parent)
|
|
|
|
(funcall queue-messages)
|
|
|
|
(funcall send-messages-to-parent)))
|
2018-01-07 23:21:53 -06:00
|
|
|
(quit
|
2018-02-04 17:43:59 -06:00
|
|
|
(mapc #'jupyter-stop-channel (mapcar #'cdr channels))
|
|
|
|
(zmq-prin1 '(quit))))))))
|
2017-12-19 18:47:57 -06:00
|
|
|
|
2018-01-02 00:56:02 -06:00
|
|
|
(defun jupyter--ioloop-pop-request (client)
|
2018-01-07 14:06:14 -06:00
|
|
|
"Remove a pending request from CLIENT's ioloop subprocess.
|
|
|
|
Specifically remove the oldest element of the ring located in the
|
2018-01-23 13:44:12 -06:00
|
|
|
`:pending-requests' property of CLIENT's ioloop
|
2018-01-07 14:06:14 -06:00
|
|
|
subprocess."
|
2018-01-17 20:15:10 -06:00
|
|
|
(let ((ring (process-get (oref client ioloop) :pending-requests)))
|
2018-01-12 17:42:53 -06:00
|
|
|
(unless (ring-empty-p ring)
|
|
|
|
(ring-remove ring))))
|
2018-01-02 00:56:02 -06:00
|
|
|
|
|
|
|
(defun jupyter--ioloop-push-request (client req)
|
2018-01-07 14:06:14 -06:00
|
|
|
"Insert a request into CLIENT's pending requests.
|
2018-01-23 13:44:12 -06:00
|
|
|
Pending requests are stored in a ring located in the
|
|
|
|
`:pending-requests' property of an ioloop subprocess. REQ is
|
|
|
|
added as the newest element in this ring."
|
|
|
|
(let ((ring (or (process-get (oref client ioloop) :pending-requests)
|
|
|
|
(let ((ring (make-ring 10)))
|
|
|
|
(process-put (oref client ioloop) :pending-requests ring)
|
|
|
|
ring))))
|
2018-01-02 00:56:02 -06:00
|
|
|
(ring-insert+extend ring req 'grow)))
|
|
|
|
|
2018-05-13 11:08:49 -05:00
|
|
|
;;; HB channel methods
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-hb-pause ((client jupyter-kernel-client))
|
|
|
|
"Pause CLIENT's heartbeeat channel."
|
|
|
|
(jupyter-hb-pause (oref client hb-channel)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-hb-unpause ((client jupyter-kernel-client))
|
|
|
|
"Unpause CLIENT's heartbeat channel."
|
|
|
|
(jupyter-hb-unpause (oref client hb-channel)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-hb-beating-p ((client jupyter-kernel-client))
|
|
|
|
"Is CLIENT still connected to its kernel?"
|
|
|
|
(jupyter-hb-beating-p (oref client hb-channel)))
|
|
|
|
|
2018-01-13 22:51:27 -06:00
|
|
|
;;; Channel subprocess filter/sentinel
|
|
|
|
|
2018-02-09 17:23:46 -06:00
|
|
|
(defun jupyter--ioloop-sentinel (client ioloop _event)
|
2018-01-07 14:06:14 -06:00
|
|
|
"The process sentinel for CLIENT's IOLOOP subprocess.
|
|
|
|
When EVENT is one of the events signifying that the process is
|
|
|
|
dead, stop the heartbeat channel and set the IOLOOP slot to nil
|
|
|
|
in CLIENT."
|
2017-12-27 00:00:59 -06:00
|
|
|
(cond
|
2018-02-09 17:23:46 -06:00
|
|
|
((not (process-live-p ioloop))
|
2018-01-04 17:08:41 -06:00
|
|
|
(jupyter-stop-channel (oref client hb-channel))
|
2018-09-09 20:10:58 -05:00
|
|
|
(jupyter--ioloop-unlock client)
|
2018-09-16 21:17:10 -05:00
|
|
|
(oset client kernel-info nil)
|
2017-12-27 00:00:59 -06:00
|
|
|
(oset client ioloop nil))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-02-03 19:10:30 -06:00
|
|
|
(defun jupyter--get-channel (client ctype)
|
|
|
|
"Get CLIENT's channel based on CTYPE."
|
2018-09-09 20:38:32 -05:00
|
|
|
(catch 'found
|
|
|
|
(dolist (channel (mapcar (lambda (sym) (slot-value client sym))
|
|
|
|
'(hb-channel
|
|
|
|
stdin-channel
|
|
|
|
shell-channel
|
|
|
|
iopub-channel)))
|
|
|
|
(when (eq (oref channel type) ctype)
|
|
|
|
(throw 'found channel)))))
|
2018-02-03 19:10:30 -06:00
|
|
|
|
2017-12-17 02:39:16 -06:00
|
|
|
(defun jupyter--ioloop-filter (client event)
|
2018-01-06 21:04:03 -06:00
|
|
|
"The process filter for CLIENT's ioloop subprocess.
|
|
|
|
EVENT will be an s-expression emitted from the function returned
|
|
|
|
by `jupyter--ioloop'."
|
|
|
|
(pcase event
|
|
|
|
(`(sent ,ctype ,msg-id)
|
|
|
|
(when jupyter--debug
|
2018-01-18 23:09:10 -06:00
|
|
|
(message "SENT: %s" msg-id))
|
2018-01-06 21:04:03 -06:00
|
|
|
(unless (eq ctype :stdin)
|
|
|
|
;; Anything sent on stdin is a reply and therefore never added to
|
2018-01-17 20:15:10 -06:00
|
|
|
;; `:pending-requests'
|
2018-05-27 14:50:20 -05:00
|
|
|
(let ((req (jupyter--ioloop-pop-request client))
|
|
|
|
(requests (oref client requests)))
|
2018-05-25 02:07:47 -05:00
|
|
|
(cl-assert (equal (jupyter-request-id req) msg-id)
|
|
|
|
nil "Message request sent out of order to the kernel.")
|
2018-05-27 14:50:20 -05:00
|
|
|
(puthash msg-id req requests)
|
|
|
|
(puthash "last-sent" req requests))))
|
2018-01-07 23:21:53 -06:00
|
|
|
(`(recvd ,ctype ,idents . ,msg)
|
2018-01-06 21:04:03 -06:00
|
|
|
(when jupyter--debug
|
2018-01-18 23:09:10 -06:00
|
|
|
(message "RECV: %s %s %s %s"
|
|
|
|
idents
|
2018-01-07 23:21:53 -06:00
|
|
|
(jupyter-message-type msg)
|
|
|
|
(jupyter-message-parent-id msg)
|
|
|
|
(jupyter-message-content msg)))
|
2018-02-03 19:10:30 -06:00
|
|
|
(let ((channel (jupyter--get-channel client ctype)))
|
2018-01-18 23:04:22 -06:00
|
|
|
(if (not channel) (warn "No handler for channel type (%s)" ctype)
|
|
|
|
(jupyter-queue-message channel (cons idents msg))
|
|
|
|
(run-with-timer 0.0001 nil #'jupyter-handle-message client channel))))
|
2018-02-03 00:02:33 -06:00
|
|
|
(`(start-channel ,ctype)
|
2018-05-13 11:10:14 -05:00
|
|
|
(when jupyter--debug
|
|
|
|
(message "STARTING-CHANNEL: %s" ctype))
|
2018-02-03 19:10:30 -06:00
|
|
|
(let ((channel (jupyter--get-channel client ctype)))
|
2018-02-03 00:02:33 -06:00
|
|
|
(oset channel status 'running)))
|
|
|
|
(`(stop-channel ,ctype)
|
2018-05-13 11:10:14 -05:00
|
|
|
(when jupyter--debug
|
|
|
|
(message "STOPPING-CHANNEL: %s" ctype))
|
2018-02-03 19:10:30 -06:00
|
|
|
(let ((channel (jupyter--get-channel client ctype)))
|
2018-02-03 00:02:33 -06:00
|
|
|
(oset channel status 'stopped)))
|
2018-02-04 17:55:47 -06:00
|
|
|
('(start)
|
2018-05-13 11:10:14 -05:00
|
|
|
(when jupyter--debug
|
|
|
|
(message "CLIENT STARTING"))
|
2018-02-04 17:55:47 -06:00
|
|
|
;; TODO: Generalize setting flag variables for IOLoop events and having
|
|
|
|
;; event callbacks.
|
|
|
|
(process-put (oref client ioloop) :start t))
|
2018-01-06 21:04:03 -06:00
|
|
|
('(quit)
|
|
|
|
;; Cleanup handled in sentinel
|
2018-05-13 11:10:14 -05:00
|
|
|
(process-put (oref client ioloop) :quit t)
|
2018-01-06 21:04:03 -06:00
|
|
|
(when jupyter--debug
|
2018-01-08 22:44:32 -06:00
|
|
|
(message "CLIENT CLOSED")))))
|
2018-01-22 19:12:47 -06:00
|
|
|
|
2018-01-13 22:51:27 -06:00
|
|
|
;;; Starting the channel subprocess
|
2017-12-31 15:22:48 -06:00
|
|
|
|
2018-02-04 17:55:47 -06:00
|
|
|
(defun jupyter-ioloop-wait-until (event ioloop &optional timeout)
|
2018-05-12 14:52:35 -05:00
|
|
|
"Wait until EVENT occurs in IOLOOP.
|
|
|
|
Currently EVENT can be :start or :quit and this function will
|
2018-10-01 23:13:07 -05:00
|
|
|
block for TIMEOUT seconds until IOLOOP starts or quits depending
|
2018-05-12 14:52:35 -05:00
|
|
|
on EVENT. If TIMEOUT is nil it defaults to 1 s."
|
2018-02-04 17:55:47 -06:00
|
|
|
(or timeout (setq timeout 1))
|
|
|
|
(with-timeout (timeout nil)
|
|
|
|
(while (null (process-get ioloop event))
|
|
|
|
(sleep-for 0.01))
|
|
|
|
t))
|
|
|
|
|
2018-02-03 00:02:33 -06:00
|
|
|
(defun jupyter--start-ioloop (client)
|
2018-10-01 23:13:07 -05:00
|
|
|
"Start CLIENT's channel subprocess."
|
2018-02-03 00:02:33 -06:00
|
|
|
(unless (oref client ioloop)
|
|
|
|
(oset client ioloop
|
|
|
|
(zmq-start-process
|
|
|
|
(jupyter--ioloop client)
|
2018-10-04 14:47:14 -05:00
|
|
|
:filter (apply-partially #'jupyter--ioloop-filter client)
|
|
|
|
:sentinel (apply-partially #'jupyter--ioloop-sentinel client)
|
|
|
|
:buffer (oref client -buffer)))
|
2018-02-04 17:55:47 -06:00
|
|
|
(jupyter-ioloop-wait-until :start (oref client ioloop))))
|
2018-02-03 00:02:33 -06:00
|
|
|
|
2017-12-13 11:27:13 -06:00
|
|
|
(cl-defmethod jupyter-start-channels ((client jupyter-kernel-client)
|
|
|
|
&key (shell t)
|
|
|
|
(iopub t)
|
|
|
|
(stdin t)
|
|
|
|
(hb t))
|
2017-12-15 18:14:28 -06:00
|
|
|
"Start the pre-configured channels of CLIENT.
|
2018-09-16 21:31:18 -05:00
|
|
|
Before starting the channels, ensure that the channel subprocess
|
|
|
|
responsible for encoding/decoding messages and sending/receiving
|
|
|
|
messages to/from the kernel is running.
|
|
|
|
|
|
|
|
Call `jupyter-start-channel' for every channel whose key has a
|
|
|
|
non-nil value passed to this function.
|
|
|
|
|
|
|
|
If the shell channel is started, send an initial
|
|
|
|
`:kernel-info-request' to set the kernel-info slot of CLIENT if
|
|
|
|
necessary."
|
2018-02-03 00:02:33 -06:00
|
|
|
(jupyter--start-ioloop client)
|
|
|
|
(when hb
|
|
|
|
(jupyter-start-channel (oref client hb-channel)))
|
|
|
|
(cl-loop
|
|
|
|
for (sym . start) in `((shell-channel . ,shell)
|
|
|
|
(iopub-channel . ,iopub)
|
|
|
|
(stdin-channel . ,stdin))
|
|
|
|
for channel = (slot-value client sym)
|
|
|
|
do (oset channel ioloop (oref client ioloop))
|
2018-02-04 17:43:20 -06:00
|
|
|
and if start do (jupyter-start-channel channel)))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-19 18:13:42 -06:00
|
|
|
(cl-defmethod jupyter-stop-channels ((client jupyter-kernel-client))
|
|
|
|
"Stop any running channels of CLIENT."
|
2018-02-03 00:02:33 -06:00
|
|
|
(cl-loop
|
|
|
|
for sym in '(hb-channel shell-channel iopub-channel stdin-channel)
|
|
|
|
for channel = (slot-value client sym)
|
|
|
|
when channel do (jupyter-stop-channel channel))
|
2017-12-19 18:13:42 -06:00
|
|
|
(let ((ioloop (oref client ioloop)))
|
2018-05-13 11:09:20 -05:00
|
|
|
(when (process-live-p ioloop)
|
2017-12-19 18:47:57 -06:00
|
|
|
(zmq-subprocess-send ioloop (cons 'quit nil))
|
2018-01-16 12:02:23 -06:00
|
|
|
(with-timeout (1 (delete-process ioloop)
|
2018-05-13 11:09:20 -05:00
|
|
|
(message "IOloop process not killed by request"))
|
2017-12-19 18:47:57 -06:00
|
|
|
(while (oref client ioloop)
|
|
|
|
(sleep-for 0 100))))))
|
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))
|
2018-01-18 23:10:16 -06:00
|
|
|
"Are any channels of CLIENT running?"
|
2017-12-13 11:27:13 -06:00
|
|
|
(cl-loop
|
2018-02-03 00:02:33 -06:00
|
|
|
for sym in '(hb-channel shell-channel iopub-channel stdin-channel)
|
|
|
|
for channel = (slot-value client sym)
|
|
|
|
thereis (jupyter-channel-alive-p channel)))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2017-12-15 22:26:21 -06:00
|
|
|
;;; Message callbacks
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-01-06 15:31:39 -06:00
|
|
|
(defun jupyter--run-callbacks (req msg)
|
|
|
|
"Run REQ's MSG callbacks.
|
|
|
|
See `jupyter-add-callback'."
|
2017-12-19 18:02:55 -06:00
|
|
|
(when req
|
|
|
|
(let* ((callbacks (jupyter-request-callbacks req))
|
2018-01-06 19:47:47 -06:00
|
|
|
(cb-all-types (cdr (assoc t callbacks)))
|
|
|
|
(cb-for-type (cdr (assoc (jupyter-message-type msg) callbacks))))
|
|
|
|
(and cb-all-types (funcall cb-all-types msg))
|
|
|
|
(and cb-for-type (funcall cb-for-type msg)))))
|
2017-12-19 18:02:55 -06:00
|
|
|
|
2018-01-06 15:31:39 -06:00
|
|
|
(defun jupyter--add-callback (req msg-type cb)
|
2018-01-06 16:37:18 -06:00
|
|
|
"Helper function for `jupyter-add-callback'.
|
2018-05-12 14:52:35 -05:00
|
|
|
REQ is a `jupyter-request' object, MSG-TYPE is one of the
|
2018-01-06 16:37:18 -06:00
|
|
|
keywords corresponding to a received message type in
|
2018-05-12 14:52:35 -05:00
|
|
|
`jupyter-message-types', and CB is the callback that will be run
|
|
|
|
when MSG-TYPE is received for REQ."
|
2018-05-16 11:33:05 -05:00
|
|
|
(unless (or (plist-member jupyter-message-types msg-type)
|
|
|
|
;; A msg-type of t means that FUNCTION is run for all messages
|
|
|
|
;; associated with a request.
|
|
|
|
(eq msg-type t))
|
2018-01-06 15:31:39 -06:00
|
|
|
(error "Not a valid message type (`%s')" msg-type))
|
|
|
|
(let ((callbacks (jupyter-request-callbacks req)))
|
|
|
|
(if (null callbacks)
|
|
|
|
(setf (jupyter-request-callbacks req)
|
|
|
|
(list (cons msg-type cb)))
|
|
|
|
(let ((cb-for-type (assoc msg-type callbacks)))
|
|
|
|
(if (not cb-for-type)
|
|
|
|
(nconc callbacks (list (cons msg-type cb)))
|
2018-01-17 20:11:43 -06:00
|
|
|
(setcdr cb-for-type
|
|
|
|
(let ((ccb (cdr cb-for-type)))
|
|
|
|
(lambda (msg)
|
|
|
|
(funcall ccb msg)
|
|
|
|
(funcall cb msg)))))))))
|
2018-01-06 15:31:39 -06:00
|
|
|
|
|
|
|
(defun jupyter-add-callback (req msg-type cb &rest callbacks)
|
|
|
|
"Add a callback to run when a message is received for a request.
|
|
|
|
REQ is a `jupyter-request' returned by one of the request methods
|
2018-02-04 18:05:53 -06:00
|
|
|
of a `jupyter-kernel-client'. MSG-TYPE is one of the keys in
|
|
|
|
`jupyter-message-types'. CB is the callback function to run when
|
|
|
|
a message with MSG-TYPE is received for REQ.
|
|
|
|
|
2018-01-08 18:11:08 -06:00
|
|
|
MSG-TYPE can also be a list, in which case run CB for every
|
2018-02-04 18:05:53 -06:00
|
|
|
MSG-TYPE in the list. If MSG-TYPE is t, run CB for every message
|
|
|
|
received for REQ.
|
2018-01-06 16:37:18 -06:00
|
|
|
|
|
|
|
Any additional arguments to `jupyter-add-callback' are
|
|
|
|
interpreted as additional CALLBACKS to add to REQ. So to add
|
2018-02-04 18:05:53 -06:00
|
|
|
multiple callbacks you would do
|
2018-01-06 15:31:39 -06:00
|
|
|
|
|
|
|
(jupyter-add-callback
|
2018-05-15 23:40:09 -05:00
|
|
|
(jupyter-send-execute-request client :code \"1 + 2\")
|
2018-01-06 15:31:39 -06:00
|
|
|
:status (lambda (msg) ...)
|
|
|
|
:execute-reply (lambda (msg) ...)
|
|
|
|
:execute-result (lambda (msg) ...))"
|
|
|
|
(declare (indent 1))
|
|
|
|
(if (jupyter-request-idle-received-p req)
|
|
|
|
(error "Request already received idle message")
|
|
|
|
(setq callbacks (append (list msg-type cb) callbacks))
|
|
|
|
(cl-loop for (msg-type cb) on callbacks by 'cddr
|
|
|
|
if (listp msg-type)
|
|
|
|
do (mapc (lambda (mt) (jupyter--add-callback req mt cb)) msg-type)
|
|
|
|
else do (jupyter--add-callback req msg-type cb))))
|
|
|
|
|
2018-01-13 22:51:27 -06:00
|
|
|
;;; Waiting for messages
|
|
|
|
|
2018-01-06 16:37:18 -06:00
|
|
|
(defun jupyter-wait-until (req msg-type cb &optional timeout)
|
|
|
|
"Wait until conditions for a request are satisfied.
|
|
|
|
REQ, MSG-TYPE, and CB have the same meaning as in
|
2018-01-12 17:43:55 -06:00
|
|
|
`jupyter-add-callback'. If CB returns non-nil within TIMEOUT
|
2018-01-06 16:37:18 -06:00
|
|
|
seconds, return the message that caused CB to return non-nil. If
|
|
|
|
CB never returns a non-nil value within TIMEOUT, return nil. Note
|
|
|
|
that if no TIMEOUT is given, `jupyter-default-timeout' is used."
|
2018-01-22 19:24:26 -06:00
|
|
|
(declare (indent 2))
|
2018-01-04 20:58:28 -06:00
|
|
|
(setq timeout (or timeout jupyter-default-timeout))
|
2017-12-19 18:16:58 -06:00
|
|
|
(cl-check-type timeout number)
|
2018-01-22 19:24:26 -06:00
|
|
|
(let (msg)
|
2018-01-06 15:31:39 -06:00
|
|
|
(jupyter-add-callback req
|
2018-01-06 16:37:18 -06:00
|
|
|
msg-type (lambda (m) (setq msg (when (funcall cb m) m))))
|
2018-01-02 13:23:28 -06:00
|
|
|
(with-timeout (timeout nil)
|
|
|
|
(while (null msg)
|
|
|
|
(sleep-for 0.01))
|
|
|
|
msg)))
|
2017-12-15 18:18:41 -06:00
|
|
|
|
2017-12-31 13:54:43 -06:00
|
|
|
(defun jupyter-wait-until-idle (req &optional timeout)
|
2018-01-06 16:37:18 -06:00
|
|
|
"Wait until a status: idle message is received for a request.
|
|
|
|
REQ has the same meaning as in `jupyter-add-callback'. If an idle
|
|
|
|
message for REQ is received within TIMEOUT seconds, return the
|
|
|
|
message. Otherwise return nil if the message was not received
|
|
|
|
within TIMEOUT. Note that if no TIMEOUT is given, it defaults to
|
|
|
|
`jupyter-default-timeout'."
|
2018-01-06 15:31:39 -06:00
|
|
|
(jupyter-wait-until req :status #'jupyter-message-status-idle-p timeout))
|
2017-12-15 18:18:41 -06:00
|
|
|
|
2017-12-31 13:54:43 -06:00
|
|
|
(defun jupyter-wait-until-received (msg-type req &optional timeout)
|
2018-01-06 16:37:18 -06:00
|
|
|
"Wait until a message of a certain type is received for a request.
|
2018-05-12 14:52:35 -05:00
|
|
|
MSG-TYPE and REQ have the same meaning as their corresponding
|
|
|
|
arguments in `jupyter-add-callback'. If no message that matches
|
2018-01-06 16:37:18 -06:00
|
|
|
MSG-TYPE is received for REQ within TIMEOUT seconds, return nil.
|
|
|
|
Otherwise return the first message that matched MSG-TYPE. Note
|
|
|
|
that if no TIMEOUT is given, it defaults to
|
|
|
|
`jupyter-default-timeout'."
|
2017-12-31 13:54:43 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 15:31:39 -06:00
|
|
|
(jupyter-wait-until req msg-type #'identity timeout))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-01-13 22:51:27 -06:00
|
|
|
;;; Client handlers
|
2018-01-11 12:14:35 -06:00
|
|
|
|
2018-06-03 22:04:21 -05:00
|
|
|
(cl-defgeneric jupyter-drop-request ((_client jupyter-kernel-client) _req)
|
|
|
|
"Called when CLIENT removes REQ, from its request table."
|
|
|
|
nil)
|
|
|
|
|
2018-01-11 12:06:26 -06:00
|
|
|
(defun jupyter--drop-idle-requests (client)
|
2018-02-04 17:41:48 -06:00
|
|
|
"Drop completed requests from CLIENT's request table.
|
|
|
|
A request is deemed complete when an idle message has been
|
2018-05-27 14:50:20 -05:00
|
|
|
received for it and it is not the most recently sent request."
|
2018-01-11 12:06:26 -06:00
|
|
|
(cl-loop
|
|
|
|
with requests = (oref client requests)
|
2018-05-27 14:50:20 -05:00
|
|
|
with last-sent = (gethash "last-sent" requests)
|
2018-01-11 12:06:26 -06:00
|
|
|
for req in (hash-table-values requests)
|
2018-02-12 10:36:22 -06:00
|
|
|
when (and (jupyter-request-idle-received-p req)
|
2018-05-27 14:50:20 -05:00
|
|
|
(not (eq req last-sent)))
|
2018-01-18 23:09:10 -06:00
|
|
|
do (when jupyter--debug
|
2018-05-27 14:50:20 -05:00
|
|
|
(message "DROPPING-REQ: %s" (jupyter-request-id req)))
|
2018-06-03 22:04:21 -05:00
|
|
|
(remhash (jupyter-request-id req) requests)
|
|
|
|
(jupyter-drop-request client req)))
|
2018-01-11 12:06:26 -06:00
|
|
|
|
2018-02-08 13:38:27 -06:00
|
|
|
(defun jupyter--run-handler-maybe (client channel req msg)
|
2018-05-26 20:04:02 -05:00
|
|
|
"Possibly run CLIENT's CHANNEL handler on REQ's received MSG."
|
2018-02-09 17:22:45 -06:00
|
|
|
(let ((inhibited-handlers (and req (jupyter-request-inhibited-handlers req))))
|
2018-02-08 13:38:27 -06:00
|
|
|
(unless (or (eq inhibited-handlers t)
|
2018-05-16 11:33:05 -05:00
|
|
|
(memq (jupyter-message-type msg) inhibited-handlers))
|
2018-02-08 13:38:27 -06:00
|
|
|
(jupyter-handle-message channel client req msg))))
|
|
|
|
|
2018-01-06 19:43:21 -06:00
|
|
|
(cl-defmethod jupyter-handle-message ((client jupyter-kernel-client) channel)
|
2017-12-13 11:27:13 -06:00
|
|
|
"Process a message on CLIENT's CHANNEL.
|
2018-01-06 19:43:21 -06:00
|
|
|
When a message is received from the kernel, the
|
|
|
|
`jupyter-handle-message' method is called on the client. The
|
|
|
|
client method runs any callbacks for the message and possibly
|
|
|
|
runs the client handler for the channel the message was received
|
|
|
|
on. The channel's `jupyter-handle-message' method will then pass
|
2018-05-15 23:41:19 -05:00
|
|
|
the message to the appropriate message handler based on the
|
|
|
|
message type.
|
2018-01-06 19:43:21 -06:00
|
|
|
|
|
|
|
So when a message is received from the kernel the following steps
|
|
|
|
are taken:
|
|
|
|
|
|
|
|
- `jupyter-handle-message' (client)
|
|
|
|
- Run callbacks for message
|
|
|
|
- Possibly run channel handlers
|
|
|
|
- `jupyter-handle-message' (channel)
|
|
|
|
- Based on message type, dispatch to
|
|
|
|
`jupyter-handle-execute-result',
|
|
|
|
`jupyter-handle-kernel-info-reply', ...
|
2018-01-08 18:11:08 -06:00
|
|
|
- Remove request from client request table when idle message is received"
|
2018-02-03 00:02:33 -06:00
|
|
|
(let ((msg (jupyter-get-message channel)))
|
|
|
|
(when msg
|
|
|
|
(let* ((pmsg-id (jupyter-message-parent-id msg))
|
|
|
|
(requests (oref client requests))
|
|
|
|
(req (gethash pmsg-id requests)))
|
|
|
|
(if (not req)
|
|
|
|
(when (jupyter-get client 'jupyter-include-other-output)
|
2018-02-08 13:38:27 -06:00
|
|
|
(jupyter--run-handler-maybe client channel req msg))
|
2018-05-20 12:09:00 -05:00
|
|
|
(setf (jupyter-request-last-message req) msg)
|
2017-12-15 22:26:21 -06:00
|
|
|
(unwind-protect
|
2018-02-03 00:02:33 -06:00
|
|
|
(jupyter--run-callbacks req msg)
|
|
|
|
(unwind-protect
|
2018-02-08 13:38:27 -06:00
|
|
|
(jupyter--run-handler-maybe client channel req msg)
|
2018-02-03 00:02:33 -06:00
|
|
|
(when (jupyter-message-status-idle-p msg)
|
|
|
|
(setf (jupyter-request-idle-received-p req) t))
|
|
|
|
(jupyter--drop-idle-requests client))))))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-05-12 14:50:21 -05:00
|
|
|
;;; Channel handler macros
|
|
|
|
|
|
|
|
(defmacro jupyter-dispatch-message-cases (client req msg cases)
|
|
|
|
"Dispatch to CLIENT handler's based on REQ and MSG.
|
2018-06-14 21:07:22 -05:00
|
|
|
CASES defines the handlers to dispatch to based on the
|
2018-05-12 14:50:21 -05:00
|
|
|
`jupyter-message-type' of MSG and should be a list of lists, the
|
|
|
|
first element of each inner list being the name of the handler,
|
|
|
|
excluding the `jupyter-handle-' prefix. The rest of the elements
|
2018-06-14 21:07:22 -05:00
|
|
|
in the list are the name of the keys that will be extracted from
|
2018-05-12 14:50:21 -05:00
|
|
|
the `jupyter-message-content' of MSG and passed to the handler in
|
|
|
|
the same order as they appear. For example,
|
|
|
|
|
|
|
|
(jupyter-dispatch-message-cases client req msg
|
|
|
|
((shutdown-reply restart)
|
|
|
|
(stream name text)))
|
|
|
|
|
|
|
|
will be transformed to
|
|
|
|
|
|
|
|
(let ((content (jupyter-message-content msg)))
|
|
|
|
(pcase (jupyter-message-type msg)
|
2018-05-16 11:33:05 -05:00
|
|
|
(:shutdown-reply
|
2018-06-14 21:07:22 -05:00
|
|
|
(cl-destructuring-bind (&key restart &allow-other-keys)
|
2018-05-12 14:50:21 -05:00
|
|
|
content
|
|
|
|
(jupyter-handle-shutdown-reply client req restart)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(:stream
|
2018-06-14 21:07:22 -05:00
|
|
|
(cl-destructuring-bind (&key name text &allow-other-keys)
|
2018-05-12 14:50:21 -05:00
|
|
|
content
|
|
|
|
(jupyter-handle-stream client req name text)))
|
|
|
|
(_ (warn \"Message type not handled (%s)\"
|
|
|
|
(jupyter-message-type msg)))))"
|
|
|
|
(declare (indent 3))
|
|
|
|
(let ((handlers nil)
|
|
|
|
(content (make-symbol "contentvar"))
|
|
|
|
(jclient (make-symbol "clientvar"))
|
|
|
|
(jreq (make-symbol "reqvar"))
|
|
|
|
(jmsg (make-symbol "msgvar")))
|
|
|
|
(dolist (case cases)
|
|
|
|
(cl-destructuring-bind (msg-type . keys) case
|
2018-05-16 11:33:05 -05:00
|
|
|
(let ((handler (intern (format "jupyter-handle-%s" msg-type)))
|
|
|
|
(msg-type (intern (concat ":" (symbol-name msg-type)))))
|
|
|
|
(push `(,msg-type
|
2018-05-12 14:50:21 -05:00
|
|
|
(cl-destructuring-bind (&key ,@keys &allow-other-keys)
|
|
|
|
,content
|
|
|
|
(,handler ,jclient ,jreq ,@keys)))
|
|
|
|
handlers))))
|
|
|
|
`(let* ((,jmsg ,msg)
|
|
|
|
(,jreq ,req)
|
|
|
|
(,jclient ,client)
|
|
|
|
(,content (jupyter-message-content ,jmsg)))
|
|
|
|
(pcase (jupyter-message-type ,jmsg)
|
|
|
|
,@handlers
|
|
|
|
(_ (warn "Message type not handled (%s)"
|
|
|
|
(jupyter-message-type msg)))))))
|
|
|
|
|
2018-01-13 22:51:27 -06:00
|
|
|
;;; STDIN handlers
|
2018-01-06 19:43:21 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defmethod jupyter-handle-message ((_channel jupyter-stdin-channel)
|
2018-01-04 15:56:04 -06:00
|
|
|
client
|
|
|
|
req
|
|
|
|
msg)
|
2018-05-25 21:12:54 -05:00
|
|
|
(unless (jupyter-run-hook-with-args-until-success
|
|
|
|
client 'jupyter-stdin-message-hook msg)
|
|
|
|
(jupyter-dispatch-message-cases client req msg
|
2018-10-10 00:12:56 -05:00
|
|
|
((input-reply prompt password)
|
|
|
|
(input-request prompt password)))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-10-10 00:12:56 -05:00
|
|
|
(cl-defgeneric jupyter-handle-input-request ((client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
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."
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-16 11:58:55 -06:00
|
|
|
(let* ((channel (oref client stdin-channel))
|
|
|
|
(value nil)
|
|
|
|
(msg (jupyter-message-input-reply
|
|
|
|
:value (condition-case nil
|
2018-10-10 00:12:56 -05:00
|
|
|
(if (eq password t) (read-passwd prompt)
|
2018-01-16 11:58:55 -06:00
|
|
|
(setq value (read-from-minibuffer prompt)))
|
|
|
|
(quit "")))))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send client channel :input-reply msg)
|
2018-01-16 11:58:55 -06:00
|
|
|
(or value "")))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-10-10 00:12:56 -05:00
|
|
|
(defalias 'jupyter-handle-input-reply 'jupyter-handle-input-request)
|
|
|
|
|
2018-01-13 22:51:27 -06:00
|
|
|
;;; SHELL handlers
|
2017-12-13 11:27:13 -06:00
|
|
|
|
|
|
|
;; http://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-shell-router-dealer-channel
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defmethod jupyter-handle-message ((_channel jupyter-shell-channel)
|
2018-01-04 15:56:04 -06:00
|
|
|
client
|
|
|
|
req
|
|
|
|
msg)
|
2018-05-25 21:12:54 -05:00
|
|
|
(unless (jupyter-run-hook-with-args-until-success
|
|
|
|
client 'jupyter-shell-message-hook msg)
|
2018-05-27 14:52:39 -05:00
|
|
|
(jupyter-dispatch-message-cases client req msg
|
2018-06-01 23:13:50 -05:00
|
|
|
((execute-reply status execution_count user_expressions payload)
|
2018-05-27 14:52:39 -05:00
|
|
|
(shutdown-reply restart)
|
|
|
|
(inspect-reply found data metadata)
|
|
|
|
(complete-reply matches cursor_start cursor_end metadata)
|
|
|
|
(history-reply history)
|
|
|
|
(is-complete-reply status indent)
|
|
|
|
(comm-info-reply comms)
|
|
|
|
(kernel-info-reply protocol_version implementation
|
|
|
|
implementation_version language_info
|
|
|
|
banner help_links)))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-05-15 23:40:09 -05:00
|
|
|
(cl-defgeneric jupyter-send-execute-request ((client jupyter-kernel-client)
|
|
|
|
&key code
|
|
|
|
(silent nil)
|
|
|
|
(store-history t)
|
|
|
|
(user-expressions nil)
|
|
|
|
(allow-stdin
|
|
|
|
(jupyter-channel-alive-p
|
|
|
|
(oref client stdin-channel)))
|
|
|
|
(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))
|
2017-12-31 15:19:23 -06:00
|
|
|
(msg (jupyter-message-execute-request
|
2017-12-13 11:27:13 -06:00
|
|
|
:code code
|
|
|
|
:silent silent
|
|
|
|
:store-history store-history
|
|
|
|
:user-expressions user-expressions
|
|
|
|
:allow-stdin allow-stdin
|
|
|
|
:stop-on-error stop-on-error)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send client channel :execute-request msg)))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-execute-reply ((_client jupyter-kernel-client)
|
|
|
|
_req
|
2018-06-01 23:13:50 -05:00
|
|
|
_status
|
2018-01-22 19:12:47 -06:00
|
|
|
_execution-count
|
|
|
|
_user-expressions
|
|
|
|
_payload)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default execute reply handler."
|
2018-02-03 15:22:07 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
|
|
|
|
2018-05-15 23:40:09 -05:00
|
|
|
(cl-defgeneric jupyter-send-inspect-request ((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))
|
2017-12-31 15:19:23 -06:00
|
|
|
(msg (jupyter-message-inspect-request
|
2017-12-14 13:34:03 -06:00
|
|
|
:code code :pos pos :detail detail)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send client channel :inspect-request msg)))
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-inspect-reply ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_found
|
|
|
|
_data
|
|
|
|
_metadata)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default inspect reply handler."
|
2018-02-03 15:22:07 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-09-16 20:09:55 -05:00
|
|
|
;;; Completion contexts
|
|
|
|
|
|
|
|
(cl-defgeneric jupyter-code-context (type)
|
|
|
|
"Return a list, (CODE POS), for the context around `point'.
|
|
|
|
CODE is the required context for TYPE (either `inspect' or
|
|
|
|
`completion') and POS is the relative position of `point' within
|
|
|
|
CODE. Depending on the current context such as the current
|
|
|
|
`major-mode', CODE and POS will be used for `:complete-request's
|
|
|
|
originating from `jupyter-completion-at-point' and
|
2018-10-02 12:03:04 -05:00
|
|
|
`:inspect-request's from `jupyter-inspect-at-point'.")
|
2018-09-16 20:09:55 -05:00
|
|
|
|
|
|
|
(defun jupyter-line-context (&optional start)
|
|
|
|
"Return the code context of the current line.
|
|
|
|
START is the buffer position considered as the start of the line. See
|
|
|
|
`jupyter-code-context' for the form of the returned list."
|
|
|
|
(or start (setq start (line-beginning-position)))
|
2018-09-16 23:11:00 -05:00
|
|
|
(let ((code (buffer-substring-no-properties start (line-end-position)))
|
2018-10-08 18:44:37 -05:00
|
|
|
(pos (1+ (- (point) start))))
|
2018-09-16 20:09:55 -05:00
|
|
|
(list code (min pos (length code)))))
|
|
|
|
|
2018-10-08 18:48:52 -05:00
|
|
|
(defun jupyter-line-or-region-context (&optional start)
|
|
|
|
"Return the code context of the region or line.
|
|
|
|
If the region is active, return the active region context.
|
|
|
|
Otherwise return the line context. START has the same meaning as
|
|
|
|
in `jupyter-line-context' and is ignored if the region is active."
|
|
|
|
(if (region-active-p)
|
|
|
|
(list (buffer-substring-no-properties (region-beginning) (region-end))
|
|
|
|
(min (- (region-end) (region-beginning))
|
|
|
|
(1+ (- (point) (region-beginning)))))
|
|
|
|
(jupyter-line-context start)))
|
|
|
|
|
2018-09-16 20:09:55 -05:00
|
|
|
(cl-defmethod jupyter-code-context ((_type (eql inspect)))
|
2018-10-08 18:48:52 -05:00
|
|
|
(jupyter-line-or-region-context))
|
2018-09-16 20:09:55 -05:00
|
|
|
|
|
|
|
(cl-defmethod jupyter-code-context ((_type (eql completion)))
|
2018-10-08 18:48:52 -05:00
|
|
|
(jupyter-line-or-region-context))
|
2018-09-16 20:09:55 -05:00
|
|
|
|
2018-05-15 23:40:09 -05:00
|
|
|
(cl-defgeneric jupyter-send-complete-request ((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))
|
2017-12-31 15:19:23 -06:00
|
|
|
(msg (jupyter-message-complete-request
|
2017-12-14 13:34:03 -06:00
|
|
|
:code code :pos pos)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send client channel :complete-request msg)))
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-complete-reply ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_matches
|
|
|
|
_cursor-start
|
|
|
|
_cursor-end
|
|
|
|
_metadata)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default complete reply handler."
|
2018-02-03 15:22:07 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
|
|
|
|
2018-05-15 23:40:09 -05:00
|
|
|
(cl-defgeneric jupyter-send-history-request ((client jupyter-kernel-client)
|
|
|
|
&key
|
|
|
|
output
|
|
|
|
raw
|
|
|
|
(hist-access-type "tail")
|
|
|
|
session
|
|
|
|
start
|
|
|
|
stop
|
|
|
|
(n 10)
|
|
|
|
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))
|
2017-12-31 15:19:23 -06:00
|
|
|
(msg (jupyter-message-history-request
|
2017-12-14 13:34:03 -06:00
|
|
|
: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)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send client channel :history-request msg)))
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-history-reply ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_history)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default history reply handler."
|
2018-02-03 15:22:07 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-05-15 23:40:09 -05:00
|
|
|
(cl-defgeneric jupyter-send-is-complete-request ((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))
|
2017-12-31 15:19:23 -06:00
|
|
|
(msg (jupyter-message-is-complete-request
|
2017-12-14 13:34:03 -06:00
|
|
|
:code code)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send client channel :is-complete-request msg)))
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-is-complete-reply ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_status
|
|
|
|
_indent)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default is complete reply handler."
|
2018-02-03 15:22:07 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-05-15 23:40:09 -05:00
|
|
|
(cl-defgeneric jupyter-send-comm-info-request ((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-31 15:19:23 -06:00
|
|
|
(msg (jupyter-message-comm-info-request
|
2017-12-14 13:34:03 -06:00
|
|
|
:target-name target-name)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send client channel :comm-info-request msg)))
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-05-20 12:09:00 -05:00
|
|
|
(cl-defgeneric jupyter-send-comm-open ((client jupyter-kernel-client)
|
|
|
|
&key id
|
|
|
|
target-name
|
|
|
|
data)
|
|
|
|
(declare (indent 1))
|
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-message-comm-open
|
|
|
|
:id id
|
|
|
|
:target-name target-name
|
|
|
|
:data data)))
|
|
|
|
(jupyter-send client channel :comm-open msg)))
|
|
|
|
|
|
|
|
(cl-defgeneric jupyter-send-comm-msg ((client jupyter-kernel-client)
|
|
|
|
&key id
|
|
|
|
data)
|
|
|
|
(declare (indent 1))
|
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-message-comm-msg
|
|
|
|
:id id
|
|
|
|
:data data)))
|
|
|
|
(jupyter-send client channel :comm-msg msg)))
|
|
|
|
|
|
|
|
(cl-defgeneric jupyter-send-comm-close ((client jupyter-kernel-client)
|
|
|
|
&key id
|
|
|
|
data)
|
|
|
|
(declare (indent 1))
|
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-message-comm-close
|
|
|
|
:id id
|
|
|
|
:data data)))
|
|
|
|
(jupyter-send client channel :comm-close msg)))
|
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-comm-info-reply ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_comms)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default comm info. reply handler."
|
2018-02-03 15:22:07 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-09-16 22:53:18 -05:00
|
|
|
;;; Accessing kernel info properties
|
|
|
|
|
2018-09-16 21:17:10 -05:00
|
|
|
(cl-defmethod jupyter-kernel-info ((client jupyter-kernel-client))
|
|
|
|
"Return the kernel info plist of CLIENT.
|
|
|
|
Return CLIENT's kernel-info slot if non-nil. Otherwise send a
|
|
|
|
`:kernel-info-request' to CLIENT's kernel, set CLIENT's
|
|
|
|
kernel-info slot to the plist retrieved from the kernel, and
|
|
|
|
return it.
|
|
|
|
|
|
|
|
If the kernel CLIENT is connected to does not respond to a
|
|
|
|
`:kernel-info-request', raise an error."
|
|
|
|
(or (oref client kernel-info)
|
|
|
|
(let ((reporter (make-progress-reporter "Requesting kernel info..."))
|
|
|
|
(jupyter-inhibit-handlers t))
|
|
|
|
(prog1 (oset client kernel-info
|
|
|
|
(jupyter-message-content
|
|
|
|
(jupyter-wait-until-received :kernel-info-reply
|
|
|
|
(jupyter-send-kernel-info-request client)
|
|
|
|
;; TODO: Make this timeout configurable? The
|
|
|
|
;; python kernel starts up fast, but the Julia
|
|
|
|
;; kernel not so much.
|
|
|
|
5)))
|
|
|
|
(unless (oref client kernel-info)
|
|
|
|
(error "Kernel did not respond to kernel-info request"))
|
|
|
|
(progress-reporter-done reporter)))))
|
|
|
|
|
2018-09-16 22:53:18 -05:00
|
|
|
(cl-defmethod jupyter-kernel-language ((client jupyter-kernel-client))
|
|
|
|
"Return the language of the kernel CLIENT is connected to."
|
|
|
|
(plist-get (plist-get (jupyter-kernel-info client) :language_info) :name))
|
|
|
|
|
2018-05-15 23:40:09 -05:00
|
|
|
(cl-defgeneric jupyter-send-kernel-info-request ((client jupyter-kernel-client))
|
2017-12-15 18:14:28 -06:00
|
|
|
"Send a kernel-info request."
|
2018-01-04 15:56:04 -06:00
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-message-kernel-info-request)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send client channel :kernel-info-request msg)))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-kernel-info-reply ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_protocol-version
|
|
|
|
_implementation
|
|
|
|
_implementation-version
|
|
|
|
_language-info
|
|
|
|
_banner
|
|
|
|
_help-links)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default kernel-info reply handler."
|
2018-02-03 15:22:07 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-05-15 23:40:09 -05:00
|
|
|
(cl-defgeneric jupyter-send-shutdown-request ((client jupyter-kernel-client)
|
|
|
|
&key restart)
|
2018-01-08 22:30:00 -06:00
|
|
|
"Request a shutdown of CLIENT's kernel.
|
|
|
|
If RESTART is non-nil, request a restart instead of a complete shutdown."
|
2018-01-22 19:22:22 -06:00
|
|
|
(declare (indent 1))
|
2018-01-08 22:30:00 -06:00
|
|
|
(let ((channel (oref client shell-channel))
|
|
|
|
(msg (jupyter-message-shutdown-request :restart restart)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send client channel :shutdown-request msg)))
|
2018-01-08 22:30:00 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-shutdown-reply ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_restart)
|
2018-01-08 22:30:00 -06:00
|
|
|
"Default shutdown reply handler."
|
2018-02-03 15:22:07 -06:00
|
|
|
(declare (indent 1))
|
2018-01-08 22:30:00 -06:00
|
|
|
nil)
|
|
|
|
|
2018-01-13 22:51:27 -06:00
|
|
|
;;; IOPUB handlers
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defmethod jupyter-handle-message ((_channel jupyter-iopub-channel)
|
2018-01-04 15:56:04 -06:00
|
|
|
client
|
|
|
|
req
|
|
|
|
msg)
|
2018-05-25 21:12:54 -05:00
|
|
|
(unless (jupyter-run-hook-with-args-until-success
|
|
|
|
client 'jupyter-iopub-message-hook msg)
|
|
|
|
(jupyter-dispatch-message-cases client req msg
|
|
|
|
((shutdown-reply restart)
|
|
|
|
(stream name text)
|
|
|
|
(comm-open comm_id target_name target_module data)
|
|
|
|
(comm-msg comm_id data)
|
|
|
|
(comm-close comm_id data)
|
|
|
|
(execute-input code execution_count)
|
|
|
|
(execute-result execution_count data metadata)
|
|
|
|
(error ename evalue traceback)
|
|
|
|
(status execution_state)
|
|
|
|
(clear-output wait)
|
|
|
|
(display-data data metadata transient)
|
|
|
|
(update-display-data data metadata transient)))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
2018-05-26 20:01:29 -05:00
|
|
|
(cl-defgeneric jupyter-handle-comm-open ((client jupyter-kernel-client)
|
|
|
|
req
|
|
|
|
id
|
2018-05-20 12:09:00 -05:00
|
|
|
_target-name
|
|
|
|
_target-module
|
2018-05-26 20:01:29 -05:00
|
|
|
data)
|
2018-05-20 12:09:00 -05:00
|
|
|
(declare (indent 1))
|
2018-05-26 20:01:29 -05:00
|
|
|
(let ((comms (oref client comms)))
|
|
|
|
(puthash id (cons (jupyter-request-id req) data) comms)))
|
2018-05-20 12:09:00 -05:00
|
|
|
|
|
|
|
(cl-defgeneric jupyter-handle-comm-msg ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_id
|
|
|
|
_data)
|
|
|
|
(declare (indent 1))
|
|
|
|
nil)
|
|
|
|
|
2018-05-26 20:01:29 -05:00
|
|
|
(cl-defgeneric jupyter-handle-comm-close ((client jupyter-kernel-client)
|
2018-05-20 12:09:00 -05:00
|
|
|
_req
|
2018-05-26 20:01:29 -05:00
|
|
|
id
|
2018-05-20 12:09:00 -05:00
|
|
|
_data)
|
|
|
|
(declare (indent 1))
|
2018-05-26 20:01:29 -05:00
|
|
|
(let ((comms (oref client comms)))
|
|
|
|
(remhash id comms)))
|
2018-05-20 12:09:00 -05:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-stream ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_name
|
|
|
|
_text)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default stream handler."
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-execute-input ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_code
|
|
|
|
_execution-count)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default execute input handler."
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-execute-result ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_execution-count
|
|
|
|
_data
|
|
|
|
_metadata)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default execute result handler."
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-error ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_ename
|
|
|
|
_evalue
|
|
|
|
_traceback)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default error handler."
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-status ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_execution-state)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default status handler."
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-14 13:34:03 -06:00
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-clear-output ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_wait)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default clear output handler."
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-display-data ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_data
|
|
|
|
_metadata
|
|
|
|
_transient)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default display data handler."
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-display-data ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_data
|
|
|
|
_metadata
|
|
|
|
_transient)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default display data handler."
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
|
|
|
|
2018-01-22 19:12:47 -06:00
|
|
|
(cl-defgeneric jupyter-handle-update-display-data ((_client jupyter-kernel-client)
|
|
|
|
_req
|
|
|
|
_data
|
|
|
|
_metadata
|
|
|
|
_transient)
|
2018-01-06 22:50:55 -06:00
|
|
|
"Default update display handler"
|
2018-02-03 19:14:24 -06:00
|
|
|
(declare (indent 1))
|
2018-01-06 22:50:55 -06:00
|
|
|
nil)
|
2017-12-13 11:27:13 -06:00
|
|
|
|
|
|
|
(provide 'jupyter-client)
|
2018-01-08 21:38:32 -06:00
|
|
|
|
|
|
|
;;; jupyter-client.el ends here
|