mirror of
https://github.com/vale981/emacs-jupyter
synced 2025-03-04 23:41:37 -05:00
Implement jupyter-insert
method
The goal of this method is to act as a single entry point for insertion of kernel results in any context. One would simply add another method to handle a specific context. * jupyter-base.el (jupyter-mime-types): (jupyter-nongraphic-mime-types): New variables that give mime-types that can be handled. (jupyter-insert): New method for dispatching to code that inserts mimetype representations in the current buffer. * jupyter-mime.el: New file. (jupyter-display-ids): (jupyter-handle-control-codes): (jupyter-fontify-buffers): (jupyter-get-fontify-buffer): (jupyter-fixup-font-lock-properties): (jupyter-add-font-lock-properties): (jupyter-fontify-according-to-mode): (jupyter-insert-html): (jupyter-markdown-mouse-map): (juputer-markdown-follow-link-at-point): (jupyter-insert-markdown): (jupyter-insert-latex): (jupyter-insert-ansi-coded-text): Moved from jupyter-repl.el, replaced `jupyter-repl-` prefix with `jupyter-`. (jupyter--shr-put-image): Ditto. Also add `shr-` prefix. (jupyter--delete-javascript-tags): Ditto. Also mark as private functions. (jupyter-insert-image): Ditto. Also mark as a public function. (jupyter-insert): (DISPLAY-ID ...) Moved from jupyter-repl.el. Was `jupyter-repl-insert-data-with-id`. (jupyter-with-control-code-handling): (jupyter-markdown-follow-link): Moved from jupyter-repl.el (jupyter-insert): Implement methods to do the work previously done by `jupyter-repl-insert-data`. * jupyter-repl.el (jupyter-repl-graphic-mimetypes): Moved to jupyter-base.el, inverted and renamed to `jupyter-nongraphic-mime-types`. (jupyter-repl-graphic-data-p): Remove unused function. (jupyter-repl-insert-data): Remove, replace calls with `jupyter-insert`. (jupyter-repl-add-font-lock-properties): (jupyter-repl-fixup-font-lock-properties): (jupyter-repl-get-fontify-buffer): (jupyter-repl-fontify-according-to-mode): (jupyter-repl-delete-javascript-tags): (jupyter-repl-put-image): (jupyter-repl-insert-html): (jupyter-repl-markdown-mouse-map): (jupyter-repl-markdown-follow-link-at-point): (jupyter-repl-insert-markdown): (jupyter-repl-insert-latex): (jupyter-repl--insert-image): Moved to jupyter-mime.el, which see. (jupyter-repl-insert-data-with-id): Ditto. Changed to a `jupyter-insert` method dispatched on a string argument. (jupyter-repl-insert-ansi-coded-text): Ditto. Replace calls with `jupyter-insert-ansi-coded-text`. (jupyter-with-control-code-handling): (jupyter-markdown-follow-link): Moved to jupyter-mime.el. * jupyter-org-client.el (jupyter-handle-error): Replace `jupyter-repl-insert-ansi-coded-text` with `jupyter-insert-ansi-coded-text`. * jupyter-tests.el (jupyter-insert): Add tests for `jupyter-insert`
This commit is contained in:
parent
08a20ebf71
commit
75a08c26d0
5 changed files with 515 additions and 373 deletions
|
@ -146,6 +146,17 @@ directory is where kernel connection files are written to."
|
|||
The plist values are the message types either sent or received
|
||||
from the kernel.")
|
||||
|
||||
(defconst jupyter-mime-types '(:application/vnd.jupyter.widget-view+json
|
||||
:text/html :text/markdown
|
||||
:image/svg+xml :image/jpeg :image/png
|
||||
:text/latex :text/plain)
|
||||
"MIME types handled by Jupyter.")
|
||||
|
||||
(defconst jupyter-nongraphic-mime-types '(:application/vnd.jupyter.widget-view+json
|
||||
:text/html :text/markdown
|
||||
:text/plain)
|
||||
"MIME types that can be used in terminal Emacs.")
|
||||
|
||||
(defvar jupyter--debug nil
|
||||
"When non-nil, some parts of Jupyter will emit debug statements.")
|
||||
|
||||
|
|
434
jupyter-mime.el
Normal file
434
jupyter-mime.el
Normal file
|
@ -0,0 +1,434 @@
|
|||
;;; jupyter-mime.el --- Insert mime types -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2018 Nathaniel Nicandro
|
||||
|
||||
;; Author: Nathaniel Nicandro <nathanielnicandro@gmail.com>
|
||||
;; Created: 09 Nov 2018
|
||||
;; Version: 0.0.1
|
||||
|
||||
;; 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 2, 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:
|
||||
|
||||
;; Routines for working with MIME types.
|
||||
;; Adds the following methods which may be extended:
|
||||
;;
|
||||
;; - jupyter-markdown-follow-link
|
||||
;; - jupyter-insert
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'jupyter-base)
|
||||
(require 'shr)
|
||||
(require 'ansi-color)
|
||||
|
||||
(declare-function org-format-latex "org" (prefix &optional beg end dir overlays msg forbuffer processing-type))
|
||||
(declare-function markdown-link-at-pos "ext:markdown-mode" (pos))
|
||||
(declare-function markdown-follow-link-at-point "ext:markdown-mode")
|
||||
|
||||
(defvar-local jupyter-display-ids nil
|
||||
"A hash table of display IDs.
|
||||
Display IDs are implemented by setting the text property,
|
||||
`jupyter-display', to the display ID requested by a
|
||||
`:display-data' message. When a display is updated from an
|
||||
`:update-display-data' message, the display ID from the initial
|
||||
`:display-data' message is retrieved from this table and used to
|
||||
find the display in the REPL buffer. See
|
||||
`jupyter-update-display'.")
|
||||
|
||||
;;; Macros
|
||||
|
||||
;; Taken from `eshell-handle-control-codes'
|
||||
(defun jupyter-handle-control-codes (beg end)
|
||||
"Handle any control sequences between BEG and END."
|
||||
(save-excursion
|
||||
(goto-char beg)
|
||||
(while (< (point) end)
|
||||
(let ((char (char-after)))
|
||||
(cond
|
||||
((eq char ?\r)
|
||||
(if (< (1+ (point)) end)
|
||||
(if (memq (char-after (1+ (point)))
|
||||
'(?\n ?\r))
|
||||
(delete-char 1)
|
||||
(let ((end (1+ (point))))
|
||||
(beginning-of-line)
|
||||
(delete-region (point) end)))
|
||||
(add-text-properties (point) (1+ (point))
|
||||
'(invisible t))
|
||||
(forward-char)))
|
||||
((eq char ?\a)
|
||||
(delete-char 1)
|
||||
(beep))
|
||||
((eq char ?\C-h)
|
||||
(delete-region (1- (point)) (1+ (point))))
|
||||
(t
|
||||
(forward-char)))))))
|
||||
|
||||
(defmacro jupyter-with-control-code-handling (&rest body)
|
||||
"Handle control codes in any produced output generated by evaluating BODY.
|
||||
After BODY is evaluated, call `jupyter-handle-control-codes'
|
||||
on the region inserted by BODY."
|
||||
`(jupyter-with-insertion-bounds
|
||||
beg end (progn ,@body)
|
||||
(jupyter-handle-control-codes beg end)))
|
||||
|
||||
;;; Fontificiation routines
|
||||
|
||||
(defvar jupyter-fontify-buffers nil
|
||||
"An alist of (MODE . BUFFER) pairs used for fontification.
|
||||
See `jupyter-fontify-according-to-mode'.")
|
||||
|
||||
(defun jupyter-get-fontify-buffer (mode)
|
||||
"Return the buffer used to fontify text for MODE.
|
||||
Retrieve the buffer for MODE from `jupyter-repl-fontify-buffers'.
|
||||
If no buffer for MODE exists, create a new one."
|
||||
(let ((buf (alist-get mode jupyter-fontify-buffers)))
|
||||
(unless buf
|
||||
(setq buf (get-buffer-create
|
||||
(format " *jupyter-repl-fontify[%s]*" mode)))
|
||||
(with-current-buffer buf
|
||||
(funcall mode))
|
||||
(setf (alist-get mode jupyter-fontify-buffers) buf))
|
||||
buf))
|
||||
|
||||
(defun jupyter-fixup-font-lock-properties (beg end &optional object)
|
||||
"Fixup the text properties in the `current-buffer' between BEG END.
|
||||
If OBJECT is non-nil, fixup the text properties of OBJECT. Fixing
|
||||
the text properties involves substituting any `face' property
|
||||
with `font-lock-face'."
|
||||
(let ((next beg) val)
|
||||
(while (/= beg end)
|
||||
(setq val (get-text-property beg 'face object)
|
||||
next (next-single-property-change beg 'face object end))
|
||||
(remove-text-properties beg next '(face) object)
|
||||
(put-text-property beg next 'font-lock-face (or val 'default) object)
|
||||
(setq beg next))))
|
||||
|
||||
(defun jupyter-add-font-lock-properties (start end &optional object)
|
||||
"Add font lock text properties between START and END in the `current-buffer'.
|
||||
START, END, and OBJECT have the same meaning as in
|
||||
`add-text-properties'. The properties added are the ones that
|
||||
mark the text between START and END as fontified according to
|
||||
font lock. Any text between START and END that does not have a
|
||||
`font-lock-face' property will have the `default' face filled in
|
||||
for the property."
|
||||
(jupyter-fixup-font-lock-properties start end object)
|
||||
(add-text-properties start end '(fontified t font-lock-fontified t) object))
|
||||
|
||||
(defun jupyter-fontify-according-to-mode (mode str)
|
||||
"Fontify a string according to MODE.
|
||||
Return the fontified string. In addition to fontifying STR, if
|
||||
MODE has a non-default `fill-forward-paragraph-function', STR
|
||||
will be filled using `fill-region'."
|
||||
(with-current-buffer (jupyter-get-fontify-buffer mode)
|
||||
(with-silent-modifications
|
||||
(erase-buffer)
|
||||
(insert str)
|
||||
(font-lock-ensure)
|
||||
(jupyter-add-font-lock-properties (point-min) (point-max))
|
||||
(when (not (memq fill-forward-paragraph-function
|
||||
'(forward-paragraph)))
|
||||
(fill-region (point-min) (point-max) t 'nosqueeze))
|
||||
(buffer-string))))
|
||||
|
||||
;;; `jupyter-insert' method
|
||||
|
||||
(cl-defgeneric jupyter-insert (_mime _data &optional _metadata)
|
||||
"Insert MIME data in the current buffer.
|
||||
Additions to this method should insert DATA assuming it has a
|
||||
mime type of MIME. If METADATA is non-nil, it will be a property
|
||||
list containing extra properties for inserting DATA such as
|
||||
:width and :height for image mime types.
|
||||
|
||||
If MIME is considered handled, but does not insert anything in
|
||||
the current buffer, return a non-nil value to indicate that MIME
|
||||
has been handled."
|
||||
(ignore))
|
||||
|
||||
(cl-defmethod jupyter-insert ((plist cons) &optional metadata)
|
||||
"Insert the content contained in PLIST.
|
||||
PLIST should be a property list that contains the key :data and
|
||||
optionally the key :metadata. The value of :data shall be another
|
||||
property list that contains MIME types as keys and their
|
||||
representations as values. For each MIME type in
|
||||
`jupyter-mime-types' call
|
||||
|
||||
(jupyter-insert MIME (plist-get data MIME) (plist-get metadata MIME))
|
||||
|
||||
until one of the invocations inserts text into the current
|
||||
buffer (tested by comparisons with `buffer-modified-tick') or
|
||||
returns a non-nil value. When either of these cases occur, return
|
||||
MIME. Note you may also call this method like
|
||||
|
||||
(jupyter-insert data metadata)
|
||||
|
||||
Note on non-graphic displays, `jupyter-nongraphic-mime-types' is
|
||||
used instead of `jupyter-mime-types'.
|
||||
|
||||
When no valid mimetype is present, a warning is shown."
|
||||
(cl-assert plist json-plist)
|
||||
;; Allow for passing the data plist directly this allows for
|
||||
;; (jupyter-insert data nil) to work
|
||||
(let* ((data (or (plist-get plist :data) plist))
|
||||
(metadata (if (eq data plist) metadata
|
||||
(plist-get plist :metadata))))
|
||||
(when data
|
||||
(or (let ((tick (buffer-modified-tick)))
|
||||
(jupyter-loop-over-mime (if (display-graphic-p) jupyter-mime-types
|
||||
jupyter-nongraphic-mime-types)
|
||||
mime data metadata
|
||||
(and (or (jupyter-insert mime data metadata)
|
||||
(/= tick (buffer-modified-tick)))
|
||||
mime)))
|
||||
(prog1 nil
|
||||
(warn "No valid mimetype found %s"
|
||||
(cl-loop for (k _v) on data by #'cddr collect k)))))))
|
||||
|
||||
;;; HTML
|
||||
|
||||
(defun jupyter--shr-put-image (spec alt &optional flags)
|
||||
"Identical to `shr-put-image', but ensure :ascent is 50.
|
||||
SPEC, ALT and FLAGS have the same meaning as in `shr-put-image'.
|
||||
The :ascent of an image is set to 50 so that the image center
|
||||
aligns on the current line."
|
||||
(let ((image (shr-put-image spec alt flags)))
|
||||
(prog1 image
|
||||
(when image
|
||||
;; Ensure we use an ascent of 50 so that the image center aligns with
|
||||
;; the output prompt of a REPL buffer.
|
||||
(setf (image-property image :ascent) 50)
|
||||
(force-window-update)))))
|
||||
|
||||
(defun jupyter--delete-javascript-tags ()
|
||||
(while (re-search-forward "<script type='text/javascript'>" nil t)
|
||||
(delete-region
|
||||
(match-beginning 0)
|
||||
(progn
|
||||
(re-search-forward "</script>")
|
||||
(point)))))
|
||||
|
||||
(defun jupyter-insert-html (html)
|
||||
"Parse and insert the HTML string using `shr'."
|
||||
(cl-letf (((symbol-function #'libxml-parse-html-region)
|
||||
;; Be strict about syntax. Specifically `libxml-parse-html-region'
|
||||
;; converts camel cased tags/attributes such as viewBox to viewbox
|
||||
;; in the dom since html is case insensitive. See #4.
|
||||
#'libxml-parse-xml-region)
|
||||
(shr-put-image-function #'jupyter--shr-put-image)
|
||||
(beg (point)))
|
||||
(insert html)
|
||||
(save-restriction
|
||||
(narrow-to-region beg (point))
|
||||
(goto-char (point-min))
|
||||
;; TODO: We can't really do much about javascript so
|
||||
;; delete those regions instead of trying to parse
|
||||
;; them. Maybe just re-direct to a browser like with
|
||||
;; widgets?
|
||||
;; NOTE: Parsing takes a very long time when the text
|
||||
;; is > ~500000 characters.
|
||||
(jupyter--delete-javascript-tags)
|
||||
(shr-render-region (point-min) (point-max))
|
||||
(jupyter-add-font-lock-properties (point-min) (point-max)))))
|
||||
|
||||
;;; Markdown
|
||||
|
||||
(defvar markdown-hide-markup)
|
||||
(defvar markdown-hide-urls)
|
||||
(defvar markdown-fontify-code-blocks-natively)
|
||||
(defvar markdown-mode-mouse-map)
|
||||
|
||||
(defvar jupyter-markdown-mouse-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map [return] 'jupyter-markdown-follow-link-at-point)
|
||||
(define-key map [follow-link] 'mouse-face)
|
||||
(define-key map [mouse-2] 'jupyter-markdown-follow-link-at-point)
|
||||
map)
|
||||
"Keymap when `point' is over a markdown link in the REPL buffer.")
|
||||
|
||||
(cl-defgeneric jupyter-markdown-follow-link (_link-text _url _ref-label _title-text _bang)
|
||||
"Follow the markdown link at `point'."
|
||||
(markdown-follow-link-at-point))
|
||||
|
||||
(defun jupyter-markdown-follow-link-at-point ()
|
||||
"Handle markdown links specially."
|
||||
(interactive)
|
||||
(let ((link (markdown-link-at-pos (point))))
|
||||
(when (car link)
|
||||
(apply #'jupyter-markdown-follow-link (cddr link)))))
|
||||
|
||||
(defun jupyter-insert-markdown (text)
|
||||
"Insert TEXT, fontifying it using `markdown-mode' first."
|
||||
(let ((beg (point)))
|
||||
(insert
|
||||
(let ((markdown-hide-markup t)
|
||||
(markdown-hide-urls t)
|
||||
(markdown-fontify-code-blocks-natively t))
|
||||
(jupyter-fontify-according-to-mode 'markdown-mode text)))
|
||||
;; Update keymaps
|
||||
(let ((end (point)) next)
|
||||
(setq beg (next-single-property-change beg 'keymap nil end))
|
||||
(while (/= beg end)
|
||||
(setq next (next-single-property-change beg 'keymap nil end))
|
||||
(when (eq (get-text-property beg 'keymap) markdown-mode-mouse-map)
|
||||
(put-text-property beg next 'keymap jupyter-markdown-mouse-map))
|
||||
(setq beg next)))))
|
||||
|
||||
;;; LaTeX
|
||||
|
||||
(defvar org-format-latex-options)
|
||||
(defvar org-preview-latex-image-directory)
|
||||
(defvar org-babel-jupyter-resource-directory)
|
||||
(defvar org-preview-latex-default-process)
|
||||
|
||||
(defun jupyter-insert-latex (tex)
|
||||
"Generate and insert a LaTeX image based on TEX.
|
||||
|
||||
Note that this uses `org-format-latex' to generate the LaTeX
|
||||
image."
|
||||
;; FIXME: Getting a weird error when killing the temp buffers created by
|
||||
;; `org-format-latex'. When generating the image, it seems that the temp
|
||||
;; buffers created have the same major mode and local variables as the REPL
|
||||
;; buffer which causes the query function to ask to kill the kernel client
|
||||
;; when the temp buffers are killed!
|
||||
(let ((kill-buffer-query-functions nil)
|
||||
(org-format-latex-options
|
||||
`(:foreground
|
||||
default
|
||||
:background default :scale 2.0
|
||||
:matchers ,(plist-get org-format-latex-options :matchers)))
|
||||
(beg (point-marker))
|
||||
(end (point-marker)))
|
||||
(set-marker-insertion-type end t)
|
||||
(insert tex)
|
||||
(org-format-latex
|
||||
org-preview-latex-image-directory
|
||||
beg end org-babel-jupyter-resource-directory
|
||||
'overlays "Creating LaTeX image...%s"
|
||||
'forbuffer
|
||||
;; Use the default method for creating image files
|
||||
org-preview-latex-default-process)
|
||||
(goto-char end)
|
||||
(set-marker beg nil)
|
||||
(set-marker end nil)))
|
||||
|
||||
;;; Images
|
||||
|
||||
(defun jupyter-insert-image (data type &optional metadata)
|
||||
"Insert image DATA as TYPE in the current buffer.
|
||||
TYPE has the same meaning as in `create-image'. METADATA is a
|
||||
plist containing :width and :height keys that will be used as the
|
||||
width and height of the image."
|
||||
(cl-destructuring-bind (&key width height &allow-other-keys) metadata
|
||||
(let ((img (create-image data type 'data :width width :height height)))
|
||||
(insert-image img))))
|
||||
|
||||
;;; Plain text
|
||||
|
||||
(defun jupyter-insert-ansi-coded-text (text)
|
||||
"Insert TEXT, converting ANSI color codes to font lock faces."
|
||||
(jupyter-with-insertion-bounds
|
||||
beg end (insert (ansi-color-apply text))
|
||||
(jupyter-fixup-font-lock-properties beg end)))
|
||||
|
||||
;;; `jupyter-insert' method additions
|
||||
|
||||
(cl-defmethod jupyter-insert ((_mime (eql :text/html)) data
|
||||
&context ((functionp 'libxml-parse-html-region)
|
||||
(eql t))
|
||||
&optional _metadata)
|
||||
(jupyter-insert-html data)
|
||||
(insert "\n"))
|
||||
|
||||
(cl-defmethod jupyter-insert ((_mime (eql :text/markdown)) data
|
||||
&context ((require 'markdown-mode nil t)
|
||||
(eql markdown-mode))
|
||||
&optional _metadata)
|
||||
(jupyter-insert-markdown data))
|
||||
|
||||
(cl-defmethod jupyter-insert ((_mime (eql :text/latex)) data
|
||||
&context ((require 'org nil t)
|
||||
(eql org))
|
||||
&optional _metadata)
|
||||
(jupyter-insert-latex data)
|
||||
(insert "\n"))
|
||||
|
||||
(cl-defmethod jupyter-insert ((_mime (eql :image/svg+xml)) data
|
||||
&context ((and (image-type-available-p 'svg) t)
|
||||
(eql t))
|
||||
&optional metadata)
|
||||
(jupyter-insert-image data 'svg metadata)
|
||||
(insert "\n"))
|
||||
|
||||
(cl-defmethod jupyter-insert ((_mime (eql :image/jpeg)) data
|
||||
&context ((and (image-type-available-p 'jpeg) t)
|
||||
(eql t))
|
||||
&optional metadata)
|
||||
(jupyter-insert-image (base64-decode-string data) 'jpeg metadata)
|
||||
(insert "\n"))
|
||||
|
||||
(cl-defmethod jupyter-insert ((_mime (eql :image/png)) data
|
||||
&context ((and (image-type-available-p 'png) t)
|
||||
(eql t))
|
||||
&optional metadata)
|
||||
(jupyter-insert-image (base64-decode-string data) 'png metadata)
|
||||
(insert "\n"))
|
||||
|
||||
(cl-defmethod jupyter-insert ((_mime (eql :text/plain)) data
|
||||
&optional _metadata)
|
||||
(jupyter-insert-ansi-coded-text data)
|
||||
(insert "\n"))
|
||||
|
||||
;;; Insert with display IDs
|
||||
|
||||
;; FIXME: The support for display IDs has not really been tested.
|
||||
|
||||
(cl-defmethod jupyter-insert :before ((_display-id string) &rest _)
|
||||
"Initialize `juptyer-display-ids'"
|
||||
;; FIXME: Set the local display ID hash table for the current buffer, or
|
||||
;; should display IDs be global? Then we would have to associate marker
|
||||
;; positions as well in this table.
|
||||
(unless jupyter-display-ids
|
||||
(setq jupyter-display-ids (make-hash-table
|
||||
:test #'equal
|
||||
:weakness 'value))))
|
||||
|
||||
(cl-defmethod jupyter-insert ((display-id string) data &optional metadata)
|
||||
"Associate DISPLAY-ID with DATA when inserting DATA.
|
||||
DATA and METADATA have the same meaning as in
|
||||
`jupyter-insert'.
|
||||
|
||||
The default implementation adds a jupyter-display text property
|
||||
to any inserted text and a jupyter-display-begin property to the
|
||||
first character.
|
||||
|
||||
Currently there is no support for associating a DISPLAY-ID if
|
||||
DATA is displayed as a widget."
|
||||
(jupyter-with-insertion-bounds
|
||||
beg end (jupyter-insert data metadata)
|
||||
;; Don't add display IDs to widgets since those are currently implemented
|
||||
;; using an external browser and not in the current buffer.
|
||||
(when (and (not (memq :application/vnd.jupyter.widget-view+json data))
|
||||
(< beg end))
|
||||
(let ((id (gethash display-id jupyter-display-ids)))
|
||||
(unless id
|
||||
(setq id (puthash display-id display-id jupyter-display-ids)))
|
||||
(put-text-property beg end 'jupyter-display id)
|
||||
(put-text-property beg (1+ beg) 'jupyter-display-begin t)))))
|
||||
|
||||
(provide 'jupyter-mime)
|
||||
|
||||
;;; jupyter-mime.el ends here
|
|
@ -113,7 +113,7 @@ source code block. Set by `org-babel-execute:jupyter'.")))
|
|||
(setcar (member "file" (assq :result-params params)) "scalar")))
|
||||
(let ((emsg (format "%s: %s" ename (ansi-color-apply evalue))))
|
||||
(jupyter-with-output-buffer "traceback" 'reset
|
||||
(jupyter-repl-insert-ansi-coded-text
|
||||
(jupyter-insert-ansi-coded-text
|
||||
(mapconcat #'identity traceback "\n"))
|
||||
(goto-char (line-beginning-position))
|
||||
(pop-to-buffer (current-buffer)))
|
||||
|
|
407
jupyter-repl.el
407
jupyter-repl.el
|
@ -57,12 +57,11 @@
|
|||
:group 'jupyter)
|
||||
|
||||
(require 'jupyter-base)
|
||||
(require 'jupyter-mime)
|
||||
(require 'jupyter-client)
|
||||
(require 'jupyter-widget-client)
|
||||
(require 'jupyter-kernel-manager)
|
||||
(require 'shr)
|
||||
(require 'ring)
|
||||
(require 'ansi-color)
|
||||
|
||||
(declare-function company-begin-backend "ext:company" (backend &optional callback))
|
||||
(declare-function company-doc-buffer "ext:company" (&optional string))
|
||||
|
@ -70,10 +69,6 @@
|
|||
(declare-function company-input-noop "ext:company")
|
||||
(declare-function company-auto-begin "ext:company")
|
||||
|
||||
(declare-function org-format-latex "org" (prefix &optional beg end dir overlays msg forbuffer processing-type))
|
||||
|
||||
(declare-function markdown-link-at-pos "ext:markdown-mode" (pos))
|
||||
(declare-function markdown-follow-link-at-point "ext:markdown-mode")
|
||||
|
||||
(declare-function yas-minor-mode "ext:yasnippet" (&optional arg))
|
||||
(declare-function yas-expand-snippet "ext:yasnippet" (content &optional start end expand-env))
|
||||
|
@ -170,16 +165,6 @@ the buffer local value of this variable is set to t and code in a
|
|||
cell is considered complete if the last line in a code cell is a
|
||||
blank line, i.e. if RET is pressed twice in a row.")
|
||||
|
||||
(defvar jupyter-repl-display-ids nil
|
||||
"A hash table of display IDs.
|
||||
Display IDs are implemented by setting the text property,
|
||||
`jupyter-display', to the display ID requested by a
|
||||
`:display-data' message. When a display is updated from an
|
||||
`:update-display-data' message, the display ID from the initial
|
||||
`:display-data' message is retrieved from this table and used to
|
||||
find the display in the REPL buffer. See
|
||||
`jupyter-repl-update-display'.")
|
||||
|
||||
(cl-generic-define-context-rewriter jupyter-repl-mode (mode &rest modes)
|
||||
`(jupyter-repl-lang-mode (derived-mode ,mode ,@modes)))
|
||||
|
||||
|
@ -208,41 +193,6 @@ executing BODY."
|
|||
`(let ((inhibit-modification-hooks t))
|
||||
,@body))
|
||||
|
||||
;; Taken from `eshell-handle-control-codes'
|
||||
(defun jupyter-repl-handle-control-codes (beg end)
|
||||
"Handle any control sequences between BEG and END."
|
||||
(save-excursion
|
||||
(goto-char beg)
|
||||
(while (< (point) end)
|
||||
(let ((char (char-after)))
|
||||
(cond
|
||||
((eq char ?\r)
|
||||
(if (< (1+ (point)) end)
|
||||
(if (memq (char-after (1+ (point)))
|
||||
'(?\n ?\r))
|
||||
(delete-char 1)
|
||||
(let ((end (1+ (point))))
|
||||
(beginning-of-line)
|
||||
(delete-region (point) end)))
|
||||
(add-text-properties (point) (1+ (point))
|
||||
'(invisible t))
|
||||
(forward-char)))
|
||||
((eq char ?\a)
|
||||
(delete-char 1)
|
||||
(beep))
|
||||
((eq char ?\C-h)
|
||||
(delete-region (1- (point)) (1+ (point))))
|
||||
(t
|
||||
(forward-char)))))))
|
||||
|
||||
(defmacro jupyter-with-control-code-handling (&rest body)
|
||||
"Handle control codes in any produced output generated by evaluating BODY.
|
||||
After BODY is evaluated, call `jupyter-repl-handle-control-codes'
|
||||
on the region inserted by BODY."
|
||||
`(jupyter-with-insertion-bounds
|
||||
beg end (progn ,@body)
|
||||
(jupyter-repl-handle-control-codes beg end)))
|
||||
|
||||
(defmacro jupyter-repl-append-output (client req &rest body)
|
||||
"Switch to CLIENT's buffer, move to the end of REQ, and run BODY.
|
||||
REQ is a `jupyter-request' previously made using CLIENT, a
|
||||
|
@ -377,60 +327,6 @@ the output buffer."
|
|||
|
||||
;;; Text insertion
|
||||
|
||||
(defun jupyter-repl-add-font-lock-properties (start end &optional object)
|
||||
"Add font lock text properties between START and END in the `current-buffer'.
|
||||
START, END, and OBJECT have the same meaning as in
|
||||
`add-text-properties'. The properties added are the ones that
|
||||
mark the text between START and END as fontified according to
|
||||
font lock. Any text between START and END that does not have a
|
||||
`font-lock-face' property will have the `default' face filled in
|
||||
for the property."
|
||||
(jupyter-repl-fixup-font-lock-properties start end object)
|
||||
(add-text-properties
|
||||
start end '(fontified t font-lock-fontified t) object))
|
||||
|
||||
(defun jupyter-repl-fixup-font-lock-properties (beg end &optional object)
|
||||
"Fixup the text properties in the `current-buffer' between BEG END.
|
||||
If OBJECT is non-nil, fixup the text properties of OBJECT. Fixing
|
||||
the text properties involves substituting any `face' property
|
||||
with `font-lock-face' for insertion into the REPL buffer."
|
||||
(let ((next beg) val)
|
||||
(while (/= beg end)
|
||||
(setq val (get-text-property beg 'face object)
|
||||
next (next-single-property-change beg 'face object end))
|
||||
(remove-text-properties beg next '(face) object)
|
||||
(put-text-property beg next 'font-lock-face (or val 'default) object)
|
||||
(setq beg next))))
|
||||
|
||||
(defun jupyter-repl-get-fontify-buffer (mode)
|
||||
"Return the buffer used to fontify text for MODE.
|
||||
Retrieve the buffer for MODE from `jupyter-repl-fontify-buffers'.
|
||||
If no buffer for MODE exists, create a new one."
|
||||
(let ((buf (alist-get mode jupyter-repl-fontify-buffers)))
|
||||
(unless buf
|
||||
(setq buf (get-buffer-create
|
||||
(format " *jupyter-repl-fontify[%s]*" mode)))
|
||||
(with-current-buffer buf
|
||||
(funcall mode))
|
||||
(setf (alist-get mode jupyter-repl-fontify-buffers) buf))
|
||||
buf))
|
||||
|
||||
(defun jupyter-repl-fontify-according-to-mode (mode str)
|
||||
"Fontify a string according to MODE.
|
||||
In addition to fontifying STR, if MODE has a non-default
|
||||
`fill-forward-paragraph-function', STR will be filled using
|
||||
`fill-region'."
|
||||
(with-current-buffer (jupyter-repl-get-fontify-buffer mode)
|
||||
(let ((inhibit-modification-hooks nil))
|
||||
(erase-buffer)
|
||||
(insert str)
|
||||
(font-lock-ensure))
|
||||
(jupyter-repl-add-font-lock-properties (point-min) (point-max))
|
||||
(when (not (memq fill-forward-paragraph-function
|
||||
'(forward-paragraph)))
|
||||
(fill-region (point-min) (point-max) t 'nosqueeze))
|
||||
(buffer-string)))
|
||||
|
||||
(defun jupyter-repl-insert (&rest args)
|
||||
"Insert text into the `current-buffer', possibly with text properties.
|
||||
|
||||
|
@ -475,236 +371,26 @@ can contain the following keywords along with their values:
|
|||
"Insert a read-only newline into the `current-buffer'."
|
||||
(jupyter-repl-insert "\n"))
|
||||
|
||||
;;; Handling rich output
|
||||
(cl-defmethod jupyter-insert :around (mime-or-plist
|
||||
&context (major-mode jupyter-repl-mode) &rest _)
|
||||
"If MIME was inserted, mark the region that was inserted as read only.
|
||||
Do this only when the `major-mode' is `jupyter-repl-mode'."
|
||||
(if (listp mime-or-plist) (cl-call-next-method)
|
||||
(jupyter-with-insertion-bounds
|
||||
beg end (cl-call-next-method)
|
||||
(add-text-properties beg end '(read-only t)))))
|
||||
|
||||
(defvar jupyter-repl-graphic-mimetypes '(:image/png :image/jpeg :image/svg+xml :text/latex)
|
||||
"Mimetypes that display graphics in the REPL buffer.")
|
||||
|
||||
(defun jupyter-repl-graphic-data-p (msg)
|
||||
"Check to see if MSG has mimetypes for graphics."
|
||||
(cl-loop
|
||||
with graphic-types = jupyter-repl-graphic-mimetypes
|
||||
for (mimetype _value) on (jupyter-message-get msg :data) by #'cddr
|
||||
thereis (memq mimetype graphic-types)))
|
||||
|
||||
(defun jupyter-repl-delete-javascript-tags ()
|
||||
(while (re-search-forward "<script type='text/javascript'>" nil t)
|
||||
(delete-region
|
||||
(match-beginning 0)
|
||||
(progn
|
||||
(re-search-forward "</script>")
|
||||
(point)))))
|
||||
|
||||
(defun jupyter-repl-put-image (spec alt &optional flags)
|
||||
"Identical to `shr-put-image', but ensure :ascent is 50.
|
||||
SPEC, ALT and FLAGS have the same meaning as in `shr-put-image'.
|
||||
The :ascent of an image is set to 50 so that the image center
|
||||
aligns with the output prompt."
|
||||
(let ((image (shr-put-image spec alt flags)))
|
||||
(prog1 image
|
||||
(when image
|
||||
;; Ensure we use an ascent of 50 so that the image center aligns with
|
||||
;; the output prompt.
|
||||
(setf (image-property image :ascent) 50)
|
||||
(force-window-update)))))
|
||||
|
||||
(defun jupyter-repl-insert-html (html)
|
||||
"Parse and insert the HTML string using `shr'."
|
||||
(cl-letf (((symbol-function #'libxml-parse-html-region)
|
||||
;; Be strict about syntax. Specifically `libxml-parse-html-region'
|
||||
;; converts camel cased tags/attributes such as viewBox to viewbox
|
||||
;; in the dom since html is case insensitive. See #4.
|
||||
#'libxml-parse-xml-region)
|
||||
(shr-put-image-function #'jupyter-repl-put-image)
|
||||
(beg (point)))
|
||||
(insert html)
|
||||
(save-restriction
|
||||
(narrow-to-region beg (point))
|
||||
(goto-char (point-min))
|
||||
;; TODO: We can't really do much about javascript so
|
||||
;; delete those regions instead of trying to parse
|
||||
;; them. Maybe just re-direct to a browser like with
|
||||
;; widgets?
|
||||
;; NOTE: Parsing takes a very long time when the text
|
||||
;; is > ~500000 characters.
|
||||
(jupyter-repl-delete-javascript-tags)
|
||||
(shr-render-region (point-min) (point-max))
|
||||
(jupyter-repl-add-font-lock-properties (point-min) (point-max))
|
||||
(add-text-properties (point-min) (point-max) '(read-only t)))))
|
||||
|
||||
;; Markdown integration
|
||||
|
||||
(defvar markdown-hide-markup)
|
||||
(defvar markdown-hide-urls)
|
||||
(defvar markdown-fontify-code-blocks-natively)
|
||||
|
||||
(defvar jupyter-repl-markdown-mouse-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map [return] 'jupyter-repl-markdown-follow-link-at-point)
|
||||
(define-key map [follow-link] 'mouse-face)
|
||||
(define-key map [mouse-2] 'jupyter-repl-markdown-follow-link-at-point)
|
||||
map)
|
||||
"Keymap when `point' is over a markdown link in the REPL buffer.")
|
||||
|
||||
(cl-defgeneric jupyter-markdown-follow-link (_link-text _url _ref-label _title-text _bang)
|
||||
"Follow the markdown link at `point'."
|
||||
(markdown-follow-link-at-point))
|
||||
|
||||
(defun jupyter-repl-markdown-follow-link-at-point ()
|
||||
"Handle markdown links specially."
|
||||
(interactive)
|
||||
(let ((link (markdown-link-at-pos (point))))
|
||||
(when (car link)
|
||||
(apply #'jupyter-markdown-follow-link (cddr link)))))
|
||||
|
||||
(defun jupyter-repl-insert-markdown (text)
|
||||
"Insert TEXT, fontifying it using `markdown-mode' first."
|
||||
(let ((pos (point)))
|
||||
(jupyter-repl-insert
|
||||
(let ((markdown-hide-markup t)
|
||||
(markdown-hide-urls t)
|
||||
(markdown-fontify-code-blocks-natively t))
|
||||
(jupyter-repl-fontify-according-to-mode 'markdown-mode text)))
|
||||
;; Update keymaps
|
||||
(let ((limit (point)) next)
|
||||
(setq pos (next-single-property-change pos 'keymap nil limit))
|
||||
(while (/= pos limit)
|
||||
(setq next (next-single-property-change pos 'keymap nil limit))
|
||||
(when (eq (get-text-property pos 'keymap) markdown-mode-mouse-map)
|
||||
(put-text-property pos next 'keymap jupyter-repl-markdown-mouse-map))
|
||||
(setq pos next)))))
|
||||
|
||||
(defvar org-format-latex-options)
|
||||
(defvar org-preview-latex-image-directory)
|
||||
|
||||
(defun jupyter-repl-insert-latex (tex)
|
||||
"Generate and insert a LaTeX image based on TEX.
|
||||
|
||||
Note that this uses `org-format-latex' to generate the LaTeX
|
||||
image."
|
||||
;; FIXME: Getting a weird error when killing the temp buffers created by
|
||||
;; `org-format-latex'. When generating the image, it seems that the temp
|
||||
;; buffers created have the same major mode and local variables as the REPL
|
||||
;; buffer which causes the query function to ask to kill the kernel client
|
||||
;; when the temp buffers are killed!
|
||||
(let ((kill-buffer-query-functions nil)
|
||||
(org-format-latex-options
|
||||
`(:foreground
|
||||
default
|
||||
:background default :scale 2.0
|
||||
:matchers ,(plist-get org-format-latex-options :matchers)))
|
||||
beg end)
|
||||
(setq beg (point))
|
||||
(jupyter-repl-insert tex)
|
||||
(setq end (point))
|
||||
(org-format-latex
|
||||
org-preview-latex-image-directory
|
||||
beg end org-babel-jupyter-resource-directory
|
||||
'overlays "Creating LaTeX image...%s"
|
||||
'forbuffer
|
||||
;; Use the default method for creating image files
|
||||
org-preview-latex-default-process)
|
||||
(goto-char end)))
|
||||
|
||||
(defun jupyter-repl-insert-ansi-coded-text (text)
|
||||
"Insert TEXT, converting ANSI color codes to font lock faces."
|
||||
(setq text (ansi-color-apply text))
|
||||
(jupyter-repl-add-font-lock-properties 0 (length text) text)
|
||||
(jupyter-repl-insert text))
|
||||
|
||||
(defun jupyter-repl--insert-image (data type metadata)
|
||||
"Insert image DATA as TYPE.
|
||||
TYPE has the same meaning as in `create-image'. METADATA is a
|
||||
plist containing :width and :height keys that will be used as the
|
||||
width and height of the image."
|
||||
(cl-destructuring-bind (&key width height &allow-other-keys)
|
||||
metadata
|
||||
(let ((img (create-image data type 'data :width width :height height)))
|
||||
(insert-image img (propertize " " 'read-only t))))
|
||||
(jupyter-repl-newline))
|
||||
|
||||
(defun jupyter-repl-insert-data (data metadata)
|
||||
"Insert DATA into the REPL buffer in order of decreasing richness.
|
||||
Return the mimetype of the data inserted.
|
||||
|
||||
DATA is a plist mapping mimetypes to their content. METADATA is a
|
||||
plist similar to data, but with values describing extra
|
||||
information for inserting each kind of mimetype. For example the
|
||||
value of `image/png' can be a plist with the keys `:width',
|
||||
`:height'.
|
||||
|
||||
Attempt to insert a recognized mimetype, trying each one in order
|
||||
of decreasing richness of the mimetype. The current order is
|
||||
|
||||
- application/vnd.jupyter.widget-view+json
|
||||
- text/html
|
||||
- text/markdown (only if `markdown-mode' is available)
|
||||
- text/latex
|
||||
- image/png
|
||||
- image/svg+xml
|
||||
- text/plain
|
||||
|
||||
As a special case, since Emacs is currently unable to embed a web
|
||||
browser in a sufficient way, inserting a widget does not actually
|
||||
insert it into the buffer. Instead the widget is displayed in a
|
||||
browser.
|
||||
|
||||
When no valid mimetype is present in DATA, a warning is shown."
|
||||
(let ((mimetype-inserted nil)
|
||||
(mimetypes (cl-loop
|
||||
for (k d) on data by #'cddr
|
||||
when
|
||||
(and d (not (equal d ""))
|
||||
(or (display-graphic-p)
|
||||
(not (memq k jupyter-repl-graphic-mimetypes))))
|
||||
collect k)))
|
||||
(cond
|
||||
((and (memq :application/vnd.jupyter.widget-view+json mimetypes)
|
||||
(require 'websocket nil t)
|
||||
(require 'simple-httpd nil t))
|
||||
(setq mimetype-inserted :application/vnd.jupyter.widget-view+json)
|
||||
(jupyter-widgets-display-model
|
||||
jupyter-current-client
|
||||
(plist-get (plist-get data :application/vnd.jupyter.widget-view+json)
|
||||
:model_id)))
|
||||
((and (memq :text/html mimetypes)
|
||||
(functionp 'libxml-parse-html-region))
|
||||
(setq mimetype-inserted :text/html)
|
||||
(let ((html (plist-get data :text/html)))
|
||||
(jupyter-repl-insert-html html)
|
||||
(jupyter-repl-newline)))
|
||||
((and (memq :text/markdown mimetypes)
|
||||
(require 'markdown-mode nil t))
|
||||
(setq mimetype-inserted :text/markdown)
|
||||
(jupyter-repl-insert-markdown (plist-get data :text/markdown)))
|
||||
((and (memq :text/latex mimetypes)
|
||||
(require 'org nil t))
|
||||
(setq mimetype-inserted :text/latex)
|
||||
(jupyter-repl-insert-latex (plist-get data :text/latex))
|
||||
(jupyter-repl-newline))
|
||||
((and (memq :image/svg+xml mimetypes)
|
||||
(image-type-available-p 'svg))
|
||||
(setq mimetype-inserted :image/svg+xml)
|
||||
(jupyter-repl--insert-image
|
||||
(plist-get data :image/svg+xml)
|
||||
'svg (plist-get metadata :image/svg+xml)))
|
||||
((memq :image/jpeg mimetypes)
|
||||
(setq mimetype-inserted :image/jpeg)
|
||||
(jupyter-repl--insert-image
|
||||
(base64-decode-string (plist-get data :image/jpeg))
|
||||
'jpeg (plist-get metadata :image/jpeg)))
|
||||
((memq :image/png mimetypes)
|
||||
(setq mimetype-inserted :image/png)
|
||||
(jupyter-repl--insert-image
|
||||
(base64-decode-string (plist-get data :image/png))
|
||||
'png (plist-get metadata :image/png)))
|
||||
((memq :text/plain mimetypes)
|
||||
(setq mimetype-inserted :text/plain)
|
||||
(jupyter-repl-insert-ansi-coded-text
|
||||
(plist-get data :text/plain))
|
||||
(jupyter-repl-newline))
|
||||
(t (warn "No supported mimetype found %s" mimetypes)))
|
||||
mimetype-inserted))
|
||||
(cl-defmethod jupyter-insert ((_mime (eql :application/vnd.jupyter.widget-view+json)) data
|
||||
&context ((and (require 'websocket nil t)
|
||||
(require 'simple-httpd nil t)
|
||||
(and jupyter-current-client
|
||||
(object-of-class-p
|
||||
jupyter-current-client
|
||||
'jupyter-widget-client))
|
||||
t)
|
||||
(eql t))
|
||||
&optional _metadata)
|
||||
(jupyter-widgets-display-model jupyter-current-client (plist-get data :model_id)))
|
||||
|
||||
(defun jupyter-repl-insert-message (msg)
|
||||
"Insert a messages contents in the current buffer.
|
||||
|
@ -714,10 +400,10 @@ its content has a status key of \"ok\" and has a found key of t.
|
|||
Calls the method `jupyter-repl-after-insert-message', if the
|
||||
message was inserted."
|
||||
(jupyter-with-message-content msg
|
||||
(status found data metadata)
|
||||
(status found)
|
||||
(when (and (equal status "ok") (eq found t))
|
||||
(let ((beg (point))
|
||||
(mime (jupyter-repl-insert-data data metadata)))
|
||||
(mime (jupyter-insert (jupyter-message-content msg))))
|
||||
(when mime
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
|
@ -734,29 +420,6 @@ function is called. MIME will be the mimetype of the data
|
|||
inserted."
|
||||
(ignore))
|
||||
|
||||
;; FIXME: The support for display IDs has not really been tested.
|
||||
|
||||
(defun jupyter-repl-insert-data-with-id (display-id data metadata)
|
||||
"Associate DISPLAY-ID with DATA when inserting DATA.
|
||||
DATA and METADATA have the same meaning as in
|
||||
`jupyter-repl-insert-data'.
|
||||
|
||||
Currently there is no support for associating a DISPLAY-ID if
|
||||
DATA is displayed as a widget."
|
||||
(unless jupyter-repl-display-ids
|
||||
(setq-local jupyter-repl-display-ids
|
||||
(make-hash-table :test #'equal
|
||||
:weakness 'value)))
|
||||
(let ((beg (point))
|
||||
(id (gethash display-id jupyter-repl-display-ids))
|
||||
(widget-p (memq :application/vnd.jupyter.widget-view+json data)))
|
||||
(or id widget-p (setq id (puthash display-id
|
||||
display-id
|
||||
jupyter-repl-display-ids)))
|
||||
(jupyter-repl-insert-data data metadata)
|
||||
(unless widget-p
|
||||
(put-text-property beg (point) 'jupyter-display id))))
|
||||
|
||||
;;; Prompt
|
||||
|
||||
(defun jupyter-repl--prompt-display-value (str face)
|
||||
|
@ -1181,7 +844,7 @@ lines, truncate it to something less than
|
|||
(let ((text (plist-get (plist-get pl :data) :text/plain))
|
||||
(line (or (plist-get pl :start) 0)))
|
||||
(jupyter-with-output-buffer "pager" 'reset
|
||||
(jupyter-repl-insert-ansi-coded-text text)
|
||||
(jupyter-insert-ansi-coded-text text)
|
||||
(goto-char (point-min))
|
||||
(forward-line line)
|
||||
(display-buffer (current-buffer)))))
|
||||
|
@ -1222,7 +885,7 @@ lines, truncate it to something less than
|
|||
(when req
|
||||
(jupyter-repl-append-output client req
|
||||
(jupyter-repl-insert-prompt 'out)
|
||||
(jupyter-repl-insert-data data metadata))))
|
||||
(jupyter-insert data metadata))))
|
||||
|
||||
(defun jupyter-repl-next-display-with-id (id)
|
||||
"Go to the start of the next display matching ID.
|
||||
|
@ -1246,7 +909,7 @@ message."
|
|||
(let (str)
|
||||
(while (jupyter-repl-next-display-with-id id)
|
||||
(or str (setq str (with-temp-buffer
|
||||
(jupyter-repl-insert-data data metadata)
|
||||
(jupyter-insert data metadata)
|
||||
(put-text-property
|
||||
(point-min) (point-max) 'jupyter-display id)
|
||||
(buffer-string))))
|
||||
|
@ -1285,7 +948,7 @@ message."
|
|||
(cl-destructuring-bind (&key display_id &allow-other-keys)
|
||||
transient
|
||||
(if display_id
|
||||
(jupyter-repl-insert-data-with-id display_id data metadata)
|
||||
(jupyter-insert display_id data metadata)
|
||||
(let ((inhibit-redisplay (not debug-on-error)))
|
||||
(when clear
|
||||
(jupyter-repl-clear-last-cell-output client)
|
||||
|
@ -1294,7 +957,7 @@ message."
|
|||
;; it can be placed anywhere within this let and it will prevent
|
||||
;; flickering.
|
||||
(sit-for 0.1 t))
|
||||
(jupyter-repl-insert-data data metadata)))))))
|
||||
(jupyter-insert data metadata)))))))
|
||||
|
||||
(cl-defmethod jupyter-handle-update-display-data ((client jupyter-repl-client)
|
||||
_req
|
||||
|
@ -1306,7 +969,7 @@ message."
|
|||
(unless display_id
|
||||
(error "No display ID in `:update-display-data' message"))
|
||||
(jupyter-with-repl-buffer client
|
||||
(let ((id (gethash display_id jupyter-repl-display-ids)))
|
||||
(let ((id (gethash display_id jupyter-display-ids)))
|
||||
(unless id
|
||||
(error "Display ID not found (%s)" id))
|
||||
(jupyter-repl-update-display id data metadata)))))
|
||||
|
@ -1354,7 +1017,7 @@ buffer to display TEXT."
|
|||
;; FIXME: Reset this on the next request
|
||||
(jupyter-with-output-buffer stream-buffer nil
|
||||
(let ((pos (point)))
|
||||
(jupyter-repl-insert-ansi-coded-text text)
|
||||
(jupyter-insert-ansi-coded-text text)
|
||||
(fill-region pos (point)))
|
||||
(display-buffer (current-buffer) '(display-buffer-pop-up-window
|
||||
(pop-up-windows . t))))))
|
||||
|
@ -1367,18 +1030,18 @@ buffer to display TEXT."
|
|||
(jupyter-request-last-message req))
|
||||
:comm-msg)
|
||||
(jupyter-with-output-buffer "output" req
|
||||
(jupyter-repl-insert-ansi-coded-text text)
|
||||
(jupyter-insert-ansi-coded-text text)
|
||||
(display-buffer (current-buffer))))
|
||||
(t
|
||||
(jupyter-repl-append-output client req
|
||||
(jupyter-repl-insert-ansi-coded-text text))))))
|
||||
(jupyter-insert-ansi-coded-text text))))))
|
||||
|
||||
(defun jupyter-repl-display-traceback (traceback)
|
||||
"Display TRACEBACK in its own buffer."
|
||||
(when (or (vectorp traceback) (listp traceback))
|
||||
(setq traceback (concat (mapconcat #'identity traceback "\n") "\n")))
|
||||
(jupyter-with-output-buffer "traceback" 'reset
|
||||
(jupyter-repl-insert-ansi-coded-text traceback)
|
||||
(jupyter-insert-ansi-coded-text traceback)
|
||||
(goto-char (point-min))
|
||||
(display-buffer (current-buffer) '(display-buffer-below-selected))))
|
||||
|
||||
|
@ -1392,7 +1055,7 @@ buffer to display TEXT."
|
|||
(jupyter-repl-display-traceback traceback))
|
||||
(t
|
||||
(jupyter-repl-append-output client req
|
||||
(jupyter-repl-insert-ansi-coded-text
|
||||
(jupyter-insert-ansi-coded-text
|
||||
(concat (mapconcat #'identity traceback "\n") "\n")))))))
|
||||
|
||||
(defun jupyter-repl-history--next (n)
|
||||
|
@ -2172,7 +1835,7 @@ to the above explanation."
|
|||
(if (null res)
|
||||
(jupyter-with-output-buffer "result" 'reset
|
||||
(jupyter-with-message-content msg (data metadata)
|
||||
(jupyter-repl-insert-data data metadata))
|
||||
(jupyter-insert data metadata))
|
||||
(goto-char (point-min))
|
||||
(display-buffer (current-buffer)))
|
||||
(setq res (ansi-color-apply res))
|
||||
|
@ -2198,7 +1861,7 @@ to the above explanation."
|
|||
(jupyter-with-message-content msg (name text)
|
||||
(when (equal name "stdout")
|
||||
(jupyter-with-output-buffer "output" req
|
||||
(jupyter-repl-insert-ansi-coded-text text)
|
||||
(jupyter-insert-ansi-coded-text text)
|
||||
(display-buffer (current-buffer)
|
||||
'(display-buffer-below-selected)))))))
|
||||
req)))
|
||||
|
|
|
@ -241,6 +241,40 @@ running BODY."
|
|||
(jupyter-wait-until-idle req)
|
||||
(should (= callback-count 3))))))
|
||||
|
||||
(ert-deftest jupyter-insert ()
|
||||
"Test the `jupyter-insert' method."
|
||||
(with-temp-buffer
|
||||
(let ((msg (list :data (list :text/plain "foo")
|
||||
:metadata nil)))
|
||||
(ert-info ("Return value is the mimetype inserted")
|
||||
(should (eq (jupyter-insert msg) :text/plain))
|
||||
(should (equal (buffer-string) "foo\n"))
|
||||
(erase-buffer))
|
||||
(ert-info ("Return nil on invalid mimetype")
|
||||
(should-not (jupyter-insert :text/foo "bar"))
|
||||
(should-not (jupyter-insert (list :data (list :text/foo "bar")))))
|
||||
(ert-info ("Calling with data plist directly")
|
||||
(should (eq (jupyter-insert (plist-get msg :data)) :text/plain))
|
||||
(should (equal (buffer-string) "foo\n"))
|
||||
(erase-buffer)))
|
||||
(let ((msg (list :data (list :text/plain "foo"
|
||||
:text/html "<b>bar</b>")
|
||||
:metadata nil)))
|
||||
(ert-info ("Mimetype priority")
|
||||
(should (eq (jupyter-insert msg) :text/html))
|
||||
(should (equal (buffer-string) "bar\n"))
|
||||
(erase-buffer)))
|
||||
(let ((data (list :image/jpeg (base64-encode-string "kjdaljk"))))
|
||||
(ert-info ("Method specializers")
|
||||
(cl-letf (((symbol-function #'jupyter-insert-image)
|
||||
(lambda (data &rest _) (insert data))))
|
||||
(cl-letf (((symbol-function #'image-type-available-p)
|
||||
(lambda (_typ) nil)))
|
||||
(should-not (jupyter-insert data)))
|
||||
(should (eq (jupyter-insert data) :image/jpeg))
|
||||
(should (equal (buffer-string) "kjdaljk\n"))
|
||||
(erase-buffer))))))
|
||||
|
||||
(ert-deftest jupyter-messages ()
|
||||
(ert-info ("Splitting identities from messages")
|
||||
(let ((msg (list "123" "323" jupyter-message-delimiter
|
||||
|
|
Loading…
Add table
Reference in a new issue