Add jupyter-message-lambda

This simplifies the writing of message callbacks.
This commit is contained in:
Nathaniel Nicandro 2019-01-21 23:18:50 -06:00
parent 1054dc8857
commit d709b31a64
No known key found for this signature in database
GPG key ID: C34814B309DD06B8
4 changed files with 122 additions and 72 deletions

View file

@ -978,10 +978,9 @@ text/plain representation."
(prog1 req
(jupyter-add-callback req
:execute-reply
(lambda (msg)
(jupyter-with-message-content msg (status evalue)
(jupyter-message-lambda (status evalue)
(unless (equal status "ok")
(error "%s" (ansi-color-apply evalue)))))))))))
(error "%s" (ansi-color-apply evalue))))))))))
(when msg
(jupyter-message-data msg (or mime :text/plain)))))
@ -1006,29 +1005,29 @@ to the above explanation."
(had-result nil))
(jupyter-add-callback req
:execute-reply
(lambda (msg)
(jupyter-with-message-content msg (status ename evalue)
(jupyter-message-lambda (status ename evalue)
(if (equal status "ok")
(unless had-result
(message "jupyter: eval done"))
(message "%s: %s"
(ansi-color-apply ename)
(ansi-color-apply evalue)))))
(setq ename (ansi-color-apply ename))
(setq evalue (ansi-color-apply evalue))
(if (string-prefix-p ename evalue)
;; Happens in IJulia
(message evalue)
(message "%s: %s" ename evalue))))
:execute-result
(or (and (functionp cb) cb)
(lambda (msg)
(setq had-result t)
(jupyter--display-eval-result msg)))
:error
(lambda (msg)
(jupyter-with-message-content msg (traceback)
(jupyter-message-lambda (traceback)
;; FIXME: Assumes the error in the
;; execute-reply is good enough
(when (> (apply '+ (mapcar 'length traceback)) 250)
(jupyter-display-traceback traceback))))
(jupyter-display-traceback traceback)))
:stream
(lambda (msg)
(jupyter-with-message-content msg (name text)
(jupyter-message-lambda (name text)
(pcase name
("stdout"
(jupyter-with-display-buffer "output" req
@ -1039,7 +1038,7 @@ to the above explanation."
(jupyter-with-display-buffer "error" req
(jupyter-insert-ansi-coded-text text)
(display-buffer (current-buffer)
'(display-buffer-below-selected))))))))
'(display-buffer-below-selected)))))))
req))
(defun jupyter-eval-region (beg end &optional cb)
@ -1050,8 +1049,22 @@ ignored when called interactively."
(interactive "r")
(jupyter-eval-string (buffer-substring-no-properties beg end) cb))
(defun jupyter-eval--insert-result (pos region msg)
(jupyter-with-message-data msg ((res text/plain))
(defun jupyter-eval-line-or-region (insert)
"Evaluate the current line or region with the `jupyter-current-client'.
If the current region is active send it using
`jupyter-eval-region', otherwise send the current line.
With a prefix argument, evaluate and INSERT the text/plain
representation of the results in the current buffer."
(interactive "P")
(let ((region (when (use-region-p)
(car (region-bounds)))))
(funcall #'jupyter-eval-region
(or (car region) (line-beginning-position))
(or (cdr region) (line-end-position))
(when insert
(let ((pos (point-marker)))
(jupyter-message-lambda ((res text/plain))
(when res
(setq res (ansi-color-apply res))
(with-current-buffer (marker-buffer pos)
@ -1066,24 +1079,7 @@ ignored when called interactively."
(insert "\n")))
(set-marker pos nil)
(insert res)
(when region (push-mark)))))))
(defun jupyter-eval-line-or-region (insert)
"Evaluate the current line or region with the `jupyter-current-client'.
If the current region is active send it using
`jupyter-eval-region', otherwise send the current line.
With a prefix argument, evaluate and INSERT the text/plain
representation of the results in the current buffer."
(interactive "P")
(let ((cb (when insert
(apply-partially
#'jupyter-eval--insert-result
(point-marker) (when (use-region-p)
(car (region-bounds)))))))
(if (use-region-p)
(jupyter-eval-region (region-beginning) (region-end) cb)
(jupyter-eval-region (line-beginning-position) (line-end-position) cb))))
(when region (push-mark)))))))))))
(defun jupyter-load-file (file)
"Send the contents of FILE using `jupyter-current-client'."

View file

@ -453,9 +453,11 @@ So to bind the :status key of MSG you would do
(jupyter-with-message-content msg (status)
BODY)"
(declare (indent 2) (debug (form listp body)))
(if keys
`(cl-destructuring-bind (&key ,@keys &allow-other-keys)
(jupyter-message-content ,msg)
,@body))
,@body)
`(progn ,@body)))
(defmacro jupyter-with-message-data (msg varlist &rest body)
"For MSG, bind the mimetypes in VARLIST and evaluate BODY.
@ -477,8 +479,42 @@ you would do
,m ',(if (keywordp (cadr el)) (cadr el)
(intern (concat ":" (symbol-name (cadr el))))))))
varlist)))
`(let* ((,m ,msg) ,@vars)
(if vars `(let* ((,m ,msg) ,@vars)
,@body)
`(progn ,@body))))
(defmacro jupyter-message-lambda (keys &rest body)
"Return a function binding KEYS to fields of a message then evaluating BODY.
The returned function takes a single argument which is expected
to be a Jupyter message property list.
The elements of KEYS can either be a symbol, KEY, or a two
element list (VAL MIMETYPE). In the former case, KEY will be
bound to the corresponding value of KEY in the
`jupyter-message-content' of the message argument. In the latter
case, VAL will be bound to the value of the MIMETYPE found in the
`jupyter-message-data' of the message."
(declare (indent defun) (debug ((&rest [&or symbolp (symbolp symbolp)]) body)))
(let ((msg (cl-gensym "msg"))
content-keys
data-keys)
(while (car keys)
(let ((key (pop keys)))
(push key (if (listp key) data-keys content-keys))))
`(lambda (,msg)
,(cond
((and data-keys content-keys)
`(jupyter-with-message-content ,msg ,content-keys
(jupyter-with-message-data ,msg ,data-keys
,@body)))
(data-keys
`(jupyter-with-message-data ,msg ,data-keys
,@body))
(content-keys
`(jupyter-with-message-content ,msg ,content-keys
,@body))
(t
`(progn ,@body))))))
(defmacro jupyter--decode-message-part (key msg)
"Return a form to decode the value of KEY in MSG.

View file

@ -1533,9 +1533,9 @@ it."
(req (let ((jupyter-inhibit-handlers t))
(jupyter-send-execute-request client :code "" :silent t))))
(jupyter-add-callback req
:execute-reply (lambda (msg)
(oset client execution-count
(1+ (jupyter-message-get msg :execution_count)))
:execute-reply
(jupyter-message-lambda (execution_count)
(oset client execution-count (1+ execution_count))
(unless (equal (jupyter-execution-state client) "busy")
;; Set the cell count and update the prompt
(jupyter-with-repl-buffer client

View file

@ -406,6 +406,24 @@
(should (json-plist-p res))
(should (eq (jupyter-message-type res) :shutdown-reply))))))
(ert-deftest jupyter-message-lambda ()
:tags '(messages)
(let ((msg (jupyter-test-message
(jupyter-request) :execute-reply
(list :status "idle" :data (list :text/plain "foo")))))
(should (equal (funcall (jupyter-message-lambda (status)
status)
msg)
"idle"))
(should (equal (funcall (jupyter-message-lambda ((res text/plain))
res)
msg)
"foo"))
(should (equal (funcall (jupyter-message-lambda (status (res text/plain))
(cons status res))
msg)
(cons "idle" "foo")))))
;;; Channels
(ert-deftest jupyter-sync-channel ()