Do not conform to the semantics of org-babel-insert-result

There was an ugly hack that destructively modified the source block parameters
supplied to the org-babel execute function to make `org-babel-insert-result` do
all of the insertion work. This relied too much on knowing the internals of
that function. I also could never figure out how to insert stream results in a
clean way.

Instead we manually insert the results by taking advantage of the `org-element`
API. Specifically the function `org-element-interpret-data` which takes an org
syntax tree and returns its printed representation. Now the
`jupyter-org-result` method returns either a string or a syntax tree. If the
latter is returned, it is filtered through `org-element-interpret-data` to
obtain the string representation for insertion.

In addition, all source blocks insert results in a RESULTS drawer. This allows
for inserting stream output as raw text in the drawer and allows for a way to
append results since the end of the drawer acts as an insertion point.
This commit is contained in:
Nathaniel Nicandro 2018-11-18 22:11:12 -06:00
parent acaafd46be
commit 2ac0a8b621
3 changed files with 296 additions and 207 deletions

View file

@ -29,12 +29,11 @@
(require 'jupyter-repl) (require 'jupyter-repl)
(require 'ob) (require 'ob)
(require 'org-element)
(declare-function org-at-drawer-p "org") (declare-function org-at-drawer-p "org")
(declare-function org-in-regexp "org" (regexp &optional nlines visually)) (declare-function org-in-regexp "org" (regexp &optional nlines visually))
(declare-function org-in-src-block-p "org" (&optional inside)) (declare-function org-in-src-block-p "org" (&optional inside))
(declare-function org-element-at-point "org-element")
(declare-function org-element-property "org-element")
(declare-function org-babel-python-table-or-string "ob-python" (results)) (declare-function org-babel-python-table-or-string "ob-python" (results))
(declare-function org-babel-jupyter-initiate-session "ob-jupyter" (&optional session params)) (declare-function org-babel-jupyter-initiate-session "ob-jupyter" (&optional session params))
@ -97,36 +96,35 @@ source code block. Set by `org-babel-execute:jupyter'.")))
(cl-defmethod jupyter-handle-stream ((_client jupyter-org-client) (cl-defmethod jupyter-handle-stream ((_client jupyter-org-client)
(req jupyter-org-request) (req jupyter-org-request)
name _name
text) text)
(and (eq (jupyter-org-request-result-type req) 'output) (jupyter-org--add-result
(equal name "stdout") req (with-temp-buffer
(jupyter-org--add-result req (ansi-color-apply text)))) (jupyter-with-control-code-handling
(insert (ansi-color-apply text)))
(buffer-string))))
(cl-defmethod jupyter-handle-status ((_client jupyter-org-client) (cl-defmethod jupyter-handle-status ((_client jupyter-org-client)
(req jupyter-org-request) (req jupyter-org-request)
execution-state) execution-state)
(when (and (jupyter-org-request-async req) (when (equal execution-state "idle")
(equal execution-state "idle")) (message "Code block evaluation complete.")
(jupyter-org--clear-request-id req) (when (jupyter-org-request-async req)
(run-hooks 'org-babel-after-execute-hook))) (jupyter-org--clear-request-id req)
(run-hooks 'org-babel-after-execute-hook))))
(cl-defmethod jupyter-handle-error ((_client jupyter-org-client) (cl-defmethod jupyter-handle-error ((_client jupyter-org-client)
(req jupyter-org-request) (req jupyter-org-request)
ename ename
evalue evalue
traceback) traceback)
;; Clear the file parameter to prevent showing the error as a file link (jupyter-with-display-buffer "traceback" 'reset
(when (jupyter-org-file-header-arg-p req) (jupyter-insert-ansi-coded-text
(let ((params (jupyter-org-request-block-params req))) (mapconcat #'identity traceback "\n"))
(setcar (member "file" (assq :result-params params)) "scalar"))) (goto-char (line-beginning-position))
(let ((emsg (format "%s: %s" ename (ansi-color-apply evalue)))) (pop-to-buffer (current-buffer)))
(jupyter-with-display-buffer "traceback" 'reset (jupyter-org--add-result
(jupyter-insert-ansi-coded-text req (jupyter-org-scalar (format "%s: %s" ename (ansi-color-apply evalue)))))
(mapconcat #'identity traceback "\n"))
(goto-char (line-beginning-position))
(pop-to-buffer (current-buffer)))
(jupyter-org--add-result req emsg)))
(cl-defmethod jupyter-handle-execute-result ((_client jupyter-org-client) (cl-defmethod jupyter-handle-execute-result ((_client jupyter-org-client)
(req jupyter-org-request) (req jupyter-org-request)
@ -134,7 +132,8 @@ source code block. Set by `org-babel-execute:jupyter'.")))
data data
metadata) metadata)
(unless (eq (jupyter-org-request-result-type req) 'output) (unless (eq (jupyter-org-request-result-type req) 'output)
(jupyter-org--add-result req (jupyter-org-result req data metadata)))) (jupyter-org--add-result
req (jupyter-org-result req data metadata))))
(cl-defmethod jupyter-handle-display-data ((_client jupyter-org-client) (cl-defmethod jupyter-handle-display-data ((_client jupyter-org-client)
(req jupyter-org-request) (req jupyter-org-request)
@ -146,8 +145,7 @@ source code block. Set by `org-babel-execute:jupyter'.")))
;; can #+NAME be used as a display ;; can #+NAME be used as a display
;; ID? ;; ID?
_transient) _transient)
(unless (eq (jupyter-org-request-result-type req) 'output) (jupyter-org--add-result req (jupyter-org-result req data metadata)))
(jupyter-org--add-result req (jupyter-org-result req data metadata))))
(cl-defmethod jupyter-handle-execute-reply ((client jupyter-org-client) (cl-defmethod jupyter-handle-execute-reply ((client jupyter-org-client)
(_req jupyter-org-request) (_req jupyter-org-request)
@ -260,6 +258,114 @@ the `syntax-table' will be set to that of the REPL buffers."
(add-hook 'org-mode-hook 'jupyter-org-enable-completion) (add-hook 'org-mode-hook 'jupyter-org-enable-completion)
;;; Constructing org syntax trees
(defun jupyter-org-export-block (type value)
"Return an export-block `org-element'.
The block will export TYPE and the contents of the block will be
VALUE."
(list 'export-block (list :type type
:value (org-element-normalize-string value))))
(defun jupyter-org-file-link (path)
"Return a file link `org-element' that points to PATH."
;; A link is an object not an element so :post-blank means number of spaces
;; not number of newlines. Wrapping the link in a list ensures that
;; `org-element-interpret-data' will insert a newline after inserting the
;; link.
(list (list 'link (list :type "file" :path path)) "\n"))
(defun jupyter-org-src-block (language parameters value &optional switches)
"Return a src-block `org-element'.
LANGUAGE, PARAMETERS, VALUE, and SWITCHES all have the same
meaning as a src-block `org-element'."
(declare (indent 2))
(list 'src-block (list :language language
:parameters parameters
:switched switches
:value value)))
(defun jupyter-org-example-block (value)
"Return an example-block `org-element' with VALUE."
(list 'example-block (list :value (org-element-normalize-string value))))
;; From `org-babel-insert-result'
(defun jupyter-org-tabulablep (r)
;; Non-nil when result R can be turned into
;; a table.
(and (listp r)
(null (cdr (last r)))
(cl-every
(lambda (e) (or (atom e) (null (cdr (last e)))))
r)))
(defun jupyter-org-scalar (value)
"Return a scalar VALUE.
If VALUE is a string, return either a fixed-width `org-element'
or example-block depending on
`org-babel-min-lines-for-block-output'.
If VALUE is a list and can be represented as a table, return an
`org-mode' table as a string.
Otherwise, return VALUE formated as a fixed-width `org-element'."
(cond
((stringp value)
(if (cl-loop with i = 0 for c across value if (eq c ?\n) do (cl-incf i)
thereis (> i org-babel-min-lines-for-block-output))
(jupyter-org-example-block value)
(list 'fixed-width (list :value value))))
((and (listp value)
(not (or (memq (car value) org-element-all-objects)
(memq (car value) org-element-all-elements)))
(jupyter-org-tabulablep value))
;; From `org-babel-insert-result'
(with-temp-buffer
(insert (concat (orgtbl-to-orgtbl
(if (cl-every
(lambda (e)
(or (eq e 'hline) (listp e)))
value)
value
(list value))
nil)
"\n"))
(goto-char (point-min))
(when (org-at-table-p) (org-table-align))
(let ((table (buffer-string)))
(prog1 table
;; We need a way to distinguish a table string that is easily removed
;; from the code block vs a regular string that will need to be
;; wrapped in a drawer. See `jupyter-org--append-result'.
(put-text-property 0 1 'org-table t table)))))
(t
(list 'fixed-width (list :value (format "%S" value))))))
(defun jupyter-org-latex-fragment (value)
"Return a latex-fragment `org-element' consisting of VALUE."
(list 'latex-fragment (list :value value)))
(defun jupyter-org-latex-environment (value)
"Return a latex-fragment `org-element' consisting of VALUE."
(list 'latex-environment (list :value value)))
(defun jupyter-org-results-drawer (&rest results)
"Return a drawer `org-element' containing RESULTS.
RESULTS can be either strings or other `org-element's. The
returned drawer has a name of \"RESULTS\"."
;; Ensure the last element has a newline if it is already a string. This is
;; to avoid situations like
;;
;; :RESULTS:
;; foo:END:
(let ((last (last results)))
(when (and (stringp (car last))
(not (zerop (length (car last)))))
(setcar last (org-element-normalize-string (car last)))))
(apply #'org-element-set-contents
(list 'drawer (list :drawer-name "RESULTS"))
results))
;;; Inserting results ;;; Inserting results
(defun jupyter-org-image-file-name (data ext) (defun jupyter-org-image-file-name (data ext)
@ -303,7 +409,7 @@ is created."
(insert data) (insert data)
(when base64-encoded (when base64-encoded
(base64-decode-region (point-min) (point-max)))))) (base64-decode-region (point-min) (point-max))))))
(cons "file" file))) (jupyter-org-file-link file)))
(cl-defmethod jupyter-org-result ((req jupyter-org-request) data &optional metadata) (cl-defmethod jupyter-org-result ((req jupyter-org-request) data &optional metadata)
"For REQ, return the rendered DATA. "For REQ, return the rendered DATA.
@ -334,32 +440,31 @@ data and metadata of the current MIME type."
(cl-loop for (k _v) on data by #'cddr collect k)))))) (cl-loop for (k _v) on data by #'cddr collect k))))))
(cl-defgeneric jupyter-org-result (_mime _params _data &optional _metadata) (cl-defgeneric jupyter-org-result (_mime _params _data &optional _metadata)
"Return a pair, (RENDER-PARAM . RESULT), used to display MIME DATA. "Return an `org-element' representing a result.
RENDER-PARAM is either a result parameter, i.e. one of the result Either a string or an `org-element' is a valid return value of
parameters of `org-babel-insert-result', or a key value pair this method. The former will be inserted as is, while the latter
which will be appended to PARAMS when rendering RESULT in the will be inserted by calling `org-element-interpret-data' first.
`org-mode' buffer.
The returned result should be a representation of a MIME type
whose value is DATA.
DATA is the data representing the MIME type and METADATA is any DATA is the data representing the MIME type and METADATA is any
associated metadata associated with the MIME DATA. associated metadata associated with the MIME DATA.
As an example, if DATA only contains the mimetype As an example, if DATA only contains the mimetype
`:text/markdown', then RESULT-PARAM should be something like `:text/markdown', then the returned results is
(:wrap . \"SRC markdown\") (jupyter-org-export-block \"markdown\" data)"
and RESULT will be the markdown text which should be wrapped in
an \"EXPORT markdown\" block. See `org-babel-insert-result'."
(ignore)) (ignore))
(cl-defmethod jupyter-org-result ((_mime (eql :application/vnd.jupyter.widget-view+json)) (cl-defmethod jupyter-org-result ((_mime (eql :application/vnd.jupyter.widget-view+json))
_params _data &optional _metadata) _params _data &optional _metadata)
(cons "scalar" "Widget")) ;; TODO: Clickable text to open up a browser
(jupyter-org-scalar "Widget"))
(cl-defmethod jupyter-org-result ((_mime (eql :text/org)) params data (cl-defmethod jupyter-org-result ((_mime (eql :text/org)) _params data
&optional _metadata) &optional _metadata)
(let ((result-params (alist-get :result-params params))) data)
(cons (if (member "raw" result-params) "scalar" "org") data)))
(cl-defmethod jupyter-org-result ((mime (eql :image/png)) params data (cl-defmethod jupyter-org-result ((mime (eql :image/png)) params data
&optional _metadata) &optional _metadata)
@ -377,16 +482,28 @@ an \"EXPORT markdown\" block. See `org-babel-insert-result'."
(cl-defmethod jupyter-org-result ((_mime (eql :text/markdown)) _params data (cl-defmethod jupyter-org-result ((_mime (eql :text/markdown)) _params data
&optional _metadata) &optional _metadata)
(cons '(:wrap . "EXPORT markdown") data)) (jupyter-org-export-block "markdown" data))
(cl-defmethod jupyter-org-result ((_mime (eql :text/latex)) params data (cl-defmethod jupyter-org-result ((_mime (eql :text/latex)) params data
&optional _metadata) &optional _metadata)
(let ((result-params (alist-get :result-params params))) (if (member "raw" (alist-get :result-params params))
(cons (if (member "raw" result-params) "scalar" "latex") data))) (with-temp-buffer
(save-excursion (insert data))
(or (save-excursion (org-element-latex-fragment-parser))
(let ((env (ignore-errors
(org-element-latex-environment-parser
(point-max) nil))))
(if (eq (org-element-type env) 'latex-environment) env
(jupyter-org-latex-environment
(concat "\
\\begin{minipage}{\\textwidth}
\\begin{flushright}\n" data "\n\\end{flushright}
\\end{minipage}\n"))))))
(jupyter-org-export-block "latex" data)))
(cl-defmethod jupyter-org-result ((_mime (eql :text/html)) _params data (cl-defmethod jupyter-org-result ((_mime (eql :text/html)) _params data
&optional _metadata) &optional _metadata)
(cons "html" data)) (jupyter-org-export-block "html" data))
;; NOTE: The order of :around methods is that the more specialized wraps the ;; NOTE: The order of :around methods is that the more specialized wraps the
;; more general, this makes sense since it is how the primary methods work as ;; more general, this makes sense since it is how the primary methods work as
@ -400,178 +517,143 @@ Call the next method, if it returns \"scalar\" results, return a
new \"scalar\" result with the result of calling new \"scalar\" result with the result of calling
`org-babel-script-escape' on the old result." `org-babel-script-escape' on the old result."
(let ((result (cl-call-next-method))) (let ((result (cl-call-next-method)))
(cond (jupyter-org-scalar
((and (equal (car result) "scalar") (cond
(stringp (cdr result))) ((stringp result) (org-babel-script-escape result))
(cons "scalar" (org-babel-script-escape (cdr result)))) (t result)))))
(t result))))
(cl-defmethod jupyter-org-result ((_mime (eql :text/plain)) _params data (cl-defmethod jupyter-org-result ((_mime (eql :text/plain)) _params data
&optional _metadata) &optional _metadata)
(cons "scalar" data)) data)
;; NOTE: The parameters are destructively added to the result parameters passed
;; to `org-babel-insert-result' in order to avoid advising
;; `org-babel-execute-src-block', but this might be the way to go to avoid
;; depending on the the priority of result parameters in
;; `org-babel-insert-result'.
(defun jupyter-org--inject-render-param (render-param params)
"Destructively modify result parameters for `org-babel-insert-result'.
RENDER-PARAM is the first element of the cons cell returned by
`jupyter-org-result', PARAMS are the parameters passed to
`org-babel-execute:jupyter'.
Append RENDER-PARAM to the :result-params of PARAMS if it is a
string. Otherwise, if RENDER-PARAM is a cons cell
(KEYWORD . STRING)
append RENDER-PARAM to PARAMS."
(cond
((consp render-param)
(nconc params (list render-param)))
((stringp render-param)
(let ((rparams (alist-get :result-params params)))
;; `org-babel-insert-result' looks for replace first, thus we have to
;; remove it if we are injecting append or prepend.
;;
;; TODO: Do the inverse operation in
;; `jupyter-org--clear-render-param'. This may not really be
;; necessary since this will only be injected for async results.
(if (and (member render-param '("append" "prepend"))
(member "replace" rparams))
(setcar (member "replace" rparams) render-param)
(nconc rparams (list render-param)))))
((not (null render-param))
(error "Render parameter unsupported (%s)" render-param))))
(defun jupyter-org--clear-render-param (render-param params)
"Destructively modify result parameters.
Remove RENDER-PARAM from PARAMS or from the result parameters
found in PARAMS. If RENDER-PARAM is a cons cell, remove it from
the PARAMS list. If RENDER-PARAM is a string, remove it from the
`:result-params' of PARAMS."
(cond
((consp render-param)
(delq render-param params))
((stringp render-param)
(delq render-param (alist-get :result-params params)))
((not (null render-param))
(error "Render parameter unsupported (%s)" render-param))))
(defun jupyter-org--clear-request-id (req) (defun jupyter-org--clear-request-id (req)
"Delete the ID of REQ in the `org-mode' buffer if present." "Delete the ID of REQ in the `org-mode' buffer if present."
(unless (jupyter-org-request-id-cleared-p req) (save-excursion
(org-with-point-at (jupyter-org-request-marker req) (when (search-forward (jupyter-request-id req) nil t)
(save-excursion (delete-region (line-beginning-position)
(let ((start (org-babel-where-is-src-block-result))) (1+ (line-end-position)))
(when start (setf (jupyter-org-request-id-cleared-p req) t))))
(goto-char start)
(forward-line 1) (defun jupyter-org--element-end-preserve-blanks (el)
(when (search-forward (jupyter-request-id req) nil t) (let ((end (org-element-property :end el))
(delete-region (line-beginning-position) (post (or (org-element-property :post-blank el) 0)))
(1+ (line-end-position))) (save-excursion
;; Delete the entire drawer when there was nothing inside of it (goto-char end)
;; except for the id. (forward-line (- post))
(when (and (org-at-drawer-p) (line-beginning-position))))
(progn
(forward-line -1) (defun jupyter-org--append-result (req result)
(org-at-drawer-p))) (org-with-point-at (jupyter-org-request-marker req)
(delete-region (let ((result-beg (org-babel-where-is-src-block-result 'insert))
(point) (inhibit-redisplay (not debug-on-error)))
(progn (goto-char result-beg)
(forward-line 1) (unless (jupyter-org-request-id-cleared-p req)
(1+ (line-end-position)))))))))) (jupyter-org--clear-request-id req))
(setf (jupyter-org-request-id-cleared-p req) t))) (if (org-at-drawer-p)
(let* ((el (org-element-at-point))
(content-end (org-element-property :contents-end el))
(pos (or content-end
(jupyter-org--element-end-preserve-blanks el))))
(goto-char pos))
;; Only wrap the result in a drawer when its a string that would be
;; hard for `org' to remove when replacing the results or if the result
;; is the first result. If there is already a result, remove it and
;; place it as the first item in a drawer and the current result being
;; inserted as the second. Then insert the drawer.
(let* ((context (org-element-context))
(first-result (eq (org-element-type context) 'keyword)))
(cond
((and
first-result
;; When this is the first result, distinguished by there only
;; being a #+RESULTS keyword line, then results are wrapped in a
;; drawer only if: (1) The results are a string, but not a string
;; representing an org table or (2) The results cannot be removed
;; by `org-babel-remove-result'.
(if (stringp result)
;; Org tables are returned as strings by this time. So we need
;; something to distinguish them from regular strings. See
;; `jupyter-org-scalar'.
(get-text-property 0 'org-table result)
(memq (org-element-type result)
;; No need to wrap these types in a drawer since
;; `org-babel-remove-result' will be able to remove
;; them. Everything else we will have to.
'(example-block
export-block fixed-width item
plain-list src-block table)))))
(t
(and (stringp result) (setq result (org-element-normalize-string result)))
(if first-result
;; If this is the first result and it is not able to be placed
;; in the buffer without being wrapped in a drawer, wrap it.
(setq result (jupyter-org-results-drawer result))
;; Otherwise there already exists a result in the buffer that
;; needs to be wrapped along with the new result.
(setq result (jupyter-org-results-drawer context result))
;; Remove these properties since `org-element-interpret-data'
;; uses them?
;; :post-affiliated is used here as opposed to :begin so that we
;; don't remove the #+RESULTS line which is an affiliated keyword
(delete-region (org-element-property :post-affiliated context)
(jupyter-org--element-end-preserve-blanks context))
;; Ensure that a #+RESULTS: line is not prepended to context when
;; calling `org-element-interpret-data'
(org-element-put-property context :results nil)
;; Ensure there is no post-blank since
;; `org-element-interpret-data' already normalizes the string
(org-element-put-property context :post-blank nil))))))
(when (= result-beg (point))
(forward-line))
(insert (org-element-interpret-data result)))))
(defun jupyter-org--add-result (req result) (defun jupyter-org--add-result (req result)
"For a request made with CLIENT, add RESULT. "For REQ, add RESULT.
REQ is a `jupyter-org-request' and if the request is a REQ is a `jupyter-org-request' and if the request is a
synchronous request, RESULT will be pushed to the list of results synchronous request, RESULT will be pushed to the list of results
in the request's results slot. Otherwise, when the request is in the request's results slot or appended to the buffer if REQ is
asynchronous, RESULT is inserted at the location of the code already complete. Otherwise, when the request is asynchronous,
block for the request." RESULT is inserted at the location of the code block for the
;; TODO: Figure out how to handle result-type output in the request."
;; async case. Should the output be pooled and displayed when
;; finished? No I don't think so. It should be appended to the
;; current output but for multiline output that is received
;; this will end up either putting it in an example block and
;; you would have multiple example blocks for a single output.
;; The best bet would be to insert it as raw text in a drawer.
(unless (equal (jupyter-org-request-silent req) "none")
(or (consp result) (setq result (cons "scalar" result))))
(if (jupyter-org-request-silent req) (if (jupyter-org-request-silent req)
(unless (equal (jupyter-org-request-silent req) "none") (unless (equal (jupyter-org-request-silent req) "none")
;; TODO: Process the result before displaying (message "%s" (org-element-interpret-data result)))
(message "%s" (cdr result))) (if (or (jupyter-org-request-async req)
(if (jupyter-org-request-async req) (jupyter-request-idle-received-p req))
(let ((params (jupyter-org-request-block-params req))) (jupyter-org--append-result req result)
(org-with-point-at (jupyter-org-request-marker req)
(jupyter-org--clear-request-id req)
(jupyter-org--insert-results result params))
(unless (member "append" (assq :result-params params))
(jupyter-org--inject-render-param "append" params)))
(push result (jupyter-org-request-results req))))) (push result (jupyter-org-request-results req)))))
(defun jupyter-org--insert-results (results params) (defun jupyter-org-insert-async-id (req)
"Insert RESULTS at the current source block location. "Insert the ID of REQ.
RESULTS is either a single cons cell or a list of such cells, Meant to be used as the return value of
each cell having the form `org-babel-execute:jupyter'."
(prog1 nil
(advice-add 'message :override #'ignore)
(setq org-babel-after-execute-hook
(let ((hook org-babel-after-execute-hook))
(lambda ()
(advice-remove 'message #'ignore)
(unwind-protect
(jupyter-org--add-result
req (jupyter-org-scalar (jupyter-org-request-id req)))
(setq org-babel-after-execute-hook hook)
(run-hooks 'org-babel-after-execute-hook)))))))
(RENDER-PARAM . RESULT) (defun jupyter-org-insert-sync-results (req)
"Insert the results of REQ.
As is returned by `jupyter-org-result'. PARAMS are the parameters
passed to `org-babel-execute:jupyter'.
Note that if RESULTS is a list, the last result in the list will
be the one that eventually is shown in the org document. This is
due to how `org-babel-insert-result' works. This behavior can be
modified if the source block has an \"append\" or \"prepend\"
parameter; in this case results will either be appended or
prepended.
The current implementation of `org-babel-execute:jupyter' will
automatically add this parameter internally so under normal use
it does not need to be added by the user."
;; Unless this is a list of results
(unless (car-safe (car results))
(setq results (list results)))
(cl-loop
;; NOTE: This relies on `org-babel-insert-result' only
;; caring about the parameters of the info and not
;; anything else.
with info = (list nil nil params)
with result-params = (alist-get :result-params params)
for (render-param . result) in results
do (jupyter-org--inject-render-param render-param params)
(cl-letf (((symbol-function 'message) #'ignore))
(org-babel-insert-result result result-params info))
(jupyter-org--clear-render-param render-param params)))
(defun jupyter-org-insert-sync-results (client req)
"For CLIENT, insert the results of REQ.
Meant to be used as the return value of `org-babel-execute:jupyter'." Meant to be used as the return value of `org-babel-execute:jupyter'."
(let ((results (nreverse (jupyter-org-request-results req))) (prog1 nil
(params (jupyter-org-request-block-params req))) (advice-add 'message :override #'ignore)
(cl-destructuring-bind (render-param . result) (car results) (setq org-babel-after-execute-hook
(jupyter-org--inject-render-param render-param params) (let ((hook org-babel-after-execute-hook))
(prog1 result (lambda ()
;; Insert remaining results after the first one has been (advice-remove 'message #'ignore)
;; inserted. (unwind-protect
(when (cdr results) (cl-loop
;; TODO: Prevent running the hooks until all results have been for result in (nreverse (jupyter-org-request-results req))
;; inserted. Think harder about how to insert a list of do (jupyter-org--add-result req result))
;; results. (setq org-babel-after-execute-hook hook)
(run-at-time (run-hooks 'org-babel-after-execute-hook)))))))
0.01 nil
(lambda ()
(org-with-point-at (jupyter-org-request-marker req)
(let ((params (jupyter-org-request-block-params req))
(jupyter-current-client client))
(jupyter-org--clear-render-param render-param params)
(jupyter-org--inject-render-param "append" params)
(jupyter-org--insert-results (cdr results) params)
(set-marker (jupyter-org-request-marker req) nil))))))))))
(provide 'jupyter-org-client) (provide 'jupyter-org-client)

View file

@ -82,9 +82,8 @@ buffer."
&rest _) &rest _)
(let ((result (cl-call-next-method))) (let ((result (cl-call-next-method)))
(cond (cond
((and (equal (car result) "scalar") ((stringp result)
(stringp (cdr result))) (org-babel-python-table-or-string result))
(cons "scalar" (org-babel-python-table-or-string (cdr result))))
(t result)))) (t result))))
(provide 'jupyter-python) (provide 'jupyter-python)

View file

@ -223,6 +223,15 @@ Re-add the file parameters on the next call to
(remove-hook 'org-babel-after-execute-hook #'reset t))) (remove-hook 'org-babel-after-execute-hook #'reset t)))
(add-hook 'org-babel-after-execute-hook #'reset nil t)))))) (add-hook 'org-babel-after-execute-hook #'reset nil t))))))
(defun org-babel-jupyter--after-execute (old-hook _client req)
(advice-remove 'message #'ignore)
(unwind-protect
(org-with-point-at (jupyter-org-request-marker req)
(jupyter-org--append-result
req (jupyter-org-scalar (jupyter-org-request-id req))))
(setq org-babel-after-execute-hook old-hook)
(run-hooks 'org-babel-after-execute-hook)))
(defun org-babel-execute:jupyter (body params) (defun org-babel-execute:jupyter (body params)
"Execute BODY according to PARAMS. "Execute BODY according to PARAMS.
BODY is the code to execute for the current Jupyter `:session' in BODY is the code to execute for the current Jupyter `:session' in
@ -242,13 +251,12 @@ the PARAMS alist."
(oset client block-params params) (oset client block-params params)
(jupyter-send-execute-request client :code code)))) (jupyter-send-execute-request client :code code))))
(cond (cond
((equal (alist-get :async params) "yes") ((or (equal (alist-get :async params) "yes")
(org-babel-jupyter-clear-file-param req) (plist-member params :async))
(concat (when (member "raw" (assq :result-params params)) ": ") (jupyter-org-insert-async-id req))
(jupyter-request-id req)))
(t (t
(jupyter-wait-until-idle req most-positive-fixnum) (jupyter-wait-until-idle req most-positive-fixnum)
(jupyter-org-insert-sync-results client req))))) (jupyter-org-insert-sync-results req)))))
(defun org-babel-jupyter-make-language-alias (kernel lang) (defun org-babel-jupyter-make-language-alias (kernel lang)
"Simimilar to `org-babel-make-language-alias' but for Jupyter src-blocks. "Simimilar to `org-babel-make-language-alias' but for Jupyter src-blocks.