2012-05-07 14:41:15 +02:00
|
|
|
|
;;; ein-kernel.el --- Communicate with IPython notebook server
|
|
|
|
|
|
|
|
|
|
;; Copyright (C) 2012- Takafumi Arakaki
|
|
|
|
|
|
2012-07-01 20:18:05 +02:00
|
|
|
|
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
;; This file is NOT part of GNU Emacs.
|
|
|
|
|
|
|
|
|
|
;; ein-kernel.el 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 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
;; ein-kernel.el 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 ein-kernel.el. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;;
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
(eval-when-compile (require 'cl))
|
2012-07-08 13:45:30 +02:00
|
|
|
|
(require 'ansi-color)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
(require 'ein-log)
|
|
|
|
|
(require 'ein-utils)
|
|
|
|
|
(require 'ein-websocket)
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(require 'ein-events)
|
2012-05-26 21:08:48 +02:00
|
|
|
|
(require 'ein-query)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defstruct ein:$kernel
|
2012-05-13 02:51:47 +02:00
|
|
|
|
"Hold kernel variables.
|
|
|
|
|
|
|
|
|
|
`ein:$kernel-url-or-port'
|
|
|
|
|
URL or port of IPython server.
|
|
|
|
|
|
|
|
|
|
FIXME: document other slots."
|
|
|
|
|
url-or-port
|
2012-05-22 13:34:38 +02:00
|
|
|
|
events
|
2012-05-07 14:41:15 +02:00
|
|
|
|
kernel-id
|
|
|
|
|
shell-channel
|
|
|
|
|
iopub-channel
|
|
|
|
|
base-url
|
|
|
|
|
kernel-url
|
|
|
|
|
ws-url
|
|
|
|
|
running
|
|
|
|
|
username
|
2012-05-22 13:34:38 +02:00
|
|
|
|
session-id
|
2012-05-31 15:43:49 +02:00
|
|
|
|
msg-callbacks
|
2012-05-31 19:33:00 +02:00
|
|
|
|
after-start-hook
|
2012-06-03 00:22:16 +02:00
|
|
|
|
after-execute-hook
|
2012-05-31 15:43:49 +02:00
|
|
|
|
kernelinfo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defstruct ein:$kernelinfo
|
|
|
|
|
"Info related (but unimportant) to kernel
|
|
|
|
|
|
|
|
|
|
`ein:$kernelinfo-buffer'
|
|
|
|
|
Notebook buffer that the kernel associated with.
|
|
|
|
|
|
|
|
|
|
`ein:$kernelinfo-hostname'
|
|
|
|
|
Host name of the machine where the kernel is running on.
|
|
|
|
|
|
|
|
|
|
`ein:$kernelinfo-ccwd'
|
|
|
|
|
cached CWD (last time checked CWD)."
|
|
|
|
|
buffer
|
|
|
|
|
hostname
|
|
|
|
|
ccwd)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
|
|
|
|
|
;;; Initialization and connection.
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel-new (url-or-port base-url events)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(make-ein:$kernel
|
2012-05-13 15:59:40 +02:00
|
|
|
|
:url-or-port url-or-port
|
2012-05-22 13:34:38 +02:00
|
|
|
|
:events events
|
2012-05-07 14:41:15 +02:00
|
|
|
|
:kernel-id nil
|
|
|
|
|
:shell-channel nil
|
|
|
|
|
:iopub-channel nil
|
2012-05-22 13:34:38 +02:00
|
|
|
|
:base-url base-url
|
2012-05-07 14:41:15 +02:00
|
|
|
|
:running nil
|
|
|
|
|
:username "username"
|
2012-05-22 13:34:38 +02:00
|
|
|
|
:session-id (ein:utils-uuid)
|
2012-05-31 15:43:49 +02:00
|
|
|
|
:msg-callbacks (make-hash-table :test 'equal)
|
|
|
|
|
:kernelinfo (make-ein:$kernelinfo)))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
|
2012-05-19 17:58:02 +02:00
|
|
|
|
(defun ein:kernel-del (kernel)
|
|
|
|
|
"Destructor for `ein:$kernel'."
|
|
|
|
|
(ein:kernel-stop-channels kernel))
|
|
|
|
|
|
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(defun ein:kernel--get-msg (kernel msg-type content)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(list
|
|
|
|
|
:header (list
|
|
|
|
|
:msg_id (ein:utils-uuid)
|
|
|
|
|
:username (ein:$kernel-username kernel)
|
|
|
|
|
:session (ein:$kernel-session-id kernel)
|
|
|
|
|
:msg_type msg-type)
|
2012-08-07 00:54:36 +02:00
|
|
|
|
:metadata (make-hash-table)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
:content content
|
|
|
|
|
:parent_header (make-hash-table)))
|
|
|
|
|
|
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(defun ein:kernel-start (kernel notebook-id)
|
|
|
|
|
"Start kernel of the notebook whose id is NOTEBOOK-ID."
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(unless (ein:$kernel-running kernel)
|
2012-06-20 21:01:58 +02:00
|
|
|
|
(ein:query-singleton-ajax
|
|
|
|
|
(list 'kernel-start (ein:$kernel-kernel-id kernel))
|
2012-05-26 21:08:48 +02:00
|
|
|
|
(concat (ein:url (ein:$kernel-url-or-port kernel)
|
|
|
|
|
(ein:$kernel-base-url kernel))
|
|
|
|
|
"?" (format "notebook=%s" notebook-id))
|
|
|
|
|
:type "POST"
|
|
|
|
|
:parser #'ein:json-read
|
2012-05-27 01:58:39 +02:00
|
|
|
|
:success (cons #'ein:kernel--kernel-started kernel))))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(defun ein:kernel-restart (kernel)
|
2012-05-22 23:30:18 +02:00
|
|
|
|
(ein:events-trigger (ein:$kernel-events kernel)
|
2012-06-06 21:03:54 +02:00
|
|
|
|
'status_restarting.Kernel)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(ein:log 'info "Restarting kernel")
|
|
|
|
|
(when (ein:$kernel-running kernel)
|
|
|
|
|
(ein:kernel-stop-channels kernel)
|
2012-06-20 21:01:58 +02:00
|
|
|
|
(ein:query-singleton-ajax
|
|
|
|
|
(list 'kernel-restart (ein:$kernel-kernel-id kernel))
|
2012-05-26 21:08:48 +02:00
|
|
|
|
(ein:url (ein:$kernel-url-or-port kernel)
|
|
|
|
|
(ein:$kernel-kernel-url kernel)
|
|
|
|
|
"restart")
|
|
|
|
|
:type "POST"
|
|
|
|
|
:parser #'ein:json-read
|
2012-05-27 01:58:39 +02:00
|
|
|
|
:success (cons #'ein:kernel--kernel-started kernel))))
|
2012-05-26 21:08:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defun* ein:kernel--kernel-started (kernel &key data &allow-other-keys)
|
|
|
|
|
(ein:log 'info "Kernel started: %s" (plist-get data :kernel_id))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(setf (ein:$kernel-running kernel) t)
|
2012-05-26 21:08:48 +02:00
|
|
|
|
(setf (ein:$kernel-kernel-id kernel) (plist-get data :kernel_id))
|
|
|
|
|
(setf (ein:$kernel-ws-url kernel) (plist-get data :ws_url))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(setf (ein:$kernel-kernel-url kernel)
|
|
|
|
|
(concat (ein:$kernel-base-url kernel) "/"
|
|
|
|
|
(ein:$kernel-kernel-id kernel)))
|
|
|
|
|
(ein:kernel-start-channels kernel)
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(let ((shell-channel (ein:$kernel-shell-channel kernel))
|
|
|
|
|
(iopub-channel (ein:$kernel-iopub-channel kernel)))
|
|
|
|
|
;; FIXME: get rid of lexical-let
|
|
|
|
|
(lexical-let ((kernel kernel))
|
|
|
|
|
(setf (ein:$websocket-onmessage shell-channel)
|
|
|
|
|
(lambda (packet)
|
|
|
|
|
(ein:kernel--handle-shell-reply kernel packet)))
|
|
|
|
|
(setf (ein:$websocket-onmessage iopub-channel)
|
|
|
|
|
(lambda (packet)
|
|
|
|
|
(ein:kernel--handle-iopub-reply kernel packet))))))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel--websocket-closed (kernel ws-url early)
|
|
|
|
|
(if early
|
2012-08-12 17:48:24 +02:00
|
|
|
|
(ein:display-warning
|
2012-08-12 18:54:03 +02:00
|
|
|
|
"Websocket connection to %s could not be established.
|
|
|
|
|
You will NOT be able to run code. Your websocket.el may not be
|
|
|
|
|
compatible with the websocket version in the server, or if the
|
|
|
|
|
url does not look right, there could be an error in the
|
|
|
|
|
server's configuration." ws-url)
|
|
|
|
|
(ein:display-warning "Websocket connection closed unexpectedly.
|
|
|
|
|
The kernel will no longer be responsive.")))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel-send-cookie (channel)
|
|
|
|
|
;; This is required to open channel. In IPython's kernel.js, it sends
|
|
|
|
|
;; `document.cookie'. This is an empty string anyway.
|
|
|
|
|
(ein:websocket-send channel ""))
|
|
|
|
|
|
|
|
|
|
|
2012-05-09 08:41:44 +02:00
|
|
|
|
(defun ein:kernel--ws-closed-callback (websocket kernel arg)
|
|
|
|
|
;; NOTE: The argument ARG should not be "unpacked" using `&rest'.
|
|
|
|
|
;; It must be given as a list to hold state `already-called-onclose'
|
|
|
|
|
;; so it can be modified in this function.
|
|
|
|
|
(destructuring-bind (&key already-called-onclose ws-url early)
|
|
|
|
|
arg
|
|
|
|
|
(unless already-called-onclose
|
|
|
|
|
(plist-put arg :already-called-onclose t)
|
2012-05-09 08:54:02 +02:00
|
|
|
|
(unless (ein:$websocket-closed-by-client websocket)
|
|
|
|
|
;; Use "event-was-clean" when it is implemented in websocket.el.
|
2012-05-09 08:41:44 +02:00
|
|
|
|
(ein:kernel--websocket-closed kernel ws-url early)))))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel-start-channels (kernel)
|
|
|
|
|
(ein:kernel-stop-channels kernel)
|
2012-05-09 08:41:44 +02:00
|
|
|
|
(let* ((ws-url (concat (ein:$kernel-ws-url kernel)
|
|
|
|
|
(ein:$kernel-kernel-url kernel)))
|
|
|
|
|
(onclose-arg (list :ws-url ws-url
|
|
|
|
|
:already-called-onclose nil
|
2012-05-09 09:06:30 +02:00
|
|
|
|
:early t)))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(ein:log 'info "Starting WS: %S" ws-url)
|
|
|
|
|
(setf (ein:$kernel-shell-channel kernel)
|
|
|
|
|
(ein:websocket (concat ws-url "/shell")))
|
|
|
|
|
(setf (ein:$kernel-iopub-channel kernel)
|
|
|
|
|
(ein:websocket (concat ws-url "/iopub")))
|
2012-05-09 08:41:44 +02:00
|
|
|
|
|
|
|
|
|
(loop for c in (list (ein:$kernel-shell-channel kernel)
|
|
|
|
|
(ein:$kernel-iopub-channel kernel))
|
|
|
|
|
do (setf (ein:$websocket-onclose-args c) (list kernel onclose-arg))
|
|
|
|
|
do (setf (ein:$websocket-onopen c)
|
2012-05-31 19:33:00 +02:00
|
|
|
|
(lexical-let ((channel c)
|
|
|
|
|
(kernel kernel))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(ein:kernel-send-cookie channel)
|
|
|
|
|
;; run `ein:$kernel-after-start-hook' if both
|
|
|
|
|
;; channels are ready.
|
2012-07-19 00:16:00 +02:00
|
|
|
|
(when (ein:kernel-live-p kernel)
|
2012-06-06 23:10:58 +02:00
|
|
|
|
(ein:kernel-run-after-start-hook kernel)))))
|
2012-05-09 08:41:44 +02:00
|
|
|
|
do (setf (ein:$websocket-onclose c)
|
|
|
|
|
#'ein:kernel--ws-closed-callback))
|
|
|
|
|
|
|
|
|
|
;; switch from early-close to late-close message after 1s
|
|
|
|
|
(run-at-time
|
|
|
|
|
1 nil
|
|
|
|
|
(lambda (onclose-arg)
|
2012-05-09 09:06:30 +02:00
|
|
|
|
(plist-put onclose-arg :early nil)
|
|
|
|
|
(ein:log 'debug "(via run-at-time) onclose-arg changed to: %S"
|
|
|
|
|
onclose-arg))
|
|
|
|
|
onclose-arg)))
|
|
|
|
|
|
|
|
|
|
;; NOTE: `onclose-arg' can be accessed as:
|
|
|
|
|
;; (nth 1 (ein:$websocket-onclose-args (ein:$kernel-shell-channel (ein:$notebook-kernel ein:notebook))))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
|
2012-05-31 19:33:00 +02:00
|
|
|
|
(defun ein:kernel-run-after-start-hook (kernel)
|
|
|
|
|
(ein:log 'debug "EIN:KERNEL-RUN-AFTER-START-HOOK")
|
|
|
|
|
(mapc #'ein:funcall-packed
|
|
|
|
|
(ein:$kernel-after-start-hook kernel)))
|
|
|
|
|
|
|
|
|
|
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(defun ein:kernel-stop-channels (kernel)
|
|
|
|
|
(when (ein:$kernel-shell-channel kernel)
|
|
|
|
|
(setf (ein:$websocket-onclose (ein:$kernel-shell-channel kernel)) nil)
|
|
|
|
|
(ein:websocket-close (ein:$kernel-shell-channel kernel))
|
|
|
|
|
(setf (ein:$kernel-shell-channel kernel) nil))
|
|
|
|
|
(when (ein:$kernel-iopub-channel kernel)
|
|
|
|
|
(setf (ein:$websocket-onclose (ein:$kernel-iopub-channel kernel)) nil)
|
|
|
|
|
(ein:websocket-close (ein:$kernel-iopub-channel kernel))
|
|
|
|
|
(setf (ein:$kernel-iopub-channel kernel) nil)))
|
|
|
|
|
|
|
|
|
|
|
2012-07-19 00:16:00 +02:00
|
|
|
|
(defun ein:kernel-live-p (kernel)
|
2012-06-06 23:08:25 +02:00
|
|
|
|
(and
|
|
|
|
|
(ein:aand (ein:$kernel-shell-channel kernel) (ein:websocket-open-p it))
|
|
|
|
|
(ein:aand (ein:$kernel-iopub-channel kernel) (ein:websocket-open-p it))))
|
2012-05-13 16:54:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defmacro ein:kernel-if-ready (kernel &rest body)
|
2012-05-13 16:51:20 +02:00
|
|
|
|
"Execute BODY if KERNEL is ready. Warn user otherwise."
|
2012-05-13 16:54:22 +02:00
|
|
|
|
(declare (indent 1))
|
2012-07-19 00:16:00 +02:00
|
|
|
|
`(if (ein:kernel-live-p ,kernel)
|
2012-05-13 16:54:22 +02:00
|
|
|
|
(progn ,@body)
|
2012-06-06 23:08:59 +02:00
|
|
|
|
(ein:log 'warn "Kernel is not ready yet! (or closed already.)")))
|
2012-05-13 16:54:22 +02:00
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
|
|
|
|
|
;;; Main public methods
|
|
|
|
|
|
|
|
|
|
;; NOTE: The argument CALLBACKS for the following functions is almost
|
|
|
|
|
;; same as the JS implementation in IPython. However, as Emacs
|
|
|
|
|
;; lisp does not support closure, value is "packed" using
|
|
|
|
|
;; `cons': `car' is the actual callback function and `cdr' is
|
|
|
|
|
;; its first argument. It's like using `cons' instead of
|
|
|
|
|
;; `$.proxy'.
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel-object-info-request (kernel objname callbacks)
|
|
|
|
|
"Send object info request of OBJNAME to KERNEL.
|
|
|
|
|
|
|
|
|
|
When calling this method pass a CALLBACKS structure of the form:
|
|
|
|
|
|
|
|
|
|
(:object_info_reply (FUNCTION . ARGUMENT))
|
|
|
|
|
|
2012-08-08 23:27:07 +02:00
|
|
|
|
Call signature::
|
|
|
|
|
|
|
|
|
|
(`funcall' FUNCTION ARGUMENT CONTENT METADATA)
|
|
|
|
|
|
|
|
|
|
CONTENT and METADATA are given by `object_into_reply' message.
|
2012-05-13 16:54:22 +02:00
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
`object_into_reply' message is documented here:
|
|
|
|
|
http://ipython.org/ipython-doc/dev/development/messaging.html#object-information
|
|
|
|
|
"
|
2012-07-19 00:16:00 +02:00
|
|
|
|
(assert (ein:kernel-live-p kernel) nil "Kernel is not active.")
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(when objname
|
2012-05-16 04:53:38 +02:00
|
|
|
|
(let* ((content (list :oname (format "%s" objname)))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(msg (ein:kernel--get-msg kernel "object_info_request" content))
|
|
|
|
|
(msg-id (plist-get (plist-get msg :header) :msg_id)))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(ein:websocket-send
|
|
|
|
|
(ein:$kernel-shell-channel kernel)
|
|
|
|
|
(json-encode msg))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
|
|
|
|
|
msg-id)))
|
|
|
|
|
|
|
|
|
|
|
2012-05-22 22:28:32 +02:00
|
|
|
|
(defun* ein:kernel-execute (kernel code &optional callbacks
|
2012-05-22 13:34:38 +02:00
|
|
|
|
&key
|
|
|
|
|
(silent t)
|
|
|
|
|
(user-variables [])
|
|
|
|
|
(user-expressions (make-hash-table))
|
|
|
|
|
(allow-stdin json-false))
|
|
|
|
|
"Execute CODE on KERNEL.
|
|
|
|
|
|
|
|
|
|
When calling this method pass a CALLBACKS structure of the form:
|
|
|
|
|
|
2012-06-04 16:23:53 +02:00
|
|
|
|
(:execute_reply EXECUTE-REPLY-CALLBACK
|
|
|
|
|
:output OUTPUT-CALLBACK
|
|
|
|
|
:clear_output CLEAR-OUTPUT-CALLBACK
|
2012-06-04 16:18:17 +02:00
|
|
|
|
:set_next_input SET-NEXT-INPUT)
|
2012-05-22 13:34:38 +02:00
|
|
|
|
|
|
|
|
|
Objects end with -CALLBACK above must pack a FUNCTION and its
|
2012-08-08 20:00:32 +02:00
|
|
|
|
first ARGUMENT in a `cons'::
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(FUNCTION . ARGUMENT)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-08-08 20:00:32 +02:00
|
|
|
|
Call signature
|
|
|
|
|
--------------
|
|
|
|
|
::
|
|
|
|
|
|
2012-08-08 21:18:00 +02:00
|
|
|
|
(`funcall' EXECUTE-REPLY-CALLBACK ARGUMENT CONTENT METADATA)
|
|
|
|
|
(`funcall' OUTPUT-CALLBACK ARGUMENT MSG-TYPE CONTENT METADATA)
|
|
|
|
|
(`funcall' CLEAR-OUTPUT-CALLBACK ARGUMENT CONTENT METADATA)
|
2012-08-08 20:00:32 +02:00
|
|
|
|
(`funcall' SET-NEXT-INPUT ARGUMENT TEXT)
|
|
|
|
|
|
2012-08-08 21:18:00 +02:00
|
|
|
|
* Both CONTENT and METADATA objects are plist.
|
2012-08-08 20:00:32 +02:00
|
|
|
|
* The MSG-TYPE argument for OUTPUT-CALLBACK is a string
|
|
|
|
|
(one of `stream', `display_data', `pyout' and `pyerr').
|
|
|
|
|
* The CONTENT object for CLEAR-OUTPUT-CALLBACK has
|
|
|
|
|
`stdout', `stderr' and `other' fields that are booleans.
|
|
|
|
|
* The SET-NEXT-INPUT callback will be passed the `set_next_input' payload,
|
|
|
|
|
which is a string.
|
|
|
|
|
See `ein:kernel--handle-shell-reply' for how the callbacks are called.
|
|
|
|
|
|
|
|
|
|
Links
|
|
|
|
|
-----
|
2012-08-08 22:15:22 +02:00
|
|
|
|
* For general description of CONTENT and METADATA:
|
|
|
|
|
http://ipython.org/ipython-doc/dev/development/messaging.html#general-message-format
|
2012-08-08 20:00:32 +02:00
|
|
|
|
* `execute_reply' message is documented here:
|
|
|
|
|
http://ipython.org/ipython-doc/dev/development/messaging.html#execute
|
|
|
|
|
* Output type messages is documented here:
|
|
|
|
|
http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
|
|
|
|
|
|
|
|
|
|
Sample implementations
|
|
|
|
|
----------------------
|
|
|
|
|
* `ein:cell--handle-execute-reply'
|
|
|
|
|
* `ein:cell--handle-output'
|
|
|
|
|
* `ein:cell--handle-clear-output'
|
|
|
|
|
* `ein:cell--handle-set-next-input'
|
|
|
|
|
"
|
2012-08-08 22:15:22 +02:00
|
|
|
|
;; FIXME: Consider changing callback to use `&key'.
|
|
|
|
|
;; Otherwise, adding new arguments to callback requires
|
|
|
|
|
;; backward incompatible changes (hence a big diff), unlike
|
|
|
|
|
;; Javascript. Downside of this is that there is no short way
|
|
|
|
|
;; to write anonymous callback because there is no `lambda*'.
|
|
|
|
|
;; You can use `function*', but that's bit long...
|
|
|
|
|
|
|
|
|
|
;; FIXME: Consider allowing a list of fixed argument so that the
|
|
|
|
|
;; call signature becomes something like:
|
|
|
|
|
;; (funcall FUNCTION [ARG ...] CONTENT METADATA)
|
|
|
|
|
|
2012-07-19 00:16:00 +02:00
|
|
|
|
(assert (ein:kernel-live-p kernel) nil "Kernel is not active.")
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(let* ((content (list
|
|
|
|
|
:code code
|
2012-05-22 13:34:38 +02:00
|
|
|
|
:silent (or silent json-false)
|
|
|
|
|
:user_variables user-variables
|
|
|
|
|
:user_expressions user-expressions
|
|
|
|
|
:allow_stdin allow-stdin))
|
|
|
|
|
(msg (ein:kernel--get-msg kernel "execute_request" content))
|
|
|
|
|
(msg-id (plist-get (plist-get msg :header) :msg_id)))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(ein:websocket-send
|
|
|
|
|
(ein:$kernel-shell-channel kernel)
|
|
|
|
|
(json-encode msg))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
|
2012-06-03 00:22:16 +02:00
|
|
|
|
(unless silent
|
|
|
|
|
(mapc #'ein:funcall-packed
|
|
|
|
|
(ein:$kernel-after-execute-hook kernel)))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
msg-id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel-complete (kernel line cursor-pos callbacks)
|
|
|
|
|
"Complete code at CURSOR-POS in a string LINE on KERNEL.
|
|
|
|
|
|
|
|
|
|
CURSOR-POS is the position in the string LINE, not in the buffer.
|
|
|
|
|
|
|
|
|
|
When calling this method pass a CALLBACKS structure of the form:
|
|
|
|
|
|
|
|
|
|
(:complete_reply (FUNCTION . ARGUMENT))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-08-08 23:27:07 +02:00
|
|
|
|
Call signature::
|
|
|
|
|
|
|
|
|
|
(`funcall' FUNCTION ARGUMENT CONTENT METADATA)
|
|
|
|
|
|
|
|
|
|
CONTENT and METADATA are given by `complete_reply' message.
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
`complete_reply' message is documented here:
|
|
|
|
|
http://ipython.org/ipython-doc/dev/development/messaging.html#complete
|
|
|
|
|
"
|
2012-07-19 00:16:00 +02:00
|
|
|
|
(assert (ein:kernel-live-p kernel) nil "Kernel is not active.")
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(let* ((content (list
|
|
|
|
|
:text ""
|
|
|
|
|
:line line
|
|
|
|
|
:cursor_pos cursor-pos))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(msg (ein:kernel--get-msg kernel "complete_request" content))
|
|
|
|
|
(msg-id (plist-get (plist-get msg :header) :msg_id)))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(ein:websocket-send
|
|
|
|
|
(ein:$kernel-shell-channel kernel)
|
|
|
|
|
(json-encode msg))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
|
|
|
|
|
msg-id))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel-interrupt (kernel)
|
|
|
|
|
(when (ein:$kernel-running kernel)
|
|
|
|
|
(ein:log 'info "Interrupting kernel")
|
2012-06-20 21:01:58 +02:00
|
|
|
|
(ein:query-singleton-ajax
|
|
|
|
|
(list 'kernel-interrupt (ein:$kernel-kernel-id kernel))
|
2012-05-26 21:08:48 +02:00
|
|
|
|
(ein:url (ein:$kernel-url-or-port kernel)
|
|
|
|
|
(ein:$kernel-kernel-url kernel)
|
|
|
|
|
"interrupt")
|
|
|
|
|
:type "POST"
|
|
|
|
|
:success (cons (lambda (&rest ignore)
|
|
|
|
|
(ein:log 'info "Sent interruption command."))
|
2012-05-27 01:58:39 +02:00
|
|
|
|
nil))))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
|
2012-06-14 12:24:40 +02:00
|
|
|
|
(defun ein:kernel-kill (kernel &optional callback cbargs)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(when (ein:$kernel-running kernel)
|
2012-06-20 21:01:58 +02:00
|
|
|
|
(ein:query-singleton-ajax
|
|
|
|
|
(list 'kernel-kill (ein:$kernel-kernel-id kernel))
|
2012-05-26 21:08:48 +02:00
|
|
|
|
(ein:url (ein:$kernel-url-or-port kernel)
|
|
|
|
|
(ein:$kernel-kernel-url kernel))
|
|
|
|
|
:cache nil
|
|
|
|
|
:type "DELETE"
|
2012-06-14 12:24:40 +02:00
|
|
|
|
:success (cons (lambda (packed &rest ignore)
|
|
|
|
|
(ein:log 'info "Notebook kernel is killed")
|
2012-06-16 12:19:03 +02:00
|
|
|
|
(destructuring-bind (kernel callback cbargs)
|
2012-06-14 12:24:40 +02:00
|
|
|
|
packed
|
2012-06-16 12:19:03 +02:00
|
|
|
|
(setf (ein:$kernel-running kernel) nil)
|
2012-06-14 12:24:40 +02:00
|
|
|
|
(when callback (apply callback cbargs))))
|
2012-06-16 12:19:03 +02:00
|
|
|
|
(list kernel callback cbargs)))))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
|
|
|
|
|
;; Reply handlers.
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel-get-callbacks-for-msg (kernel msg-id)
|
2012-05-23 01:13:10 +02:00
|
|
|
|
(gethash msg-id (ein:$kernel-msg-callbacks kernel)))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
|
|
|
|
|
(defun ein:kernel-set-callbacks-for-msg (kernel msg-id callbacks)
|
|
|
|
|
(puthash msg-id callbacks (ein:$kernel-msg-callbacks kernel)))
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel--handle-shell-reply (kernel packet)
|
2012-07-22 13:04:09 +02:00
|
|
|
|
(ein:log 'debug "KERNEL--HANDLE-SHELL-REPLY")
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(destructuring-bind
|
2012-08-08 21:18:00 +02:00
|
|
|
|
(&key header content metadata parent_header &allow-other-keys)
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(ein:json-read-from-string packet)
|
2012-05-23 01:31:56 +02:00
|
|
|
|
(let* ((msg-type (plist-get header :msg_type))
|
2012-05-22 22:28:32 +02:00
|
|
|
|
(msg-id (plist-get parent_header :msg_id))
|
|
|
|
|
(callbacks (ein:kernel-get-callbacks-for-msg kernel msg-id))
|
2012-05-23 01:31:56 +02:00
|
|
|
|
(cb (plist-get callbacks (intern (format ":%s" msg-type)))))
|
2012-07-22 13:04:09 +02:00
|
|
|
|
(ein:log 'debug "KERNEL--HANDLE-SHELL-REPLY: msg_type = %s" msg-type)
|
2012-05-22 22:03:06 +02:00
|
|
|
|
(if cb
|
2012-08-08 21:18:00 +02:00
|
|
|
|
(ein:funcall-packed cb content metadata)
|
2012-05-22 22:28:32 +02:00
|
|
|
|
(ein:log 'debug "no callback for: msg_type=%s msg_id=%s"
|
|
|
|
|
msg-type msg-id))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(ein:aif (plist-get content :payload)
|
2012-07-22 13:04:09 +02:00
|
|
|
|
(ein:kernel--handle-payload kernel callbacks it))))
|
|
|
|
|
(ein:log 'debug "KERNEL--HANDLE-SHELL-REPLY: finished"))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
|
2012-06-04 16:18:17 +02:00
|
|
|
|
(defun ein:kernel--handle-payload (kernel callbacks payload)
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(loop with events = (ein:$kernel-events kernel)
|
|
|
|
|
for p in payload
|
|
|
|
|
for text = (plist-get p :text)
|
|
|
|
|
for source = (plist-get p :source)
|
|
|
|
|
if (equal source "IPython.zmq.page.page")
|
2012-05-23 23:32:51 +02:00
|
|
|
|
do (when (not (equal (ein:trim text) ""))
|
|
|
|
|
(ein:events-trigger
|
2012-06-06 21:03:54 +02:00
|
|
|
|
events 'open_with_text.Pager (list :text text)))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
else if
|
|
|
|
|
(equal source "IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input")
|
2012-06-04 21:12:06 +02:00
|
|
|
|
do (let ((cb (plist-get callbacks :set_next_input)))
|
|
|
|
|
(when cb (ein:funcall-packed cb text)))))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
|
|
|
|
|
(defun ein:kernel--handle-iopub-reply (kernel packet)
|
2012-07-22 13:04:09 +02:00
|
|
|
|
(ein:log 'debug "KERNEL--HANDLE-IOPUB-REPLY")
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(destructuring-bind
|
2012-08-08 21:18:00 +02:00
|
|
|
|
(&key content metadata parent_header header &allow-other-keys)
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(ein:json-read-from-string packet)
|
2012-05-23 01:31:56 +02:00
|
|
|
|
(let* ((msg-type (plist-get header :msg_type))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(callbacks (ein:kernel-get-callbacks-for-msg
|
|
|
|
|
kernel (plist-get parent_header :msg_id)))
|
|
|
|
|
(events (ein:$kernel-events kernel)))
|
2012-07-22 13:04:09 +02:00
|
|
|
|
(ein:log 'debug "KERNEL--HANDLE-IOPUB-REPLY: msg_type = %s" msg-type)
|
2012-05-23 01:31:56 +02:00
|
|
|
|
(if (and (not (equal msg-type "status")) (null callbacks))
|
2012-06-06 20:41:30 +02:00
|
|
|
|
(ein:log 'verbose "Got message not from this notebook.")
|
2012-05-23 01:31:56 +02:00
|
|
|
|
(ein:case-equal msg-type
|
|
|
|
|
(("stream" "display_data" "pyout" "pyerr")
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(ein:aif (plist-get callbacks :output)
|
2012-08-08 21:18:00 +02:00
|
|
|
|
(ein:funcall-packed it msg-type content metadata)))
|
2012-05-23 01:31:56 +02:00
|
|
|
|
(("status")
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(ein:case-equal (plist-get content :execution_state)
|
|
|
|
|
(("busy")
|
2012-06-06 21:03:54 +02:00
|
|
|
|
(ein:events-trigger events 'status_busy.Kernel))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(("idle")
|
2012-06-06 21:03:54 +02:00
|
|
|
|
(ein:events-trigger events 'status_idle.Kernel))
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(("dead")
|
|
|
|
|
(ein:kernel-stop-channels kernel)
|
2012-06-06 21:03:54 +02:00
|
|
|
|
(ein:events-trigger events 'status_dead.Kernel))))
|
2012-05-23 01:31:56 +02:00
|
|
|
|
(("clear_output")
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(ein:aif (plist-get callbacks :clear_output)
|
2012-08-08 21:18:00 +02:00
|
|
|
|
(ein:funcall-packed it content metadata)))))))
|
2012-07-22 13:04:09 +02:00
|
|
|
|
(ein:log 'debug "KERNEL--HANDLE-IOPUB-REPLY: finished"))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-27 05:19:17 +02:00
|
|
|
|
|
|
|
|
|
;;; Utility functions
|
|
|
|
|
|
2012-08-04 00:26:30 +02:00
|
|
|
|
(defun ein:kernel-filename-to-python (kernel filename)
|
|
|
|
|
"See: `ein:filename-to-python'."
|
|
|
|
|
(ein:filename-to-python (ein:$kernel-url-or-port kernel) filename))
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel-filename-from-python (kernel filename)
|
|
|
|
|
"See: `ein:filename-from-python'."
|
|
|
|
|
(ein:filename-from-python (ein:$kernel-url-or-port kernel) filename))
|
|
|
|
|
|
2012-07-08 13:45:30 +02:00
|
|
|
|
(defun ein:kernel-construct-defstring (content)
|
|
|
|
|
"Construct call signature from CONTENT of ``:object_info_reply``.
|
|
|
|
|
Used in `ein:cell-finish-tooltip', etc."
|
|
|
|
|
(or (plist-get content :call_def)
|
|
|
|
|
(plist-get content :init_definition)
|
|
|
|
|
(plist-get content :definition)))
|
|
|
|
|
|
|
|
|
|
(defun ein:kernel-construct-help-string (content)
|
|
|
|
|
"Construct help string from CONTENT of ``:object_info_reply``.
|
|
|
|
|
Used in `ein:cell-finish-tooltip', etc."
|
2012-07-25 18:59:41 +02:00
|
|
|
|
(ein:log 'debug "KERNEL-CONSTRUCT-HELP-STRING")
|
2012-07-25 18:51:02 +02:00
|
|
|
|
(let* ((defstring (ein:aand
|
|
|
|
|
(ein:kernel-construct-defstring content)
|
|
|
|
|
(ansi-color-apply it)
|
|
|
|
|
(ein:string-fill-paragraph it)))
|
2012-07-25 18:55:55 +02:00
|
|
|
|
(docstring (ein:aand
|
|
|
|
|
(or (plist-get content :call_docstring)
|
|
|
|
|
(plist-get content :init_docstring)
|
|
|
|
|
(plist-get content :docstring)
|
|
|
|
|
;; "<empty docstring>"
|
|
|
|
|
)
|
|
|
|
|
(ansi-color-apply it)))
|
2012-07-25 21:54:56 +02:00
|
|
|
|
(help (ein:aand
|
|
|
|
|
(ein:filter 'identity (list defstring docstring))
|
|
|
|
|
(ein:join-str "\n" it))))
|
2012-07-25 18:59:41 +02:00
|
|
|
|
(ein:log 'debug "KERNEL-CONSTRUCT-HELP-STRING: help=%s" help)
|
2012-07-08 13:45:30 +02:00
|
|
|
|
help))
|
|
|
|
|
|
2012-05-27 05:49:41 +02:00
|
|
|
|
(defun ein:kernel-request-stream (kernel code func &optional args)
|
2012-08-04 00:04:16 +02:00
|
|
|
|
"Run lisp callback FUNC with the output stream returned by Python CODE.
|
|
|
|
|
|
|
|
|
|
The first argument to the lisp function FUNC is the stream output
|
|
|
|
|
as a string and the rest of the argument is the optional ARGS."
|
2012-05-27 05:49:41 +02:00
|
|
|
|
(ein:kernel-execute
|
|
|
|
|
kernel
|
|
|
|
|
code
|
2012-08-08 21:30:14 +02:00
|
|
|
|
(list :output (cons (lambda (packed msg-type content -metadata-not-used-)
|
2012-05-27 05:49:41 +02:00
|
|
|
|
(let ((func (car packed))
|
|
|
|
|
(args (cdr packed)))
|
|
|
|
|
(when (equal msg-type "stream")
|
|
|
|
|
(ein:aif (plist-get content :data)
|
|
|
|
|
(apply func it args)))))
|
|
|
|
|
(cons func args)))))
|
|
|
|
|
|
2012-05-27 05:19:17 +02:00
|
|
|
|
(defun ein:kernel-sync-directory (kernel buffer)
|
|
|
|
|
"Sync `default-directory' of BUFFER with cwd of KERNEL.
|
|
|
|
|
When no such directory exists, `default-directory' will not be changed."
|
|
|
|
|
(ein:log 'info "Syncing directory of %s with kernel..." buffer)
|
2012-05-27 05:49:41 +02:00
|
|
|
|
(ein:kernel-request-stream
|
2012-05-27 05:19:17 +02:00
|
|
|
|
kernel
|
|
|
|
|
"__import__('sys').stdout.write(__import__('os').getcwd())"
|
2012-08-03 23:24:27 +02:00
|
|
|
|
(lambda (path kernel buffer)
|
2012-05-27 05:49:41 +02:00
|
|
|
|
(with-current-buffer buffer
|
2012-08-04 00:26:30 +02:00
|
|
|
|
(setq path (ein:kernel-filename-from-python kernel path))
|
2012-05-27 05:49:41 +02:00
|
|
|
|
(if (file-accessible-directory-p path)
|
|
|
|
|
(progn
|
|
|
|
|
(setq default-directory path)
|
|
|
|
|
(ein:log 'info
|
|
|
|
|
"Syncing directory of %s with kernel...DONE (%s)"
|
|
|
|
|
buffer path))
|
|
|
|
|
(ein:log 'info
|
|
|
|
|
"Syncing directory of %s with kernel...FAILED (no dir: %s)"
|
|
|
|
|
buffer path))))
|
2012-08-03 23:24:27 +02:00
|
|
|
|
(list kernel buffer)))
|
2012-05-27 05:19:17 +02:00
|
|
|
|
|
2012-05-31 18:12:44 +02:00
|
|
|
|
(defun ein:kernelinfo-init (kernelinfo buffer)
|
|
|
|
|
(setf (ein:$kernelinfo-buffer kernelinfo) buffer))
|
|
|
|
|
|
2012-06-03 00:22:16 +02:00
|
|
|
|
(defun ein:kernelinfo-setup-hooks (kernel)
|
|
|
|
|
"Add `ein:kernelinfo-update-*' to `ein:$kernel-after-*-hook'."
|
2012-05-31 19:33:00 +02:00
|
|
|
|
(push (cons #'ein:kernelinfo-update-all kernel)
|
2012-06-03 00:22:16 +02:00
|
|
|
|
(ein:$kernel-after-start-hook kernel))
|
|
|
|
|
(push (cons #'ein:kernelinfo-update-ccwd kernel)
|
|
|
|
|
(ein:$kernel-after-execute-hook kernel)))
|
2012-05-31 19:33:00 +02:00
|
|
|
|
|
|
|
|
|
(defun ein:kernelinfo-update-all (kernel)
|
|
|
|
|
(ein:log 'debug "EIN:KERNELINFO-UPDATE-ALL")
|
2012-07-19 00:16:00 +02:00
|
|
|
|
(ein:log 'debug "(ein:kernel-live-p kernel) = %S"
|
|
|
|
|
(ein:kernel-live-p kernel))
|
2012-05-31 19:33:00 +02:00
|
|
|
|
(ein:kernelinfo-update-ccwd kernel)
|
|
|
|
|
(ein:kernelinfo-update-hostname kernel))
|
|
|
|
|
|
2012-05-31 15:43:49 +02:00
|
|
|
|
(defun ein:kernelinfo-update-ccwd (kernel)
|
|
|
|
|
(ein:kernel-request-stream
|
|
|
|
|
kernel
|
|
|
|
|
"__import__('sys').stdout.write(__import__('os').getcwd())"
|
2012-08-03 23:24:27 +02:00
|
|
|
|
(lambda (cwd kernel kernelinfo buffer)
|
2012-08-04 00:26:30 +02:00
|
|
|
|
(setq cwd (ein:kernel-filename-from-python kernel cwd))
|
2012-05-31 15:43:49 +02:00
|
|
|
|
(setf (ein:$kernelinfo-ccwd kernelinfo) cwd)
|
|
|
|
|
;; sync buffer's `default-directory' with CWD
|
|
|
|
|
(when (buffer-live-p buffer)
|
|
|
|
|
(with-current-buffer buffer
|
|
|
|
|
(when (file-accessible-directory-p cwd)
|
2012-06-01 21:27:34 +02:00
|
|
|
|
(setq default-directory (file-name-as-directory cwd))))))
|
2012-05-31 15:43:49 +02:00
|
|
|
|
(let ((kernelinfo (ein:$kernel-kernelinfo kernel)))
|
2012-08-03 23:24:27 +02:00
|
|
|
|
(list kernel kernelinfo (ein:$kernelinfo-buffer kernelinfo)))))
|
2012-05-31 15:43:49 +02:00
|
|
|
|
|
|
|
|
|
(defun ein:kernelinfo-update-hostname (kernel)
|
|
|
|
|
(ein:kernel-request-stream
|
|
|
|
|
kernel
|
|
|
|
|
"__import__('sys').stdout.write(__import__('os').uname()[1])"
|
|
|
|
|
(lambda (hostname kernelinfo)
|
|
|
|
|
(setf (ein:$kernelinfo-hostname kernelinfo) hostname))
|
|
|
|
|
(list (ein:$kernel-kernelinfo kernel))))
|
|
|
|
|
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(provide 'ein-kernel)
|
|
|
|
|
|
|
|
|
|
;;; ein-kernel.el ends here
|