mirror of
https://github.com/vale981/emacs-jupyter
synced 2025-03-05 07:41:37 -05:00
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:
parent
acaafd46be
commit
2ac0a8b621
3 changed files with 296 additions and 207 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Add table
Reference in a new issue