change python config, add jupyter and ein
This commit is contained in:
502
lisp/jupyter/jupyter-ioloop.el
Normal file
502
lisp/jupyter/jupyter-ioloop.el
Normal file
@@ -0,0 +1,502 @@
|
||||
;;; jupyter-ioloop.el --- Jupyter channel subprocess -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2018-2024 Nathaniel Nicandro
|
||||
|
||||
;; Author: Nathaniel Nicandro <nathanielnicandro@gmail.com>
|
||||
;; Created: 03 Nov 2018
|
||||
|
||||
;; 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 3, 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:
|
||||
|
||||
;; An ioloop encapsulates a subprocess that communicates with its parent
|
||||
;; process in a pre-defined way. The parent process sends events (lists with a
|
||||
;; head element tagging the type of event and the rest of the elements being
|
||||
;; the arguments), via a call to the `jupyter-send' method of a
|
||||
;; `jupyter-ioloop'. The ioloop subprocess then handles the event in its
|
||||
;; environment. You add an event that can be handled in the ioloop environment
|
||||
;; by calling `jupyter-ioloop-add-event' before calling `jupyter-ioloop-start'.
|
||||
;;
|
||||
;; When one of the events added through `jupyter-ioloop-add-event'
|
||||
;; returns something other than nil, it is sent back to the parent
|
||||
;; process and the handler function passed to `jupyter-ioloop-start'
|
||||
;; is called.
|
||||
;;
|
||||
;; An example that will echo back what was sent to the ioloop as a message in
|
||||
;; the parent process:
|
||||
;;
|
||||
;; (let ((ioloop (jupyter-ioloop))
|
||||
;; (jupyter-ioloop-add-event ioloop echo (data)
|
||||
;; "Return DATA back to the parent process."
|
||||
;; (list 'echo data))
|
||||
;; (jupyter-ioloop-start ioloop (lambda (event) (message "%s" (cadr event))))
|
||||
;; (jupyter-send ioloop 'echo "Message")
|
||||
;; (jupyter-ioloop-stop ioloop))
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'jupyter-base)
|
||||
(require 'zmq)
|
||||
(eval-when-compile (require 'subr-x))
|
||||
|
||||
(defvar jupyter-ioloop-poller nil
|
||||
"The polling object being used to poll for events in an ioloop.")
|
||||
|
||||
(defvar jupyter-ioloop-stdin nil
|
||||
"A file descriptor or ZMQ socket used to receive events in an ioloop.")
|
||||
|
||||
(defvar jupyter-ioloop-nsockets 1
|
||||
"The number of sockets being polled by `jupyter-ioloop-poller'.")
|
||||
|
||||
(defvar jupyter-ioloop-pre-hook nil
|
||||
"A hook called at the start of every polling loop.
|
||||
The hook is called with no arguments.")
|
||||
|
||||
(defvar jupyter-ioloop-post-hook nil
|
||||
"A hook called at the end of every polling loop.
|
||||
The hook is called with a single argument, the list of polling
|
||||
events that occurred for this iteration or nil. The polling
|
||||
events have the same value as the return value of
|
||||
`zmq-poller-wait-all'.")
|
||||
|
||||
(defvar jupyter-ioloop-timers nil)
|
||||
|
||||
(defvar jupyter-ioloop-timeout 200
|
||||
"Maximum time (in ms) to wait for polling events on `jupyter-ioloop-poller'.")
|
||||
|
||||
(defvar jupyter-ioloop--argument-types nil
|
||||
"Argument types added via `jupyter-ioloop-add-arg-type'.")
|
||||
|
||||
(defun jupyter-ioloop-environment-p ()
|
||||
"Return non-nil if this Emacs instance is an IOLoop subprocess."
|
||||
(and noninteractive jupyter-ioloop-stdin jupyter-ioloop-poller))
|
||||
|
||||
(defclass jupyter-ioloop (jupyter-finalized-object)
|
||||
((process :type (or null process) :initform nil)
|
||||
(callbacks :type list :initform nil)
|
||||
(events :type list :initform nil)
|
||||
(setup :type list :initform nil)
|
||||
(teardown :type list :initform nil))
|
||||
:documentation "An interface for sending asynchronous messages via a subprocess.
|
||||
|
||||
An ioloop starts an Emacs subprocess setup to send events back
|
||||
and forth between the parent Emacs process and the ioloop
|
||||
asynchronously. The ioloop subprocess is essentially a polling
|
||||
loop that polls its stdin and any sockets that may have been
|
||||
created in the ioloop environment and performs pre-defined
|
||||
actions when stdin sends an event. The structure of the
|
||||
subprocess is the following
|
||||
|
||||
\(progn
|
||||
(let ((jupyter-ioloop-poller (zmq-poller)))
|
||||
<jupyter-ioloop-setup>
|
||||
<send start event to parent>
|
||||
(condition-case nil
|
||||
(while t
|
||||
(run-hook 'jupyter-ioloop-pre-hook)
|
||||
<poll for stdin/socket events>
|
||||
(run-hook 'jupyter-ioloop-post-hook))
|
||||
(quit
|
||||
<jupyter-ioloop-teardown>
|
||||
<send quit event to parent>))))
|
||||
|
||||
<jupyter-ioloop-setup> is replaced by the form in the setup slot
|
||||
of an ioloop and can be conveniently added to using
|
||||
`jupyter-ioloop-add-setup'.
|
||||
|
||||
<jupyter-ioloop-teardown> is replaced with the teardown slot and
|
||||
can be added to using `jupyter-ioloop-add-teardown'.
|
||||
|
||||
<poll for stdin/socket events> is replaced by code that will
|
||||
listen for stdin/socket events using `jupyter-ioloop-poller'.
|
||||
|
||||
You add events to be handled by the subprocess using
|
||||
`jupyter-ioloop-add-event', the return value of any event added
|
||||
is what is sent to the parent Emacs process and what will
|
||||
eventually be the sole argument to the handler function passed to
|
||||
`jupyter-ioloop-start'. To suppress the subprocess from sending
|
||||
anything back to the parent, ensure nil is returned by the form
|
||||
created by `jupyter-ioloop-add-event'.
|
||||
|
||||
See `jupyter-channel-ioloop' for an example of its usage.")
|
||||
|
||||
(cl-defmethod initialize-instance ((ioloop jupyter-ioloop) &optional _slots)
|
||||
(cl-call-next-method)
|
||||
(jupyter-add-finalizer ioloop
|
||||
(lambda ()
|
||||
(with-slots (process) ioloop
|
||||
(when (process-live-p process)
|
||||
(delete-process process))))))
|
||||
|
||||
(defun jupyter-ioloop-wait-until (ioloop event cb &optional timeout progress-msg)
|
||||
"Wait until EVENT occurs on IOLOOP.
|
||||
If EVENT occurs, call CB and return its value if non-nil. CB is
|
||||
called with a single argument, an event list whose first element
|
||||
is EVENT. If CB returns nil, continue waiting until EVENT occurs
|
||||
again or until TIMEOUT seconds elapses, TIMEOUT defaults to
|
||||
`jupyter-default-timeout'. If TIMEOUT is reached, return nil.
|
||||
|
||||
If PROGRESS-MSG is non-nil, a progress reporter will be displayed
|
||||
while waiting using PROGRESS-MSG as the message."
|
||||
(declare (indent 2))
|
||||
(cl-check-type ioloop jupyter-ioloop)
|
||||
(jupyter-with-timeout
|
||||
(progress-msg (or timeout jupyter-default-timeout))
|
||||
(let ((e (jupyter-ioloop-last-event ioloop)))
|
||||
(when (eq (car-safe e) event) (funcall cb e)))))
|
||||
|
||||
(defun jupyter-ioloop-last-event (ioloop)
|
||||
"Return the last event received on IOLOOP."
|
||||
(cl-check-type ioloop jupyter-ioloop)
|
||||
(and (oref ioloop process)
|
||||
(process-get (oref ioloop process) :last-event)))
|
||||
|
||||
(defmacro jupyter-ioloop-add-setup (ioloop &rest body)
|
||||
"Set IOLOOP's `jupyter-ioloop-setup' slot to BODY.
|
||||
BODY is the code that will be evaluated before the IOLOOP sends a
|
||||
start event to the parent process."
|
||||
(declare (indent 1))
|
||||
`(setf (oref ,ioloop setup)
|
||||
(append (oref ,ioloop setup)
|
||||
(quote ,body))))
|
||||
|
||||
(defmacro jupyter-ioloop-add-teardown (ioloop &rest body)
|
||||
"Set IOLOOP's `jupyter-ioloop-teardown' slot to BODY.
|
||||
BODY is the code that will be evaluated just before the IOLOOP
|
||||
sends a quit event to the parent process."
|
||||
(declare (indent 1))
|
||||
`(setf (oref ,ioloop teardown)
|
||||
(append (oref ,ioloop teardown)
|
||||
(quote ,body))))
|
||||
|
||||
(defmacro jupyter-ioloop-add-arg-type (tag fun)
|
||||
"Add a new argument type for arguments in `jupyter-ioloop-add-event'.
|
||||
If an argument has the form (arg TAG), where TAG is a symbol, in
|
||||
the ARGS argument of `jupyter-ioloop-add-event', replace it with
|
||||
the result of evaluating the form returned by FUN on arg in the
|
||||
IOLOOP environment.
|
||||
|
||||
For example suppose we define an argument type, jupyter-channel:
|
||||
|
||||
(jupyter-ioloop-add-arg-type jupyter-channel
|
||||
(lambda (arg)
|
||||
`(or (object-assoc ,arg :type jupyter-channel-ioloop-channels)
|
||||
(error \"Channel not alive (%s)\" ,arg))))
|
||||
|
||||
and define an event like
|
||||
|
||||
(jupyter-ioloop-add-event ioloop stop-channel ((channel jupyter-channel))
|
||||
(jupyter-stop channel))
|
||||
|
||||
Finally after adding other events and starting the ioloop we send
|
||||
an event like
|
||||
|
||||
(jupyter-send ioloop \='stop-channel :shell)
|
||||
|
||||
Then before the stop-channel event defined by
|
||||
`jupyter-ioloop-add-event' is called in the IOLOOP environment,
|
||||
the value for the channel argument passed by the `jupyter-send'
|
||||
call is replaced by the form returned by the function specified
|
||||
in the `jupyter-ioloop-add-arg-type' call."
|
||||
(declare (indent 1))
|
||||
`(progn
|
||||
(setf (alist-get ',tag jupyter-ioloop--argument-types nil 'remove) nil)
|
||||
;; NOTE: FUN is quoted to ensure lexical closures aren't created
|
||||
(push (cons ',tag ,(list '\` fun)) jupyter-ioloop--argument-types)))
|
||||
|
||||
(defun jupyter-ioloop--replace-args (args)
|
||||
"Convert special arguments in ARGS.
|
||||
Map over ARGS, converting its elements into
|
||||
|
||||
,arg or ,(app (lambda (x) BODY) arg)
|
||||
|
||||
for use in a `pcase' form. The latter form occurs when one of
|
||||
ARGS is of the form (arg TAG) where TAG is one of the keys in
|
||||
`jupyter-ioloop--argument-types'. BODY will be replaced with the
|
||||
result of calling the function associated with TAG in
|
||||
`jupyter-ioloop--argument-types'.
|
||||
|
||||
Return the list of converted arguments."
|
||||
(mapcar (lambda (arg)
|
||||
(pcase arg
|
||||
(`(,val ,tag)
|
||||
(let ((form (alist-get tag jupyter-ioloop--argument-types)))
|
||||
(list '\, (list 'app `(lambda (x) ,(funcall form 'x)) val))))
|
||||
(_ (list '\, arg))))
|
||||
args))
|
||||
|
||||
(defmacro jupyter-ioloop-add-event (ioloop event args &optional doc &rest body)
|
||||
"For IOLOOP, add an EVENT handler.
|
||||
ARGS is a list of arguments that are bound when EVENT occurs. DOC
|
||||
is an optional documentation string describing what BODY, the
|
||||
expression which will be evaluated when EVENT occurs, does. If
|
||||
BODY evaluates to any non-nil value, it will be sent to the
|
||||
parent Emacs process. A nil value for BODY means don't send
|
||||
anything.
|
||||
|
||||
Some arguments are treated specially:
|
||||
|
||||
If one of ARGS is a list (<sym> tag) where <sym> is any symbol,
|
||||
then the parent process that sends EVENT to IOLOOP is expected to
|
||||
send a value that will be bound to <sym> and be handled by an
|
||||
argument handler associated with tag before BODY is evaluated in
|
||||
the IOLOOP process, see `jupyter-ioloop-add-arg-type'."
|
||||
(declare (indent 3) (doc-string 4) (debug t))
|
||||
(unless (stringp doc)
|
||||
(when doc
|
||||
(setq body (cons doc body))))
|
||||
`(setf (oref ,ioloop events)
|
||||
(cons (list (quote ,event) (quote ,args) (quote ,body))
|
||||
(cl-remove-if (lambda (x) (eq (car x) (quote ,event)))
|
||||
(oref ,ioloop events)))))
|
||||
|
||||
(defun jupyter-ioloop--event-dispatcher (ioloop exp)
|
||||
"For IOLOOP return a form suitable for matching against EXP.
|
||||
That is, return an expression which will cause an event to be
|
||||
fired if EXP matches any event types handled by IOLOOP.
|
||||
|
||||
TODO: Explain these
|
||||
By default this adds the events quit, callback, and timer."
|
||||
(let ((user-events
|
||||
(cl-loop
|
||||
for (event args body) in (oref ioloop events)
|
||||
for cond = (list '\` (cl-list*
|
||||
event (jupyter-ioloop--replace-args args)))
|
||||
if (memq event '(quit callback timer))
|
||||
do (error "Event can't be one of quit, callback, or, timer")
|
||||
;; cond = `(event ,arg1 ,arg2 ...)
|
||||
else collect `(,cond ,@body))))
|
||||
`(let* ((cmd ,exp)
|
||||
(res (pcase cmd
|
||||
,@user-events
|
||||
;; Default events
|
||||
(`(timer ,id ,period ,cb)
|
||||
;; Ensure we don't send anything back to the parent process
|
||||
(prog1 nil
|
||||
(let ((timer (run-at-time 0.0 period (byte-compile cb))))
|
||||
(puthash id timer jupyter-ioloop-timers))))
|
||||
(`(callback ,cb)
|
||||
;; Ensure we don't send anything back to the parent process
|
||||
(prog1 nil
|
||||
(setq jupyter-ioloop-timeout 0)
|
||||
(add-hook 'jupyter-ioloop-pre-hook (byte-compile cb) 'append)))
|
||||
('(quit) (signal 'quit nil))
|
||||
(_ (error "Unhandled command %s" cmd)))))
|
||||
;; Can only send lists at the moment
|
||||
(when (and res (listp res)) (zmq-prin1 res)))))
|
||||
|
||||
(cl-defgeneric jupyter-ioloop-add-callback ()
|
||||
(declare (indent 1)))
|
||||
|
||||
(cl-defmethod jupyter-ioloop-add-callback ((ioloop jupyter-ioloop) cb)
|
||||
"In IOLOOP, add CB to be run in the IOLOOP environment.
|
||||
CB is run at the start of every polling loop. Callbacks are
|
||||
called in the order they are added.
|
||||
|
||||
WARNING: A function added as a callback should be quoted to avoid
|
||||
sending closures to the IOLOOP. An example:
|
||||
|
||||
(jupyter-ioloop-add-callback ioloop
|
||||
`(lambda () (zmq-prin1 \='foo \"bar\")))"
|
||||
(cl-assert (functionp cb))
|
||||
(cl-callf append (oref ioloop callbacks) (list cb))
|
||||
(when (process-live-p (oref ioloop process))
|
||||
(jupyter-send ioloop 'callback (macroexpand-all cb))))
|
||||
|
||||
(defun jupyter-ioloop-poller-add (socket events)
|
||||
"Add SOCKET to be polled using the `jupyter-ioloop-poller'.
|
||||
EVENTS are the polling events that should be listened for on
|
||||
SOCKET. If `jupyter-ioloop-poller' is not a `zmq-poller' object
|
||||
do nothing."
|
||||
(when (zmq-poller-p jupyter-ioloop-poller)
|
||||
(zmq-poller-add jupyter-ioloop-poller socket events)
|
||||
(cl-incf jupyter-ioloop-nsockets)))
|
||||
|
||||
(defun jupyter-ioloop-poller-remove (socket)
|
||||
"Remove SOCKET from the `jupyter-ioloop-poller'.
|
||||
If `jupyter-ioloop-poller' is not a `zmq-poller' object do
|
||||
nothing."
|
||||
(when (zmq-poller-p jupyter-ioloop-poller)
|
||||
(zmq-poller-remove jupyter-ioloop-poller socket)
|
||||
(cl-decf jupyter-ioloop-nsockets)))
|
||||
|
||||
(defun jupyter-ioloop--body (ioloop on-stdin)
|
||||
`(let (events)
|
||||
(condition-case nil
|
||||
(progn
|
||||
,@(oref ioloop setup)
|
||||
;; Initialize any callbacks that were added before the ioloop was
|
||||
;; started
|
||||
(setq jupyter-ioloop-pre-hook
|
||||
(mapcar (lambda (f)
|
||||
(when (symbolp f)
|
||||
(setq f (symbol-function f)))
|
||||
(unless (byte-code-function-p f)
|
||||
(byte-compile f)))
|
||||
(append jupyter-ioloop-pre-hook
|
||||
(quote ,(mapcar #'macroexpand-all
|
||||
(oref ioloop callbacks))))))
|
||||
;; Notify the parent process we are ready to do something
|
||||
(zmq-prin1 '(start))
|
||||
(let ((on-stdin (byte-compile (lambda () ,on-stdin))))
|
||||
(while t
|
||||
(run-hooks 'jupyter-ioloop-pre-hook)
|
||||
(setq events
|
||||
(condition-case nil
|
||||
(zmq-poller-wait-all
|
||||
jupyter-ioloop-poller
|
||||
jupyter-ioloop-nsockets
|
||||
jupyter-ioloop-timeout)
|
||||
((zmq-EAGAIN zmq-EINTR zmq-ETIMEDOUT) nil)))
|
||||
(let ((stdin-event (zmq-assoc jupyter-ioloop-stdin events)))
|
||||
(when stdin-event
|
||||
(setq events (delq stdin-event events))
|
||||
(funcall on-stdin)))
|
||||
(run-hook-with-args 'jupyter-ioloop-post-hook events))))
|
||||
(quit
|
||||
,@(oref ioloop teardown)
|
||||
(zmq-prin1 '(quit))))))
|
||||
|
||||
(defun jupyter-ioloop--function (ioloop port)
|
||||
"Return the function that does the work of IOLOOP.
|
||||
The returned function is suitable to send to a ZMQ subprocess for
|
||||
evaluation using `zmq-start-process'.
|
||||
|
||||
If PORT is non-nil the returned function will create a ZMQ PULL
|
||||
socket to receive events from the parent process on the PORT of
|
||||
the local host, otherwise events are expected to be received on
|
||||
STDIN. This is useful on Windows systems which don't allow
|
||||
polling the STDIN file handle."
|
||||
`(lambda (ctx)
|
||||
(push ,(file-name-directory (locate-library "jupyter-base")) load-path)
|
||||
(require 'jupyter-ioloop)
|
||||
(setq jupyter-ioloop-poller (zmq-poller))
|
||||
(setq jupyter-ioloop-stdin
|
||||
,(if port
|
||||
`(let ((sock (zmq-socket ctx zmq-PAIR)))
|
||||
(prog1 sock
|
||||
(zmq-connect sock (format "tcp://127.0.0.1:%s" ,port))))
|
||||
0))
|
||||
(zmq-poller-add jupyter-ioloop-poller jupyter-ioloop-stdin zmq-POLLIN)
|
||||
,(jupyter-ioloop--body
|
||||
ioloop (jupyter-ioloop--event-dispatcher
|
||||
ioloop (if port '(read (zmq-recv jupyter-ioloop-stdin))
|
||||
'(zmq-subprocess-read))))))
|
||||
|
||||
(defun jupyter-ioloop-alive-p (ioloop)
|
||||
"Return non-nil if IOLOOP is ready to receive/send events."
|
||||
(cl-check-type ioloop jupyter-ioloop)
|
||||
(with-slots (process) ioloop
|
||||
(and (process-live-p process) (process-get process :start))))
|
||||
|
||||
(defun jupyter-ioloop--make-filter (ioloop handler)
|
||||
(lambda (event)
|
||||
(let ((process (oref ioloop process)))
|
||||
(process-put process :last-event event)
|
||||
(cond
|
||||
((eq (car-safe event) 'start)
|
||||
(process-put process :start t))
|
||||
((eq (car-safe event) 'quit)
|
||||
(process-put process :quit t))
|
||||
(t
|
||||
(funcall handler event))))))
|
||||
|
||||
(cl-defmethod jupyter-ioloop-start ((ioloop jupyter-ioloop)
|
||||
handler
|
||||
&key buffer)
|
||||
"Start an IOLOOP.
|
||||
HANDLER is a function of one argument and will be passed an event
|
||||
received by the subprocess that IOLOOP represents, an event is
|
||||
just a list.
|
||||
|
||||
If IOLOOP was previously running, it is stopped first.
|
||||
|
||||
If BUFFER is non-nil it should be a buffer that will be used as
|
||||
the IOLOOP subprocess buffer, see `zmq-start-process'."
|
||||
(jupyter-ioloop-stop ioloop)
|
||||
(let (stdin port)
|
||||
;; NOTE: A socket is used to read input from the parent process to avoid
|
||||
;; the stdin buffering done when using `read-from-minibuffer' in the
|
||||
;; subprocess. When `noninteractive', `read-from-minibuffer' uses
|
||||
;; `getc_unlocked' internally and `getc_unlocked' reads from the stdin FILE
|
||||
;; object as opposed to reading directly from STDIN_FILENO. The problem is
|
||||
;; that FILE objects are buffered streams which means that every message
|
||||
;; the parent process sends does not necessarily correspond to a POLLIN
|
||||
;; event on STDIN_FILENO in the subprocess. Since we only call
|
||||
;; `read-from-minibuffer' when there is a POLLIN event on STDIN_FILENO
|
||||
;; there is the potential that a message is waiting to be handled in the
|
||||
;; buffer used by stdin which will only get handled if we send more
|
||||
;; messages to the subprocess thereby creating more POLLIN events.
|
||||
(when (or t (memq system-type '(windows-nt ms-dos cygwin)))
|
||||
(setq stdin (zmq-socket (zmq-current-context) zmq-PAIR))
|
||||
(setq port (zmq-bind-to-random-port stdin "tcp://127.0.0.1")))
|
||||
(let ((process (zmq-start-process
|
||||
(jupyter-ioloop--function ioloop (when stdin port))
|
||||
:filter (jupyter-ioloop--make-filter ioloop handler)
|
||||
:buffer buffer)))
|
||||
(oset ioloop process process)
|
||||
(when stdin
|
||||
(process-put process :stdin stdin))
|
||||
(jupyter-ioloop-wait-until ioloop 'start #'identity))))
|
||||
|
||||
(cl-defmethod jupyter-ioloop-stop ((ioloop jupyter-ioloop))
|
||||
"Stop IOLOOP.
|
||||
Send a quit event to IOLOOP, wait until it actually quits before
|
||||
returning."
|
||||
(with-slots (process) ioloop
|
||||
(when (process-live-p process)
|
||||
(jupyter-send ioloop 'quit)
|
||||
(unless (jupyter-ioloop-wait-until ioloop 'quit #'identity)
|
||||
(delete-process process))
|
||||
(when-let* ((stdin (process-get process :stdin))
|
||||
(socket-p (zmq-socket-p stdin)))
|
||||
(zmq-unbind stdin (zmq-get-option stdin zmq-LAST-ENDPOINT))))))
|
||||
|
||||
(defvar jupyter-ioloop--send-buffer nil)
|
||||
|
||||
(defun jupyter-ioloop--dump-message (plist)
|
||||
(with-current-buffer
|
||||
(if (buffer-live-p jupyter-ioloop--send-buffer)
|
||||
jupyter-ioloop--send-buffer
|
||||
(setq jupyter-ioloop--send-buffer
|
||||
(get-buffer-create " *jupyter-ioloop-send*")))
|
||||
(erase-buffer)
|
||||
(let (print-level print-length)
|
||||
(prin1 plist (current-buffer)))
|
||||
(buffer-string)))
|
||||
|
||||
(cl-defmethod jupyter-send ((ioloop jupyter-ioloop) &rest args)
|
||||
"Using IOLOOP, send ARGS to its process.
|
||||
|
||||
All arguments passed to this function are sent as a list to the
|
||||
process unchanged. This means that all arguments should be
|
||||
serializable."
|
||||
(with-slots (process) ioloop
|
||||
(cl-assert (process-live-p process))
|
||||
(let ((stdin (process-get process :stdin)))
|
||||
(if stdin
|
||||
(let ((msg (jupyter-ioloop--dump-message args)) sent)
|
||||
(while (not sent)
|
||||
(condition-case nil
|
||||
(progn
|
||||
(zmq-send stdin (encode-coding-string msg 'utf-8) zmq-DONTWAIT)
|
||||
(setq sent t))
|
||||
(zmq-EAGAIN (accept-process-output nil 0)))))
|
||||
(zmq-subprocess-send process args)))))
|
||||
|
||||
(provide 'jupyter-ioloop)
|
||||
|
||||
;;; jupyter-ioloop.el ends here
|
||||
Reference in New Issue
Block a user