2018-01-08 21:38:32 -06:00
|
|
|
;;; jupyter-repl-client.el --- A Jupyter REPL client -*- lexical-binding: t -*-
|
|
|
|
|
|
|
|
;; Copyright (C) 2018 Nathaniel Nicandro
|
|
|
|
|
|
|
|
;; Author: Nathaniel Nicandro <nathanielnicandro@gmail.com>
|
|
|
|
;; Created: 08 Jan 2018
|
|
|
|
;; Version: 0.0.1
|
|
|
|
;; Keywords:
|
|
|
|
;; X-URL: https://github.com/nathan/jupyter-repl-client
|
|
|
|
|
|
|
|
;; 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:
|
|
|
|
|
|
|
|
;;
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
2018-01-13 23:19:47 -06:00
|
|
|
(defgroup jupyter-repl nil
|
2018-01-08 21:38:32 -06:00
|
|
|
"A Jupyter REPL client"
|
2018-01-13 23:19:47 -06:00
|
|
|
:group 'jupyter)
|
2018-01-08 21:38:32 -06:00
|
|
|
|
2018-01-13 22:53:19 -06:00
|
|
|
(require 'jupyter-base)
|
2017-12-23 15:34:28 -06:00
|
|
|
(require 'jupyter-client)
|
2018-01-04 23:03:18 -06:00
|
|
|
(require 'jupyter-kernel-manager)
|
2017-12-23 15:34:28 -06:00
|
|
|
(require 'xterm-color)
|
2017-12-27 21:52:19 -06:00
|
|
|
(require 'shr)
|
2018-01-13 22:53:19 -06:00
|
|
|
(require 'ring)
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
;; TODO: Read up on how method tags can be used, see
|
|
|
|
;; https://ericabrahamsen.net/tech/2016/feb/bbdb-eieio-object-oriented-elisp.html
|
|
|
|
|
|
|
|
;; TODO: Fallbacks for when the language doesn't have a major mode installed.
|
|
|
|
|
2018-01-13 23:18:15 -06:00
|
|
|
|
|
|
|
;;; User variables
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(defface jupyter-repl-input-prompt
|
|
|
|
'((((class color) (min-colors 88) (background light))
|
|
|
|
:foreground "darkseagreen2")
|
|
|
|
(((class color) (min-colors 88) (background dark))
|
|
|
|
:foreground "darkolivegreen"))
|
|
|
|
"Face used for the input prompt."
|
|
|
|
:group 'jupyter-repl)
|
|
|
|
|
|
|
|
(defface jupyter-repl-output-prompt
|
|
|
|
'((((class color) (min-colors 88) (background light))
|
|
|
|
:foreground "indianred3")
|
|
|
|
(((class color) (min-colors 88) (background dark))
|
|
|
|
:foreground "darkred"))
|
2018-01-13 23:20:50 -06:00
|
|
|
"Face used for the output prompt."
|
2017-12-23 15:34:28 -06:00
|
|
|
:group 'jupyter-repl)
|
|
|
|
|
|
|
|
(defcustom jupyter-repl-maximum-size 1024
|
|
|
|
"Maximum number of lines before the buffer is truncated."
|
|
|
|
:group 'jupyter-repl)
|
|
|
|
|
2018-01-17 21:04:22 -06:00
|
|
|
(defcustom jupyter-repl-maximum-is-complete-timeout 2
|
|
|
|
"Maximum number of seconds to wait for a is-complete reply."
|
|
|
|
:group 'jupyter-repl)
|
|
|
|
|
2018-01-13 02:17:26 -06:00
|
|
|
(defcustom jupyter-repl-history-maximum-length 100
|
|
|
|
"The maximum number of history elements to keep track of."
|
|
|
|
:group 'jupyter-repl)
|
2018-01-13 22:59:14 -06:00
|
|
|
|
|
|
|
(defcustom jupyter-repl-prompt-margin-width 12
|
|
|
|
"The width of the margin which displays prompt strings."
|
|
|
|
:group 'jupyter-repl)
|
|
|
|
|
2018-01-13 23:18:15 -06:00
|
|
|
;;; Implementation
|
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
(defclass jupyter-repl-client (jupyter-kernel-client)
|
2018-01-17 21:22:12 -06:00
|
|
|
((buffer :type buffer :initarg :buffer)
|
|
|
|
(execution-state :type string :initform "idle")
|
|
|
|
(execution-count :type integer :initform 1)))
|
|
|
|
|
2017-12-31 11:45:55 -06:00
|
|
|
|
2018-01-11 12:12:52 -06:00
|
|
|
(defvar jupyter-repl-lang-buffer nil
|
|
|
|
"A buffer with the `major-mode' set to the REPL language's `major-mode'.")
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-11 12:12:52 -06:00
|
|
|
(defvar jupyter-repl-current-client nil
|
2017-12-23 15:34:28 -06:00
|
|
|
"The `jupyter-repl-client' for the `current-buffer'.")
|
2018-01-13 23:19:34 -06:00
|
|
|
(put 'jupyter-repl-current-client 'permanent-local t)
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-11 12:12:52 -06:00
|
|
|
(defvar jupyter-repl-lang-mode nil
|
2018-01-16 11:34:22 -06:00
|
|
|
"The `major-mode' corresponding to the kernel's language.")
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-11 12:12:52 -06:00
|
|
|
(defvar jupyter-repl-history nil
|
2017-12-31 10:16:41 -06:00
|
|
|
"The history of the current Jupyter REPL.")
|
|
|
|
|
2018-01-12 18:28:23 -06:00
|
|
|
(defvar jupyter-repl-fontify-buffers nil
|
2018-01-13 23:20:50 -06:00
|
|
|
"An alist of (MODE . BUFFER) pairs used for fontification.
|
|
|
|
See `jupyter-repl-fontify-according-to-mode'.")
|
2018-01-12 18:28:23 -06:00
|
|
|
|
2018-01-11 12:26:31 -06:00
|
|
|
(defvar jupyter-repl-use-builtin-is-complete nil
|
2018-01-13 23:20:50 -06:00
|
|
|
"Whether or not to send is_complete_request's to a kernel.
|
2018-01-11 12:26:31 -06:00
|
|
|
If a Jupyter kernel does not respond to an is_complete_request,
|
2018-01-13 23:20:50 -06:00
|
|
|
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.")
|
2018-01-11 12:26:31 -06:00
|
|
|
|
2018-01-16 11:34:22 -06:00
|
|
|
;;; Macros
|
2017-12-27 21:55:58 -06:00
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
(defmacro with-jupyter-repl-buffer (client &rest body)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Switch to CLIENT's buffer before running BODY.
|
|
|
|
This switches to CLIENT's buffer slot, sets `inhibit-read-only'
|
|
|
|
to t, and then runs BODY. Afterwards, if CLIENT's buffer is
|
|
|
|
currently being shown in a window, move windows `point' to the
|
|
|
|
value of `point' in the buffer."
|
2017-12-23 15:34:28 -06:00
|
|
|
(declare (indent 1) (debug (symbolp &rest form)))
|
|
|
|
`(with-current-buffer (oref ,client buffer)
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
(prog1 (progn ,@body)
|
|
|
|
(let ((win (get-buffer-window)))
|
|
|
|
(when win (set-window-point win (point))))))))
|
|
|
|
|
2018-01-13 23:07:07 -06:00
|
|
|
(defmacro jupyter-repl-without-continuation-prompts (&rest body)
|
|
|
|
"Run BODY without inserting continuation prompts.
|
|
|
|
Normally a continuation prompt is inserted for every newline
|
|
|
|
inserted into the REPL buffer through a function in
|
|
|
|
`after-change-functions'. Prevent the function from running while
|
|
|
|
executing BODY."
|
|
|
|
`(let ((inhibit-modification-hooks t))
|
|
|
|
,@body))
|
|
|
|
|
2017-12-31 10:11:50 -06:00
|
|
|
(defmacro jupyter-repl-do-at-request (client req &rest body)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Switch to CLIENT's buffer, move to then end of REQ, and run BODY.
|
|
|
|
Switching to CLIENT's buffer is accomplished using
|
|
|
|
`with-jupyter-repl-buffer'. After switching, `point' is moved to
|
|
|
|
the `jupyter-repl-cell-beginning-position' of the cell after the
|
|
|
|
one associated with REQ, where REQ is a `jupyter-request'
|
|
|
|
previously made using CLIENT. This position is where any output
|
|
|
|
of REQ should be inserted.
|
|
|
|
|
|
|
|
Note that `inhibit-modification-hooks' is set to t when BODY is
|
|
|
|
run, this prevents any line continuation prompts to be inserted
|
|
|
|
for multi-line output."
|
2017-12-31 10:11:50 -06:00
|
|
|
(declare (indent 2) (debug (symbolp &rest form)))
|
|
|
|
`(with-jupyter-repl-buffer ,client
|
2018-01-13 23:07:07 -06:00
|
|
|
(jupyter-repl-without-continuation-prompts
|
|
|
|
(save-excursion
|
|
|
|
(jupyter-repl-goto-cell ,req)
|
|
|
|
(jupyter-repl-next-cell)
|
|
|
|
,@body))))
|
2017-12-27 21:52:19 -06:00
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
(defmacro with-jupyter-repl-lang-buffer (&rest body)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Run BODY in the `jupyter-repl-lang-buffer' of the `current-buffer'.
|
|
|
|
The contents of `jupyter-repl-lang-buffer' is erased before
|
|
|
|
running BODY."
|
2017-12-23 15:34:28 -06:00
|
|
|
(declare (indent 0) (debug (&rest form)))
|
|
|
|
`(with-current-buffer jupyter-repl-lang-buffer
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
(erase-buffer)
|
|
|
|
,@body)))
|
|
|
|
|
|
|
|
(defmacro with-jupyter-repl-cell (&rest body)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Narrow to the current cell, run BODY, then widen.
|
|
|
|
The cell is narrowed to the region between and including
|
2018-01-18 16:41:35 -06:00
|
|
|
`jupyter-repl-cell-code-beginning-position' and
|
|
|
|
`jupyter-repl-cell-code-end-position'. When BODY is run, `point' will
|
|
|
|
be at the `jupyter-repl-cell-code-beginning-position'. Note that
|
|
|
|
this assumes that the `current-buffer' is a Jupyter REPL buffer."
|
2017-12-23 15:34:28 -06:00
|
|
|
(declare (indent 0) (debug (&rest form)))
|
2017-12-27 21:25:29 -06:00
|
|
|
`(save-excursion
|
2017-12-23 15:34:28 -06:00
|
|
|
(save-restriction
|
2018-01-18 16:41:35 -06:00
|
|
|
(narrow-to-region (jupyter-repl-cell-code-beginning-position)
|
|
|
|
(jupyter-repl-cell-code-end-position))
|
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position))
|
2017-12-23 15:34:28 -06:00
|
|
|
,@body)))
|
|
|
|
|
2018-01-14 14:32:33 -06:00
|
|
|
(defun jupyter-repl-get-doc-buffer (name)
|
|
|
|
"Return the REPL documentation buffer for NAME.
|
|
|
|
A REPL documentation buffer has the following characteristics:
|
|
|
|
|
|
|
|
- `major-mode' is `special-mode'
|
|
|
|
|
|
|
|
- local keybindings to quit the window (q), and scroll the
|
|
|
|
window (SPC and <backtab>).
|
|
|
|
|
|
|
|
The buffer returned will have a `buffer-name' with the form
|
|
|
|
|
2018-01-16 11:34:22 -06:00
|
|
|
\"*jupyter-repl-NAME*\""
|
|
|
|
(let* ((bname (format "*jupyter-repl-%s*" name))
|
2018-01-14 14:32:33 -06:00
|
|
|
(buffer (get-buffer bname)))
|
|
|
|
(unless buffer
|
|
|
|
(setq buffer (get-buffer-create bname))
|
|
|
|
(with-current-buffer buffer
|
|
|
|
(special-mode)
|
|
|
|
(local-set-key "q" #'quit-window)
|
|
|
|
(local-set-key (kbd "SPC") #'scroll-down)
|
|
|
|
(local-set-key (kbd "<backtab>") #'scroll-up)))
|
|
|
|
buffer))
|
|
|
|
|
|
|
|
(defmacro with-jupyter-repl-doc-buffer (name &rest body)
|
|
|
|
"With the REPL documentation buffer corresponding to NAME, run BODY.
|
|
|
|
NAME should be a string representing the purpose of the
|
|
|
|
documentation buffer. The buffer corresponding to NAME will be
|
|
|
|
obtained by a call to `juptyer-repl-get-doc-buffer'. Before
|
|
|
|
running BODY, the doc buffer is set as the
|
|
|
|
`other-window-scroll-buffer' and the contents of the buffer are
|
|
|
|
erased."
|
|
|
|
(declare (indent 1))
|
|
|
|
(let ((buffer (make-symbol "buffer")))
|
|
|
|
`(let ((,buffer (jupyter-repl-get-doc-buffer ,name)))
|
2018-01-16 11:18:54 -06:00
|
|
|
(with-current-buffer ,buffer
|
|
|
|
(let ((other-window-scroll-buffer nil)
|
|
|
|
(inhibit-read-only t))
|
2018-01-14 14:32:33 -06:00
|
|
|
(erase-buffer)
|
2018-01-16 11:18:54 -06:00
|
|
|
(setq other-window-scroll-buffer (current-buffer))
|
2018-01-14 14:32:33 -06:00
|
|
|
,@body)))))
|
|
|
|
|
2018-01-16 11:34:22 -06:00
|
|
|
;;; Text insertion
|
2017-12-27 21:55:58 -06:00
|
|
|
|
2018-01-12 18:34:10 -06:00
|
|
|
(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
|
2018-01-16 11:34:22 -06:00
|
|
|
`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."
|
2018-01-12 18:34:10 -06:00
|
|
|
(add-text-properties
|
|
|
|
start end '(fontified t font-lock-fontified t font-lock-multiline t) object)
|
|
|
|
(font-lock-fillin-text-property
|
|
|
|
start end 'font-lock-face 'default object))
|
|
|
|
|
2018-01-17 21:23:52 -06:00
|
|
|
;; Adapted from `org-src-font-lock-fontify-block'
|
|
|
|
(defun jupyter-repl-fixup-font-lock-properties ()
|
|
|
|
"Fixup the text properties in the `curren-buffer'.
|
|
|
|
Fixing the text properties of the current buffer involves
|
|
|
|
substiuting any face properties with font-lock-face for insertion
|
|
|
|
into the REPL buffer and also adds handles
|
|
|
|
`font-lock-extra-managed-props'. Note that if text does not have
|
|
|
|
a face property, then a face of default is added to it."
|
|
|
|
(let ((pos (point-min)) next)
|
|
|
|
(catch 'done
|
|
|
|
(while (setq next (or (next-property-change pos) (point-max)))
|
|
|
|
;; Handle additional properties from font-lock, so as to
|
|
|
|
;; preserve, e.g., composition.
|
|
|
|
(dolist (prop (cons 'face font-lock-extra-managed-props))
|
|
|
|
(let ((new-prop (get-text-property pos prop)))
|
|
|
|
(put-text-property
|
|
|
|
pos next
|
|
|
|
(if (eq prop 'face) 'font-lock-face prop)
|
|
|
|
(if (eq prop 'face) (or new-prop 'default)
|
|
|
|
new-prop))))
|
|
|
|
(setq pos next)
|
|
|
|
(when (= pos (point-max))
|
|
|
|
(throw 'done t))))))
|
|
|
|
|
2018-01-12 18:28:23 -06:00
|
|
|
(defun jupyter-repl-get-fontify-buffer (mode)
|
2018-01-16 11:34:22 -06:00
|
|
|
"Get the cached buffer used to fontify text for MODE.
|
|
|
|
Consult the `jupyter-repl-fontify-buffers' alist for a buffer to
|
|
|
|
use for fontification according to MODE and return the buffer
|
|
|
|
found. If no buffer exists for MODE: create a new buffer, set its
|
|
|
|
`major-mode' to MODE, add it to `juptyer-repl-fontify-buffers',
|
|
|
|
and return the buffer."
|
2018-01-12 18:28:23 -06:00
|
|
|
(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))
|
|
|
|
|
2017-12-27 22:17:38 -06:00
|
|
|
(defun jupyter-repl-fontify-according-to-mode (mode str)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Fontify a string according to MODE.
|
2018-01-16 11:34:22 -06:00
|
|
|
MODE has the same meaning as in
|
|
|
|
`jupyter-repl-get-fontify-buffer'. STR is a string that will be
|
|
|
|
fontified according to MODE by inserting it into the buffer
|
|
|
|
returned by `jupyter-repl-get-fontify-buffer' (erasing any
|
|
|
|
contents of the buffer before insertion).
|
|
|
|
|
|
|
|
In addition to fontifying STR, if MODE has a non-default
|
|
|
|
`fill-forward-paragraph-function', STR will be filled using
|
|
|
|
`fill-region'."
|
2018-01-12 18:28:23 -06:00
|
|
|
(with-current-buffer (jupyter-repl-get-fontify-buffer mode)
|
2017-12-27 22:17:38 -06:00
|
|
|
(let ((inhibit-modification-hooks nil))
|
2018-01-12 18:28:23 -06:00
|
|
|
(erase-buffer)
|
2017-12-27 22:17:38 -06:00
|
|
|
(insert str)
|
|
|
|
(font-lock-ensure)
|
2018-01-17 21:23:52 -06:00
|
|
|
;; FIXME: This adds a font-lock-face of default if text doesn't have a
|
|
|
|
;; font-lock-face and so does `jupyter-repl-add-font-lock-properties'
|
|
|
|
(jupyter-repl-fixup-font-lock-properties))
|
2018-01-12 18:34:10 -06:00
|
|
|
(jupyter-repl-add-font-lock-properties (point-min) (point-max))
|
2018-01-16 11:37:04 -06:00
|
|
|
(when (not (memq fill-forward-paragraph-function
|
|
|
|
'(forward-paragraph)))
|
|
|
|
(fill-region (point-min) (point-max)))
|
2017-12-27 22:17:38 -06:00
|
|
|
(buffer-string)))
|
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
(defun jupyter-repl-insert (&rest args)
|
2017-12-27 21:13:23 -06:00
|
|
|
"Insert text into the `current-buffer', possibly with text properties.
|
|
|
|
|
|
|
|
This acts like `insert' except that the leading elements of ARGS
|
|
|
|
can contain the following keywords along with their values:
|
|
|
|
|
|
|
|
- `:read-only' :: A non-nil value makes the text to be inserted,
|
|
|
|
read only. This is t by default, so to make text editable you
|
|
|
|
will have to do something like:
|
|
|
|
(jupyter-repl-insert :read-only nil \"<editable text>\")
|
|
|
|
|
|
|
|
- `:properties' :: A list of text properties and their values to
|
|
|
|
be added to the inserted text. This defaults to an empty list.
|
|
|
|
|
|
|
|
- `:inherit-properties' :: A non-nil value will use
|
|
|
|
`insert-and-inherit' instead of `insert' for the function used
|
|
|
|
to insert the text. This is nil by default."
|
2017-12-23 15:34:28 -06:00
|
|
|
(let ((arg nil)
|
|
|
|
(read-only t)
|
2017-12-27 21:13:23 -06:00
|
|
|
(properties nil)
|
|
|
|
(insert-fun #'insert))
|
2017-12-23 15:34:28 -06:00
|
|
|
(while (keywordp (setq arg (car args)))
|
2017-12-27 21:13:23 -06:00
|
|
|
(cl-case arg
|
|
|
|
(:read-only (setq read-only (cadr args)))
|
|
|
|
(:properties (setq properties (cadr args)))
|
|
|
|
(:inherit-properties
|
|
|
|
(setq insert-fun (if (cadr args) #'insert-and-inherit #'insert)))
|
|
|
|
(otherwise
|
2018-01-08 22:31:33 -06:00
|
|
|
(error "Keyword not one of `:read-only', `:properties', `:inherit-properties' (`%s')" arg)))
|
2017-12-23 15:34:28 -06:00
|
|
|
(setq args (cddr args)))
|
2018-01-07 14:09:39 -06:00
|
|
|
(setq properties (append (when read-only '(read-only t))
|
|
|
|
properties))
|
2017-12-27 21:13:23 -06:00
|
|
|
(apply insert-fun (mapcar (lambda (s)
|
|
|
|
(prog1 s
|
|
|
|
(when properties
|
|
|
|
(add-text-properties
|
|
|
|
0 (length s) properties s))))
|
|
|
|
args))))
|
|
|
|
|
2017-12-27 22:17:38 -06:00
|
|
|
(defun jupyter-repl-newline ()
|
2018-01-04 17:21:59 -06:00
|
|
|
"Insert a read-only newline into the `current-buffer'."
|
2017-12-27 22:17:38 -06:00
|
|
|
(jupyter-repl-insert "\n"))
|
|
|
|
|
|
|
|
(defun jupyter-repl-insert-html (html)
|
2018-01-04 17:21:59 -06:00
|
|
|
"Parse and insert the HTML string using `shr-insert-document'."
|
2018-01-12 18:39:27 -06:00
|
|
|
(jupyter-repl-insert
|
|
|
|
;; `save-excursion' is necessary here since it seems that `with-temp-buffer'
|
|
|
|
;; moves the REPL window's `point' when it is visible
|
|
|
|
(save-excursion
|
2017-12-27 22:17:38 -06:00
|
|
|
(with-temp-buffer
|
|
|
|
(insert html)
|
2018-01-12 18:39:27 -06:00
|
|
|
(let ((xml (libxml-parse-html-region
|
|
|
|
(point-min) (point-max))))
|
|
|
|
(erase-buffer)
|
|
|
|
(shr-insert-document xml))
|
2018-01-17 21:23:52 -06:00
|
|
|
(jupyter-repl-fixup-font-lock-properties)
|
2018-01-12 18:39:27 -06:00
|
|
|
(string-trim (buffer-string))))))
|
|
|
|
|
2018-01-12 18:39:45 -06:00
|
|
|
(defun jupyter-repl-insert-markdown (text)
|
2018-01-17 21:24:29 -06:00
|
|
|
"Insert TEXT, fontifying it using `markdown-mode' first."
|
2018-01-12 18:39:45 -06:00
|
|
|
(jupyter-repl-insert
|
2018-01-17 21:24:29 -06:00
|
|
|
(let ((markdown-hide-markup t)
|
|
|
|
(markdown-hide-urls t)
|
|
|
|
(markdown-fontify-code-blocks-natively t))
|
|
|
|
(jupyter-repl-fontify-according-to-mode 'markdown-mode text))))
|
2017-12-27 22:17:38 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-insert-latex (tex)
|
2018-01-04 17:21:59 -06:00
|
|
|
"Generate and insert a LaTeX image based on TEX.
|
|
|
|
|
|
|
|
Note that this uses `org-format-latex' to generate the LaTeX
|
|
|
|
image."
|
2017-12-27 22:17:38 -06:00
|
|
|
(require 'org)
|
2018-01-17 21:25:16 -06:00
|
|
|
;; 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)
|
2017-12-27 22:17:38 -06:00
|
|
|
(setq beg (point))
|
2018-01-08 22:51:17 -06:00
|
|
|
(jupyter-repl-insert tex)
|
2017-12-27 22:17:38 -06:00
|
|
|
(setq end (point))
|
|
|
|
(org-format-latex
|
|
|
|
"jupyter-repl" beg end "jupyter-repl"
|
|
|
|
'overlays "Creating LaTeX image...%s"
|
|
|
|
'forbuffer
|
|
|
|
;; Use the default method for creating image files
|
2018-01-17 21:25:16 -06:00
|
|
|
org-preview-latex-default-process)
|
|
|
|
(goto-char end)))
|
2017-12-27 22:17:38 -06:00
|
|
|
|
2018-01-16 11:17:10 -06:00
|
|
|
(defun jupyter-repl-insert-ansi-coded-text (text)
|
|
|
|
"Insert TEXT, converting ANSI color codes to font lock faces."
|
|
|
|
(setq text (xterm-color-filter text))
|
|
|
|
(jupyter-repl-add-font-lock-properties 0 (length text) text)
|
|
|
|
(jupyter-repl-insert text))
|
|
|
|
|
2017-12-31 09:37:56 -06:00
|
|
|
(defun jupyter-repl-insert-data (data)
|
2018-01-18 16:35:16 -06:00
|
|
|
(let ((mimetypes (cl-loop
|
|
|
|
with graphic-types = '(:image/png :image/svg+xml :text/latex)
|
|
|
|
for (k d) on data by #'cddr
|
|
|
|
when (and d (not (equal d ""))
|
|
|
|
(or (display-graphic-p)
|
|
|
|
(not (memq k graphics-types))))
|
|
|
|
collect k)))
|
2017-12-31 09:37:56 -06:00
|
|
|
(cond
|
|
|
|
((memq :image/png mimetypes)
|
|
|
|
(insert-image
|
|
|
|
(create-image
|
|
|
|
(base64-decode-string
|
|
|
|
(plist-get data :image/png))
|
|
|
|
nil 'data)
|
|
|
|
(propertize " " 'read-only t)))
|
2018-01-04 23:19:49 -06:00
|
|
|
((and (memq :image/svg+xml mimetypes) (image-type-available-p 'svg))
|
|
|
|
(insert-image
|
|
|
|
(create-image
|
|
|
|
(plist-get data :image/svg+xml) 'svg)
|
|
|
|
(propertize " " 'read-only t)))
|
2017-12-31 09:37:56 -06:00
|
|
|
((memq :text/html mimetypes)
|
2018-01-12 18:39:27 -06:00
|
|
|
(let ((html (plist-get data :text/html)))
|
|
|
|
(when (string-match-p "^<img" html)
|
|
|
|
(jupyter-repl-newline))
|
|
|
|
(jupyter-repl-insert-html html)
|
|
|
|
(jupyter-repl-newline)))
|
2017-12-31 09:37:56 -06:00
|
|
|
((memq :text/latex mimetypes)
|
2018-01-17 21:25:16 -06:00
|
|
|
(jupyter-repl-insert-latex (plist-get data :text/latex))
|
|
|
|
(jupyter-repl-newline))
|
2018-01-13 23:00:45 -06:00
|
|
|
((and (memq :text/markdown mimetypes) (require 'markdown-mode nil t))
|
2018-01-12 18:39:45 -06:00
|
|
|
(jupyter-repl-insert-markdown (plist-get data :text/markdown)))
|
2017-12-31 09:37:56 -06:00
|
|
|
((memq :text/plain mimetypes)
|
2018-01-16 11:17:10 -06:00
|
|
|
(jupyter-repl-insert-ansi-coded-text
|
|
|
|
(plist-get data :text/plain))
|
|
|
|
(jupyter-repl-newline))
|
|
|
|
(t (warn "No supported mimetype found %s" mimetypes)))))
|
2017-12-31 09:37:56 -06:00
|
|
|
|
2017-12-27 21:13:23 -06:00
|
|
|
;;; Prompt
|
|
|
|
|
2017-12-31 10:09:45 -06:00
|
|
|
(defun jupyter-repl--prompt-display-value (str face)
|
2018-01-13 23:20:50 -06:00
|
|
|
"Return the margin display value for a prompt.
|
|
|
|
STR is the string used for the display value and FACE is the
|
|
|
|
`font-lock-face' to use for STR."
|
2017-12-31 10:09:45 -06:00
|
|
|
(list '(margin left-margin)
|
|
|
|
(propertize
|
|
|
|
(concat (make-string
|
|
|
|
(- jupyter-repl-prompt-margin-width
|
|
|
|
(length str))
|
|
|
|
? )
|
|
|
|
str)
|
|
|
|
'fontified t
|
|
|
|
'font-lock-face face)))
|
|
|
|
|
|
|
|
(defun jupyter-repl--insert-prompt (str face)
|
2018-01-13 23:20:50 -06:00
|
|
|
"Insert a new prompt at `point'.
|
|
|
|
STR is the prompt string displayed in the `left-margin' using
|
|
|
|
FACE as the `font-lock-face'. A newline is inserted before adding
|
|
|
|
the prompt. The prompt string is inserted as a `display' text
|
|
|
|
property in the `after-string' property of the overlay and the
|
|
|
|
overlay is added to the newline character just inserted."
|
2017-12-31 10:09:45 -06:00
|
|
|
(jupyter-repl-newline)
|
|
|
|
(let ((ov (make-overlay (1- (point)) (point) nil t))
|
|
|
|
(md (jupyter-repl--prompt-display-value str face)))
|
|
|
|
(overlay-put ov 'after-string (propertize " " 'display md))
|
|
|
|
(overlay-put ov 'evaporate t)
|
|
|
|
ov))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-insert-prompt (&optional type)
|
|
|
|
"Insert a REPL promp in CLIENT's buffer according to type.
|
|
|
|
If TYPE is nil or `in' insert a new input prompt. If TYPE is
|
|
|
|
`out' insert a new output prompt."
|
|
|
|
(setq type (or type 'in))
|
2017-12-31 10:09:45 -06:00
|
|
|
(unless (memq type '(in out continuation))
|
|
|
|
(error "Prompt type can only be (`in', `out', or `continuation')"))
|
|
|
|
(let ((inhibit-read-only t)
|
|
|
|
(inhibit-modification-hooks t)
|
|
|
|
ov props)
|
2017-12-23 15:34:28 -06:00
|
|
|
(cond
|
|
|
|
((eq type 'in)
|
2017-12-31 10:09:45 -06:00
|
|
|
(let ((count (oref jupyter-repl-current-client execution-count)))
|
|
|
|
(setq ov (jupyter-repl--insert-prompt
|
|
|
|
(format "In [%d]:" count) 'jupyter-repl-input-prompt)
|
2018-01-18 22:29:26 -06:00
|
|
|
props (list 'jupyter-cell (list 'beginning count))))
|
|
|
|
;; Insertion of an invisible character is to prevent the prompt overlay
|
|
|
|
;; from inheriting the text properties of code at the beginning of a
|
|
|
|
;; cell similarly for the output prompt.
|
|
|
|
;;
|
|
|
|
;; The front-sticky property is so that `point' will not get trapped in
|
|
|
|
;; the middle of the newline inserted by `jupyter-repl--insert-prompt'
|
|
|
|
;; and the invisible character.
|
|
|
|
;;
|
|
|
|
;; Finally the field property is so that text motions will stop at the
|
|
|
|
;; start of the code for a cell instead of moving past this invisible
|
|
|
|
;; character.
|
|
|
|
(jupyter-repl-insert
|
|
|
|
:properties '(invisible t rear-nonsticky t front-sticky t field t) " "))
|
2017-12-23 15:34:28 -06:00
|
|
|
((eq type 'out)
|
2018-01-07 14:06:14 -06:00
|
|
|
;; Output is normally inserted by first going to the end of the output
|
|
|
|
;; for the request. The end of the ouput for a request is at the
|
|
|
|
;; beginning of the next cell after the request which is why `escape' is
|
|
|
|
;; needed here.
|
|
|
|
(let ((count (jupyter-repl-cell-count 'escape)))
|
2017-12-31 10:09:45 -06:00
|
|
|
(setq ov (jupyter-repl--insert-prompt
|
|
|
|
(format "Out [%d]:" count) 'jupyter-repl-output-prompt)
|
2018-01-18 22:29:26 -06:00
|
|
|
props (list 'jupyter-cell (list 'out count))))
|
|
|
|
;; Prevent the overlay from inheriting text properties
|
|
|
|
(jupyter-repl-insert
|
|
|
|
:properties '(invisible t) " "))
|
2017-12-27 21:13:23 -06:00
|
|
|
((eq type 'continuation)
|
2017-12-31 10:09:45 -06:00
|
|
|
(setq ov (jupyter-repl--insert-prompt
|
|
|
|
":" 'jupyter-repl-input-prompt)
|
|
|
|
props (list 'read-only nil 'rear-nonsticky t))))
|
|
|
|
(add-text-properties (overlay-start ov) (overlay-end ov) props)))
|
|
|
|
|
2018-01-04 17:22:30 -06:00
|
|
|
(defun jupyter-repl-cell-update-prompt (str)
|
2018-01-13 23:20:50 -06:00
|
|
|
"Update the current cell's input prompt.
|
|
|
|
STR is the replacement prompt string."
|
2017-12-31 10:09:45 -06:00
|
|
|
(let ((ov (car (overlays-at (jupyter-repl-cell-beginning-position)))))
|
|
|
|
(when ov
|
|
|
|
(overlay-put ov 'after-string
|
|
|
|
(propertize
|
|
|
|
" " 'display (jupyter-repl--prompt-display-value
|
|
|
|
str 'jupyter-repl-input-prompt))))))
|
|
|
|
|
2018-01-04 17:22:30 -06:00
|
|
|
(defun jupyter-repl-cell-mark-busy ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Mark the current cell as busy.
|
|
|
|
The changes the current input prompt to \"In [*]:\""
|
2018-01-04 17:22:30 -06:00
|
|
|
(jupyter-repl-cell-update-prompt "In [*]:"))
|
2017-12-31 10:09:45 -06:00
|
|
|
|
2018-01-04 17:22:30 -06:00
|
|
|
(defun jupyter-repl-cell-unmark-busy ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Un-mark the current cell as busy.
|
|
|
|
This changes the current input prompt to \"In [N]:\" where N is
|
|
|
|
the execution count of the cell."
|
2018-01-04 17:22:30 -06:00
|
|
|
(jupyter-repl-cell-update-prompt
|
2017-12-31 10:09:45 -06:00
|
|
|
(format "In [%d]:" (jupyter-repl-cell-count))))
|
|
|
|
|
2018-01-07 14:06:14 -06:00
|
|
|
(defun jupyter-repl-cell-count (&optional escape)
|
|
|
|
"Get the cell count of the current cell at `point'.
|
|
|
|
If ESCAPE is non-nil and `point' is already at the beginning of a
|
|
|
|
cell, return the cell count of the cell before the current one."
|
|
|
|
(let ((pos (if (and (not escape) (jupyter-repl-cell-beginning-p)) (point)
|
2017-12-31 10:09:45 -06:00
|
|
|
(save-excursion
|
|
|
|
(jupyter-repl-previous-cell)
|
|
|
|
(point)))))
|
|
|
|
(nth 1 (get-text-property pos 'jupyter-cell))))
|
2017-12-27 21:25:29 -06:00
|
|
|
|
2017-12-31 10:11:50 -06:00
|
|
|
(defun jupyter-repl-cell-request ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Get the `jupyter-request' of the current cell."
|
2017-12-31 10:11:50 -06:00
|
|
|
(get-text-property (jupyter-repl-cell-beginning-position) 'jupyter-request))
|
|
|
|
|
2017-12-27 21:25:29 -06:00
|
|
|
;;; Cell motions
|
|
|
|
|
|
|
|
(defun jupyter-repl-cell-beginning-position ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Return the cell beginning position of the current cell.
|
|
|
|
If `point' is already at the beginning of the current cell,
|
|
|
|
return `point'. Note that if the end of a cell is found before
|
|
|
|
the beginning of a cell, i.e. when `point' is somewhere inside
|
|
|
|
the output of a cell, raise an error. If the beginning of the
|
|
|
|
buffer is found before the beginning of a cell, raise a
|
|
|
|
`beginning-of-buffer' error."
|
2017-12-27 21:25:29 -06:00
|
|
|
(let ((pos (point)))
|
2017-12-31 10:12:50 -06:00
|
|
|
(while (not (jupyter-repl-cell-beginning-p pos))
|
2018-01-17 21:27:04 -06:00
|
|
|
(setq pos (previous-single-property-change pos 'jupyter-cell))
|
2017-12-31 10:12:50 -06:00
|
|
|
(if pos (when (jupyter-repl-cell-end-p pos)
|
2018-01-08 22:31:33 -06:00
|
|
|
(error "Found end of previous cell"))
|
2017-12-27 21:25:29 -06:00
|
|
|
(signal 'beginning-of-buffer nil)))
|
|
|
|
pos))
|
|
|
|
|
|
|
|
(defun jupyter-repl-cell-end-position ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Return the cell ending position of the current cell.
|
|
|
|
This is similar to `jupyter-repl-cell-beginning-position' except
|
|
|
|
the position at the end of the current cell is returned and an
|
|
|
|
error is raised if the beginning of a cell is found before an
|
|
|
|
end. Note that if the current cell is the last cell in the
|
|
|
|
buffer, `point-max' is considered the end of the cell."
|
2017-12-27 21:25:29 -06:00
|
|
|
(let ((pos (point)))
|
|
|
|
(catch 'unfinalized
|
2017-12-31 10:12:50 -06:00
|
|
|
(while (not (jupyter-repl-cell-end-p pos))
|
2017-12-27 21:25:29 -06:00
|
|
|
(setq pos (next-single-property-change pos 'jupyter-cell))
|
2017-12-31 10:12:50 -06:00
|
|
|
(if pos (when (jupyter-repl-cell-beginning-p pos)
|
2018-01-08 22:31:33 -06:00
|
|
|
(error "Found beginning of next cell"))
|
2017-12-31 10:12:50 -06:00
|
|
|
;; Any unfinalized cell must be at the end of the buffer.
|
2017-12-27 21:25:29 -06:00
|
|
|
(throw 'unfinalized (point-max))))
|
|
|
|
pos)))
|
|
|
|
|
|
|
|
(defun jupyter-repl-cell-code-beginning-position ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Return the beginning of the current cell's code.
|
|
|
|
The code beginning position is
|
|
|
|
|
2018-01-18 22:29:26 -06:00
|
|
|
`jupyter-repl-cell-beginning-position' + 2
|
|
|
|
|
|
|
|
There is an extra invisible character after the prompt."
|
|
|
|
(+ (jupyter-repl-cell-beginning-position) 2))
|
2017-12-27 21:25:29 -06:00
|
|
|
|
2017-12-31 10:09:45 -06:00
|
|
|
(defun jupyter-repl-cell-code-end-position ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Return the end of the current cell's code.
|
|
|
|
The code ending position is
|
|
|
|
|
2018-01-16 11:34:22 -06:00
|
|
|
`jupyter-repl-cell-end-position' - 1
|
|
|
|
|
|
|
|
In the case of the last cell in the REPL buffer, i.e. an
|
|
|
|
unfinalized cell, the code ending position is `point-max'."
|
2017-12-31 10:09:45 -06:00
|
|
|
(let ((pos (jupyter-repl-cell-end-position)))
|
|
|
|
(if (= pos (point-max)) (point-max)
|
|
|
|
(1- pos))))
|
2017-12-27 21:25:29 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-next-cell (&optional N)
|
|
|
|
"Go to the start of the next cell.
|
2018-01-08 18:11:08 -06:00
|
|
|
Optional argument N is the number of times to move to the next
|
|
|
|
cell. N defaults to 1."
|
2018-01-16 11:11:30 -06:00
|
|
|
(or N (setq N 1))
|
|
|
|
(catch 'done
|
|
|
|
(while (> N 0)
|
|
|
|
(let ((pos (next-single-property-change (point) 'jupyter-cell)))
|
|
|
|
(while (and pos (not (jupyter-repl-cell-beginning-p pos)))
|
|
|
|
(setq pos (next-single-property-change pos 'jupyter-cell)))
|
|
|
|
(unless (when pos (goto-char pos) (setq N (1- N)))
|
|
|
|
(goto-char (point-max))
|
|
|
|
(throw 'done t)))))
|
|
|
|
N)
|
2017-12-27 21:25:29 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-previous-cell (&optional N)
|
2018-01-18 16:40:27 -06:00
|
|
|
"Go to the start of the current of previous cell.
|
|
|
|
If `point' is already at the start of the current cell, go to the
|
|
|
|
start of the previous cell. Otherwise go to the start of the
|
|
|
|
current cell. Optional argument N is the number of times to move
|
|
|
|
to the previous cell. N defaults to 1."
|
2018-01-16 11:11:30 -06:00
|
|
|
(or N (setq N 1))
|
|
|
|
(catch 'done
|
|
|
|
(while (> N 0)
|
|
|
|
(let ((pos (previous-single-property-change (point) 'jupyter-cell)))
|
|
|
|
(while (and pos (not (jupyter-repl-cell-beginning-p pos)))
|
|
|
|
(setq pos (previous-single-property-change pos 'jupyter-cell)))
|
|
|
|
(unless (when pos (goto-char pos) (setq N (1- N)))
|
|
|
|
(goto-char (point-min))
|
|
|
|
(throw 'done t)))))
|
|
|
|
N)
|
2018-01-13 23:02:12 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-goto-cell (req)
|
|
|
|
"Go to the cell beginning position of REQ.
|
|
|
|
REQ should be a `jupyter-request' that corresponds to one of the
|
|
|
|
`jupyter-execute-request's created by a cell in the
|
|
|
|
`current-buffer'. Note that the `current-buffer' is assumed to be
|
|
|
|
a Jupyter REPL buffer."
|
|
|
|
(goto-char (point-max))
|
2018-01-17 21:29:54 -06:00
|
|
|
(jupyter-repl-previous-cell)
|
|
|
|
(when (catch 'not-found
|
|
|
|
(while (not (eq (jupyter-repl-cell-request) req))
|
|
|
|
(jupyter-repl-previous-cell)
|
|
|
|
(when (= (point) (point-min))
|
|
|
|
(throw 'not-found t))))
|
|
|
|
(error "Cell for request not found")))
|
2018-01-13 23:02:12 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-forward-cell (&optional arg)
|
|
|
|
"Move to the code beginning of the cell after the current one.
|
|
|
|
ARG is the number of cells to move and defaults to 1."
|
|
|
|
(interactive "^p")
|
|
|
|
(or arg (setq arg 1))
|
|
|
|
(jupyter-repl-next-cell arg)
|
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position)))
|
|
|
|
|
|
|
|
(defun jupyter-repl-backward-cell (&optional arg)
|
|
|
|
"Move to the code beginning of the cell before the current one.
|
|
|
|
ARG is the number of cells to move and defaults to 1."
|
|
|
|
(interactive "^p")
|
|
|
|
(or arg (setq arg 1))
|
2018-01-17 21:29:54 -06:00
|
|
|
(goto-char (jupyter-repl-cell-beginning-position))
|
2018-01-13 23:02:12 -06:00
|
|
|
(jupyter-repl-previous-cell arg)
|
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position)))
|
2017-12-27 21:25:29 -06:00
|
|
|
|
2018-01-16 11:34:22 -06:00
|
|
|
;;; Predicates
|
2017-12-27 21:25:29 -06:00
|
|
|
|
2017-12-31 10:09:45 -06:00
|
|
|
(defun jupyter-repl-cell-beginning-p (&optional pos)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Is POS the beginning of a cell?
|
|
|
|
POS defaults to `point'."
|
2017-12-31 10:09:45 -06:00
|
|
|
(setq pos (or pos (point)))
|
|
|
|
(eq (nth 0 (get-text-property pos 'jupyter-cell)) 'beginning))
|
2017-12-27 21:25:29 -06:00
|
|
|
|
2017-12-31 10:09:45 -06:00
|
|
|
(defun jupyter-repl-cell-end-p (&optional pos)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Is POS the end of a cell?
|
|
|
|
POS defaults to `point'."
|
2017-12-31 10:09:45 -06:00
|
|
|
(setq pos (or pos (point)))
|
|
|
|
(eq (nth 0 (get-text-property pos 'jupyter-cell)) 'end))
|
2017-12-27 21:25:29 -06:00
|
|
|
|
2017-12-31 10:09:45 -06:00
|
|
|
(defun jupyter-repl-multiline-p (text)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Is TEXT a multi-line string?"
|
2017-12-31 10:09:45 -06:00
|
|
|
(string-match "\n" text))
|
2017-12-27 21:25:29 -06:00
|
|
|
|
2017-12-31 10:09:45 -06:00
|
|
|
(defun jupyter-repl-cell-line-p ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Is the current line a cell input line?"
|
2018-01-17 21:30:39 -06:00
|
|
|
(let ((pos (point)))
|
|
|
|
(save-excursion
|
|
|
|
(unless (= (point) (jupyter-repl-cell-beginning-position))
|
|
|
|
(jupyter-repl-previous-cell))
|
|
|
|
(<= (jupyter-repl-cell-code-beginning-position)
|
|
|
|
pos
|
|
|
|
(jupyter-repl-cell-code-end-position)))))
|
2017-12-27 21:25:29 -06:00
|
|
|
|
2018-01-16 11:31:58 -06:00
|
|
|
(defun jupyter-repl-cell-finalized-p ()
|
|
|
|
"Has the current cell been finalized?
|
|
|
|
A cell is considered finalized when `jupyter-repl-finalize-cell'
|
2018-01-18 16:40:27 -06:00
|
|
|
has been previously called for it. `jupyter-repl-finalize-cell'
|
|
|
|
is responsible for adding the text properties which cause
|
|
|
|
`jupyter-repl-cell-end-p' to return non-nil."
|
|
|
|
(jupyter-repl-cell-end-p (jupyter-repl-cell-end-position)))
|
2018-01-16 11:31:58 -06:00
|
|
|
|
|
|
|
;;; Buffer text manipulation
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-cell-code ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Get the code of the current cell."
|
2017-12-23 15:34:28 -06:00
|
|
|
(if (= (point-min) (point-max)) ""
|
2017-12-27 21:46:15 -06:00
|
|
|
(let (lines)
|
|
|
|
(save-excursion
|
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position))
|
2018-01-18 16:36:46 -06:00
|
|
|
(push (buffer-substring-no-properties (point) (point-at-eol))
|
2017-12-31 10:09:45 -06:00
|
|
|
lines)
|
2017-12-27 21:46:15 -06:00
|
|
|
(while (and (line-move-1 1 'noerror)
|
2017-12-31 10:09:45 -06:00
|
|
|
(jupyter-repl-cell-line-p))
|
2018-01-18 16:36:46 -06:00
|
|
|
(push (buffer-substring-no-properties (point-at-bol) (point-at-eol)) lines))
|
2017-12-31 10:09:45 -06:00
|
|
|
(mapconcat #'identity (nreverse lines) "\n")))))
|
2017-12-27 21:46:15 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-cell-code-position ()
|
2017-12-31 10:09:45 -06:00
|
|
|
"Get the position that `point' is at relative to the contents of the cell.
|
|
|
|
The first character of the cell code corresponds to position 1."
|
|
|
|
(unless (jupyter-repl-cell-line-p)
|
2018-01-08 22:31:33 -06:00
|
|
|
(error "Not in code of cell"))
|
2017-12-31 10:09:45 -06:00
|
|
|
(- (point) (jupyter-repl-cell-beginning-position)))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2017-12-28 09:45:03 -06:00
|
|
|
(defun jupyter-repl-finalize-cell (req)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Finalize the current cell.
|
|
|
|
REQ is the `jupyter-request' to associate with the current cell.
|
|
|
|
Finalizing a cell involves the following steps:
|
|
|
|
|
|
|
|
- Associate REQ with the cell
|
|
|
|
- Move `point' to the location where the next input cell can be
|
|
|
|
inserted
|
|
|
|
- Add the text property which marks the end of a cell
|
|
|
|
- Make the cell read-only"
|
2018-01-13 02:42:29 -06:00
|
|
|
(let ((beg (jupyter-repl-cell-beginning-position))
|
|
|
|
(count (jupyter-repl-cell-count)))
|
|
|
|
(remove-text-properties beg (1+ beg) '(rear-nonsticky))
|
2017-12-31 10:09:45 -06:00
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-newline)
|
|
|
|
(add-text-properties
|
|
|
|
(1- (point)) (point) (list 'jupyter-cell (list 'end count)))
|
|
|
|
(add-text-properties beg (1+ beg) (list 'jupyter-request req))
|
2017-12-27 21:25:29 -06:00
|
|
|
;; font-lock-multiline to avoid improper syntactic elements from
|
|
|
|
;; spilling over to the rest of the buffer.
|
2017-12-31 10:09:45 -06:00
|
|
|
(add-text-properties beg (point) '(read-only t font-lock-multiline t))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-replace-cell-code (new-code)
|
2018-01-08 17:17:18 -06:00
|
|
|
"Replace the current cell code with NEW-CODE."
|
2018-01-13 23:18:15 -06:00
|
|
|
;; Prevent wrapping with `inhibit-read-only' so that an error is thrown when
|
|
|
|
;; trying to replace a finalized cell.
|
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position))
|
|
|
|
(delete-region (point) (jupyter-repl-cell-code-end-position))
|
2018-01-08 17:17:18 -06:00
|
|
|
(jupyter-repl-insert :read-only nil new-code))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-truncate-buffer ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Truncate the `current-buffer' based on `jupyter-repl-maximum-size'.
|
|
|
|
The `current-buffer' is assumed to be a Jupyter REPL buffer. If
|
|
|
|
the `current-buffer' is larger than `jupyter-repl-maximum-size'
|
|
|
|
lines then truncate it to something less than
|
|
|
|
`jupyter-repl-maximum-size' lines."
|
2017-12-31 11:48:44 -06:00
|
|
|
(save-excursion
|
|
|
|
(when (= (forward-line (- jupyter-repl-maximum-size)) 0)
|
|
|
|
(jupyter-repl-next-cell)
|
|
|
|
(delete-region (point-min) (point)))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2017-12-27 21:55:58 -06:00
|
|
|
;;; Handlers
|
|
|
|
|
2017-12-31 10:16:41 -06:00
|
|
|
(defun jupyter-repl-history-add-input (code)
|
2018-01-13 23:09:13 -06:00
|
|
|
"Add CODE as the newest element in the REPL history."
|
|
|
|
;; Ensure the newest element is actually the newest element and not the most
|
|
|
|
;; recently navigated history element.
|
|
|
|
(while (not (eq (ring-ref jupyter-repl-history -1) 'jupyter-repl-history))
|
|
|
|
(ring-insert jupyter-repl-history (ring-remove jupyter-repl-history)))
|
|
|
|
;; Remove the second to last element when the ring is full to preserve the
|
|
|
|
;; sentinel.
|
|
|
|
(when (eq (ring-length jupyter-repl-history)
|
|
|
|
(ring-size jupyter-repl-history))
|
|
|
|
(ring-remove jupyter-repl-history -2))
|
|
|
|
(ring-remove+insert+extend jupyter-repl-history code))
|
2017-12-31 10:16:41 -06:00
|
|
|
|
2017-12-31 15:19:23 -06:00
|
|
|
(cl-defmethod jupyter-execute-request ((client jupyter-repl-client)
|
2017-12-23 15:34:28 -06:00
|
|
|
&key code
|
|
|
|
(silent nil)
|
|
|
|
(store-history t)
|
2017-12-27 21:52:19 -06:00
|
|
|
(user-expressions nil)
|
2017-12-23 15:34:28 -06:00
|
|
|
(allow-stdin t)
|
|
|
|
(stop-on-error nil))
|
2017-12-27 21:52:19 -06:00
|
|
|
(with-jupyter-repl-buffer client
|
2017-12-28 09:45:03 -06:00
|
|
|
(jupyter-repl-truncate-buffer)
|
2017-12-31 10:09:45 -06:00
|
|
|
(if code (cl-call-next-method)
|
|
|
|
(setq code (jupyter-repl-cell-code))
|
|
|
|
;; Handle empty code cells as just an update of the prompt number
|
|
|
|
(if (= (length code) 0)
|
|
|
|
(setq silent t)
|
|
|
|
;; Needed by the prompt insertion below
|
2017-12-31 11:49:18 -06:00
|
|
|
(oset client execution-count (1+ (oref client execution-count)))
|
|
|
|
(jupyter-repl-history-add-input code))
|
2017-12-31 10:09:45 -06:00
|
|
|
(let ((req (cl-call-next-method
|
|
|
|
client :code code :silent silent :store-history store-history
|
|
|
|
:user-expressions user-expressions :allow-stdin allow-stdin
|
2018-01-13 23:07:07 -06:00
|
|
|
:stop-on-error stop-on-error)))
|
|
|
|
(jupyter-repl-without-continuation-prompts
|
2018-01-16 11:30:11 -06:00
|
|
|
(jupyter-repl-cell-mark-busy)
|
2018-01-13 23:07:07 -06:00
|
|
|
(jupyter-repl-finalize-cell req)
|
|
|
|
(jupyter-repl-insert-prompt 'in))
|
2017-12-31 10:09:45 -06:00
|
|
|
req))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-02 00:38:46 -06:00
|
|
|
(cl-defmethod jupyter-handle-execute-reply ((client jupyter-repl-client)
|
|
|
|
req
|
|
|
|
execution-count
|
|
|
|
user-expressions
|
|
|
|
payload)
|
2017-12-28 09:45:03 -06:00
|
|
|
(oset client execution-count (1+ execution-count))
|
|
|
|
(with-jupyter-repl-buffer client
|
2018-01-12 18:43:02 -06:00
|
|
|
(save-excursion
|
|
|
|
(jupyter-repl-goto-cell req)
|
2018-01-13 23:16:04 -06:00
|
|
|
(jupyter-repl-cell-unmark-busy)
|
|
|
|
(when payload
|
|
|
|
(cl-loop
|
|
|
|
for pl in payload
|
|
|
|
do (pcase (plist-get pl :source)
|
|
|
|
("page"
|
2018-01-14 14:32:33 -06:00
|
|
|
(let ((text (plist-get (plist-get pl :data) :text/plain))
|
|
|
|
(line (or (plist-get pl :start) 0))))
|
|
|
|
(with-jupyter-repl-doc-buffer "pager"
|
2018-01-17 21:33:20 -06:00
|
|
|
(jupyter-repl-insert-ansi-coded-text text)
|
2018-01-14 14:32:33 -06:00
|
|
|
(goto-char (point-min))
|
|
|
|
(forward-line line)
|
2018-01-18 22:02:04 -06:00
|
|
|
(display-buffer (current-buffer))))
|
2018-01-13 23:16:04 -06:00
|
|
|
((or "edit" "edit_magic")
|
|
|
|
(with-current-buffer (find-file-other-window
|
|
|
|
(plist-get pl :filename))
|
2018-01-18 22:02:04 -06:00
|
|
|
(forward-line (plist-get pl :line_number))
|
|
|
|
(set-window-start (selected-window) (point))))
|
2018-01-13 23:16:04 -06:00
|
|
|
("set_next_input"
|
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-previous-cell)
|
2018-01-18 22:02:04 -06:00
|
|
|
(jupyter-repl-replace-cell-code (plist-get pl :text)))))))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-execute-input ((client jupyter-repl-client)
|
|
|
|
req
|
|
|
|
code
|
|
|
|
execution-count)
|
|
|
|
(oset client execution-count (1+ execution-count)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-execute-result ((client jupyter-repl-client)
|
|
|
|
req
|
|
|
|
execution-count
|
|
|
|
data
|
|
|
|
metadata)
|
2017-12-31 10:15:19 -06:00
|
|
|
(jupyter-repl-do-at-request client req
|
|
|
|
(jupyter-repl-insert-prompt 'out)
|
|
|
|
(jupyter-repl-insert-data data)))
|
2017-12-31 09:37:56 -06:00
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-display-data ((client jupyter-repl-client)
|
|
|
|
req data metadata transient)
|
2017-12-31 10:15:19 -06:00
|
|
|
(jupyter-repl-do-at-request client req
|
|
|
|
(jupyter-repl-insert-data data)))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-17 21:22:12 -06:00
|
|
|
(cl-defmethod jupyter-handle-status ((client jupyter-repl-client) req execution-state)
|
|
|
|
(oset client execution-state execution-state))
|
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
(cl-defmethod jupyter-handle-stream ((client jupyter-repl-client) req name text)
|
2018-01-18 16:08:43 -06:00
|
|
|
(if req
|
|
|
|
(jupyter-repl-do-at-request client req
|
|
|
|
(jupyter-repl-insert-ansi-coded-text text)
|
|
|
|
(jupyter-repl-newline))
|
|
|
|
;; Otherwise the stream request is due to someone else, pop up a buffer.
|
|
|
|
;; TODO: Make this configurable so that we can just ignore output.
|
|
|
|
(let* ((bname (buffer-name (oref client buffer)))
|
|
|
|
(inhibit-read-only t)
|
|
|
|
(stream-buffer
|
|
|
|
(concat (substring bname 0 (1- (length bname)))
|
|
|
|
"-" name "*")))
|
|
|
|
(with-current-buffer (get-buffer-create stream-buffer)
|
|
|
|
(let ((pos (point)))
|
|
|
|
(jupyter-repl-insert-ansi-coded-text text)
|
|
|
|
(fill-region pos (point)))
|
|
|
|
(display-buffer (current-buffer) '(display-buffer-pop-up-window
|
|
|
|
(pop-up-windows . t)))))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-error ((client jupyter-repl-client)
|
2018-01-18 16:14:58 -06:00
|
|
|
req ename _evalue traceback)
|
|
|
|
;; When the request is from us
|
|
|
|
(when req
|
|
|
|
(jupyter-repl-do-at-request client req
|
|
|
|
(save-excursion
|
|
|
|
;; `point' is at the cell beginning of the next cell after REQ,
|
|
|
|
;; `jupyter-repl-previous-cell' will take us back to the start of the
|
|
|
|
;; cell corresponding to REQ.
|
|
|
|
(jupyter-repl-previous-cell)
|
|
|
|
(jupyter-repl-cell-unmark-busy))
|
|
|
|
(when traceback
|
|
|
|
(let ((pos (point)))
|
|
|
|
(jupyter-repl-insert-ansi-coded-text
|
|
|
|
(mapconcat #'identity traceback "\n"))
|
|
|
|
(when (eq jupyter-repl-lang-mode 'python-mode)
|
|
|
|
;; Fix spacing between error name and Traceback
|
|
|
|
(save-excursion
|
|
|
|
(goto-char pos)
|
|
|
|
(when (search-forward ename nil t)
|
|
|
|
(let ((len (- (length jupyter-repl-error-prefix)
|
|
|
|
(- (point) (line-beginning-position))
|
|
|
|
(- (line-end-position) (point)))))
|
|
|
|
(jupyter-repl-insert
|
|
|
|
(make-string (if (> len 4) len 4) ? )))))))
|
|
|
|
(jupyter-repl-newline)))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-02 00:38:46 -06:00
|
|
|
(cl-defmethod jupyter-handle-input-reply ((client jupyter-repl-client) req prompt password)
|
2017-12-31 10:15:19 -06:00
|
|
|
(jupyter-repl-do-at-request client req
|
2017-12-23 15:34:28 -06:00
|
|
|
(let ((value (cl-call-next-method)))
|
2018-01-14 13:58:31 -06:00
|
|
|
(jupyter-repl-insert (concat prompt value))
|
|
|
|
(jupyter-repl-newline))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-16 11:20:32 -06:00
|
|
|
(defun jupyter-repl-history-next (&optional n no-replace)
|
2018-01-14 00:03:31 -06:00
|
|
|
"Go to the next history element.
|
|
|
|
Navigate through the REPL history to the next (newer) history
|
|
|
|
element and insert it as the last code cell. For N positive move
|
|
|
|
forward in history that many times. If N is negative, move to
|
2018-01-16 11:20:32 -06:00
|
|
|
older history elements.
|
|
|
|
|
|
|
|
If NO-REPLACE is non-nil, don't insert the history element in the
|
|
|
|
REPL buffer."
|
2017-12-31 10:16:41 -06:00
|
|
|
(interactive "p")
|
2018-01-14 00:04:05 -06:00
|
|
|
(or n (setq n 1))
|
2018-01-16 11:20:32 -06:00
|
|
|
(if (< n 0) (jupyter-repl-history-previous (- n) no-replace)
|
2018-01-14 00:04:05 -06:00
|
|
|
(goto-char (point-max))
|
2018-01-18 22:39:02 -06:00
|
|
|
(when (cl-loop
|
|
|
|
repeat n
|
|
|
|
thereis (eq (ring-ref jupyter-repl-history -1) 'jupyter-repl-history)
|
|
|
|
do (ring-insert
|
|
|
|
jupyter-repl-history (ring-remove jupyter-repl-history -1)))
|
|
|
|
(cond
|
|
|
|
((equal (jupyter-repl-cell-code)
|
|
|
|
(ring-ref jupyter-repl-history 0))
|
|
|
|
(jupyter-repl-replace-cell-code "")
|
|
|
|
(setq no-replace t))
|
|
|
|
((equal (jupyter-repl-cell-code) "")
|
|
|
|
(error "End of history"))))
|
|
|
|
(unless no-replace
|
|
|
|
(jupyter-repl-replace-cell-code
|
|
|
|
(ring-ref jupyter-repl-history 0)))))
|
2018-01-14 00:04:05 -06:00
|
|
|
|
2018-01-16 11:20:32 -06:00
|
|
|
(defun jupyter-repl-history-previous (&optional n no-replace)
|
2018-01-14 00:03:31 -06:00
|
|
|
"Go to the previous history element.
|
|
|
|
Similar to `jupyter-repl-history-next' but for older history
|
|
|
|
elements. If N is negative in this case, move to newer history
|
2018-01-16 11:20:32 -06:00
|
|
|
elements.
|
|
|
|
|
|
|
|
If NO-REPLACE is non-nil, don't insert the history element in the
|
|
|
|
REPL buffer."
|
2017-12-31 10:16:41 -06:00
|
|
|
(interactive "p")
|
2018-01-14 00:04:05 -06:00
|
|
|
(or n (setq n 1))
|
2018-01-16 11:20:32 -06:00
|
|
|
(if (< n 0) (jupyter-repl-history-next (- n) no-replace)
|
2018-01-14 00:04:05 -06:00
|
|
|
(goto-char (point-max))
|
|
|
|
(if (not (equal (jupyter-repl-cell-code)
|
|
|
|
(ring-ref jupyter-repl-history 0)))
|
|
|
|
(jupyter-repl-replace-cell-code (ring-ref jupyter-repl-history 0))
|
|
|
|
(if (cl-loop
|
|
|
|
repeat n
|
|
|
|
thereis (eq (ring-ref jupyter-repl-history 1) 'jupyter-repl-history)
|
|
|
|
do (ring-insert-at-beginning
|
|
|
|
jupyter-repl-history (ring-remove jupyter-repl-history 0)))
|
2018-01-16 11:20:32 -06:00
|
|
|
(error "Beginning of history")
|
|
|
|
(unless no-replace
|
|
|
|
(jupyter-repl-replace-cell-code
|
|
|
|
(ring-ref jupyter-repl-history 0)))))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-02 00:38:46 -06:00
|
|
|
(cl-defmethod jupyter-handle-history-reply ((client jupyter-repl-client) req history)
|
2017-12-23 15:34:28 -06:00
|
|
|
(with-jupyter-repl-buffer client
|
2018-01-14 00:04:05 -06:00
|
|
|
(cl-loop for (_session _line-number input-output) in history
|
|
|
|
do (ring-remove+insert+extend jupyter-repl-history input-output))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-14 00:04:05 -06:00
|
|
|
(cl-defmethod jupyter-handle-is-complete-reply ((client jupyter-repl-client) _req status indent)
|
2017-12-23 15:34:28 -06:00
|
|
|
(with-jupyter-repl-buffer client
|
|
|
|
(pcase status
|
|
|
|
("complete"
|
2017-12-31 15:19:23 -06:00
|
|
|
(jupyter-execute-request client))
|
2017-12-23 15:34:28 -06:00
|
|
|
("incomplete"
|
2017-12-27 22:21:10 -06:00
|
|
|
(jupyter-repl-newline)
|
2017-12-31 11:39:32 -06:00
|
|
|
(if (= (length indent) 0) (jupyter-repl-indent-line)
|
|
|
|
(jupyter-repl-insert :read-only nil indent)))
|
2017-12-23 15:34:28 -06:00
|
|
|
("invalid"
|
|
|
|
;; Force an execute to produce a traceback
|
2017-12-31 15:19:23 -06:00
|
|
|
(jupyter-execute-request client))
|
2017-12-23 15:34:28 -06:00
|
|
|
("unknown"))))
|
|
|
|
|
2018-01-13 23:10:40 -06:00
|
|
|
(cl-defmethod jupyter-handle-shutdown-reply ((client jupyter-repl-client) _req restart)
|
|
|
|
(with-jupyter-repl-buffer client
|
|
|
|
(goto-char (point-max))
|
|
|
|
(add-text-properties (jupyter-repl-cell-beginning-position)
|
|
|
|
(jupyter-repl-cell-end-position)
|
|
|
|
'(read-only t))
|
|
|
|
(jupyter-repl-without-continuation-prompts
|
|
|
|
(jupyter-repl-newline)
|
|
|
|
(jupyter-repl-newline)
|
|
|
|
;; TODO: Add a slot mentioning that the kernel is shutdown so that we can
|
|
|
|
;; block sending requests or delay until it has restarted.
|
|
|
|
(jupyter-repl-insert
|
|
|
|
(propertize (concat "kernel " (if restart "restart" "shutdown"))
|
|
|
|
'font-lock-face 'warning))
|
|
|
|
(jupyter-repl-newline))))
|
|
|
|
|
2018-01-13 23:11:11 -06:00
|
|
|
(defun jupyter-repl-ret (&optional force)
|
|
|
|
"Send the current cell code to the kernel.
|
|
|
|
If `point' is before the last cell in the REPL buffer move to
|
|
|
|
`point-max', i.e. move to the last cell. Otherwise if `point' is
|
|
|
|
at some position within the last cell of the REPL buffer, either
|
|
|
|
insert a newline or ask the kernel to execute the cell code
|
|
|
|
depending on the kernel's response to an is_complete_request. If
|
|
|
|
FORCE is non-nil, force the kernel to execute the current cell
|
|
|
|
code without sending the is_complete_request. See
|
|
|
|
`jupyter-repl-use-builtin-is-complete' for yet another way to
|
|
|
|
execute the current cell."
|
2017-12-23 15:34:28 -06:00
|
|
|
(interactive "P")
|
2018-01-13 23:11:11 -06:00
|
|
|
(if (< (point) (save-excursion
|
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-cell-beginning-position)))
|
|
|
|
(goto-char (point-max))
|
2018-01-18 21:18:08 -06:00
|
|
|
(unless (or (and (jupyter-repl-client-has-manager-p)
|
|
|
|
(jupyter-kernel-alive-p
|
|
|
|
(oref jupyter-repl-current-client parent-instance)))
|
2018-01-17 21:02:56 -06:00
|
|
|
(jupyter-hb-beating-p
|
|
|
|
(oref jupyter-repl-current-client hb-channel)))
|
|
|
|
(error "Kernel not alive"))
|
2018-01-17 21:22:12 -06:00
|
|
|
;; NOTE: kernels allow execution requests to queue up, but we prevent
|
|
|
|
;; sending a request when the kernel is busy because of the is-complete
|
|
|
|
;; request. Some kernels don't respond to this request when the kernel is
|
|
|
|
;; busy.
|
|
|
|
(unless (member (oref jupyter-repl-current-client execution-state)
|
|
|
|
'("starting" "idle"))
|
|
|
|
(error "Kernel busy"))
|
2018-01-17 21:02:56 -06:00
|
|
|
(if force (jupyter-execute-request jupyter-repl-current-client)
|
2018-01-11 12:26:31 -06:00
|
|
|
(if (not jupyter-repl-use-builtin-is-complete)
|
2018-01-13 23:11:11 -06:00
|
|
|
(let ((res (jupyter-wait-until-received
|
|
|
|
:is-complete-reply
|
|
|
|
(jupyter-is-complete-request
|
|
|
|
jupyter-repl-current-client
|
2018-01-17 21:04:22 -06:00
|
|
|
:code (jupyter-repl-cell-code))
|
|
|
|
jupyter-repl-maximum-is-complete-timeout)))
|
2018-01-11 12:26:31 -06:00
|
|
|
(unless res
|
2018-01-13 23:11:11 -06:00
|
|
|
(message "Kernel did not respond to is-complete-request, using built-in is-complete")
|
2018-01-11 12:26:31 -06:00
|
|
|
(setq-local jupyter-repl-use-builtin-is-complete t)
|
2018-01-13 23:11:11 -06:00
|
|
|
(jupyter-repl-ret force)))
|
2018-01-17 21:05:50 -06:00
|
|
|
(goto-char (point-max))
|
|
|
|
(let ((complete-p (equal (buffer-substring
|
|
|
|
(line-beginning-position) (point))
|
|
|
|
"")))
|
2018-01-13 23:11:11 -06:00
|
|
|
(jupyter-handle-is-complete-reply
|
|
|
|
jupyter-repl-current-client
|
2018-01-11 12:26:31 -06:00
|
|
|
nil (if complete-p "complete" "incomplete") ""))))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2017-12-27 21:21:33 -06:00
|
|
|
(defun jupyter-repl-indent-line ()
|
|
|
|
"Indent the line according to the language of the REPL."
|
2018-01-16 11:21:10 -06:00
|
|
|
(let* ((spos (jupyter-repl-cell-code-beginning-position))
|
|
|
|
(pos (jupyter-repl-cell-code-position))
|
|
|
|
(code (jupyter-repl-cell-code))
|
|
|
|
(replacement (with-jupyter-repl-lang-buffer
|
|
|
|
(insert code)
|
|
|
|
(goto-char pos)
|
|
|
|
(indent-according-to-mode)
|
|
|
|
(setq pos (point))
|
|
|
|
(buffer-string))))
|
|
|
|
;; Don't modify the buffer when unnecessary, this allows
|
|
|
|
;; `company-indent-or-complete-common' to work.
|
|
|
|
(unless (equal code replacement)
|
|
|
|
(jupyter-repl-replace-cell-code replacement)
|
|
|
|
(goto-char (+ pos spos)))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2017-12-27 21:55:58 -06:00
|
|
|
;;; Buffer change functions
|
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
(defun jupyter-repl-after-buffer-change (beg end len)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Insert line continuation prompts in `jupyter-repl-mode' buffers.
|
|
|
|
BEG, END, and LEN have the same meaning as for
|
|
|
|
`after-change-functions'. If the change corresponds to text being
|
|
|
|
inserted and the beginning of the insertion is on a
|
|
|
|
`jupyter-repl-cell-line-p', insert line continuation prompts if
|
|
|
|
the inserted text is multi-line."
|
2017-12-23 15:34:28 -06:00
|
|
|
(when (eq major-mode 'jupyter-repl-mode)
|
|
|
|
(cond
|
2017-12-31 10:09:45 -06:00
|
|
|
;; Insertions only
|
|
|
|
((= len 0)
|
2017-12-27 21:17:46 -06:00
|
|
|
(goto-char beg)
|
2017-12-31 10:09:45 -06:00
|
|
|
(when (jupyter-repl-cell-line-p)
|
|
|
|
(while (search-forward "\n" end 'noerror)
|
|
|
|
(delete-char -1)
|
|
|
|
(jupyter-repl-insert-prompt 'continuation)))
|
2017-12-27 21:17:46 -06:00
|
|
|
(goto-char end)))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-kill-buffer-query-function ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Ask before killing a Jupyter REPL buffer.
|
|
|
|
If the REPL buffer is killed, stop the client and possibly the
|
|
|
|
kernel that the REPL buffer is connected to."
|
|
|
|
(not (and (eq major-mode 'jupyter-repl-mode)
|
2018-01-18 21:18:08 -06:00
|
|
|
;; TODO: Handle case when multiple clients are connected, i.e. do
|
|
|
|
;; we want to also delete a kernel if this is the last client
|
|
|
|
;; connected. See eieio instance tracker.
|
|
|
|
(or (and (jupyter-repl-client-has-manager-p)
|
|
|
|
(jupyter-kernel-alive-p
|
|
|
|
(oref jupyter-repl-current-client parent-instance)))
|
2018-01-08 18:11:08 -06:00
|
|
|
(jupyter-channels-running-p jupyter-repl-current-client))
|
|
|
|
(if (y-or-n-p
|
2017-12-31 11:44:31 -06:00
|
|
|
(format "Jupyter REPL (%s) still connected. Kill it? "
|
2017-12-23 15:34:28 -06:00
|
|
|
(buffer-name (current-buffer))))
|
2017-12-31 11:44:31 -06:00
|
|
|
(prog1 nil
|
2018-01-08 23:10:27 -06:00
|
|
|
(kill-buffer jupyter-repl-lang-buffer)
|
2017-12-23 15:34:28 -06:00
|
|
|
(jupyter-stop-channels jupyter-repl-current-client)
|
2018-01-18 16:43:36 -06:00
|
|
|
(cl-loop
|
|
|
|
with client = jupyter-repl-current-client
|
|
|
|
for buffer in (buffer-list)
|
|
|
|
do (with-current-buffer buffer
|
|
|
|
(when (eq jupyter-repl-current-client client)
|
2018-01-18 21:18:08 -06:00
|
|
|
(jupyter-repl-interaction-mode -1))))
|
|
|
|
(when (jupyter-repl-client-has-manager-p)
|
|
|
|
(jupyter-shutdown-kernel
|
|
|
|
(oref jupyter-repl-current-client parent-instance))))
|
2018-01-08 18:11:08 -06:00
|
|
|
t))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-17 20:56:42 -06:00
|
|
|
;; FIXME: This is necessary due to some interaction with other packages (I
|
|
|
|
;; think). Sometimes the margins will disappear after the window configuration
|
|
|
|
;; changes which is why `window-configuration-change-hook' is not used.
|
|
|
|
(defun jupyter-repl-preserve-window-margins (&optional window)
|
|
|
|
"Ensure that the margins of a REPL window are present.
|
|
|
|
This function is added as a hook to `pre-redisplay-functions' to
|
|
|
|
ensure that a REPL windows margins are present. If WINDOW is
|
|
|
|
showing a REPL buffer and the margins are not set to
|
|
|
|
`jupyter-repl-prompt-margin-width', set them to the proper
|
|
|
|
value."
|
|
|
|
(when (and (eq major-mode 'jupyter-repl-mode)
|
|
|
|
(let ((margins (window-margins window)))
|
|
|
|
(not (and (consp margins)
|
|
|
|
(car margins)
|
|
|
|
(= (car margins) jupyter-repl-prompt-margin-width)))))
|
|
|
|
(set-window-buffer window (current-buffer))))
|
2018-01-12 18:45:08 -06:00
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
;;; Completion
|
|
|
|
|
2018-01-13 22:57:50 -06:00
|
|
|
(defun jupyter-repl-company-normalize-position (pos prefix)
|
|
|
|
"Normalize POS based on the length of PREFIX for the current context.
|
|
|
|
If the `major-mode' is `jupyter-repl-mode' then POS is relative
|
|
|
|
to the contents of the current code cell."
|
|
|
|
(if (eq major-mode 'jupyter-repl-mode)
|
|
|
|
(cl-decf pos (- (point) (jupyter-repl-cell-code-beginning-position)))
|
|
|
|
pos))
|
|
|
|
|
|
|
|
(defun jupyter-repl-code-context-at-point (type &optional prefix)
|
|
|
|
"Return a cons cell, (CODE . POS), for the context around `point'.
|
|
|
|
Returns the required context depending on TYPE which can be
|
|
|
|
either `inspect' or `complete'. If TYPE is `inspect' return an
|
|
|
|
appropriate context for an inspect request. If TYPE is `complete'
|
2018-01-18 16:40:27 -06:00
|
|
|
return an appropriate context for a completion request. PREFIX
|
|
|
|
should be the prefix of the completion when TYPE is `complete'.
|
|
|
|
PREFIX is unused when TYPE is `inspect'.
|
2018-01-13 22:57:50 -06:00
|
|
|
|
|
|
|
The context also depends on the `major-mode' of the
|
|
|
|
`current-buffer'. If the `current-buffer' is a
|
|
|
|
`jupyter-repl-mode' buffer, CODE is the contents of the entire
|
|
|
|
code cell. Otherwise its either the line up to `point' if TYPE is
|
|
|
|
`complete' or the entire line TYPE is `inspect'."
|
|
|
|
;; FIXME: Remove the need for PREFIX it is currently used because the Julia
|
|
|
|
;; kernel doesn't return the right completions in the following scenario
|
|
|
|
;;
|
|
|
|
;; readline(ST|)
|
|
|
|
;;
|
|
|
|
;; If the entire line is sent with the code position at |, then the kernel
|
|
|
|
;; just dumps all available completions without a prefix when clearly we want
|
|
|
|
;; the ones that start with ST.
|
|
|
|
(unless (memq type '(complete inspect))
|
|
|
|
(error "Type not `compete' or `inspect' (%s)" type))
|
|
|
|
(if (eq major-mode 'jupyter-repl-mode)
|
|
|
|
(if (and prefix (eq jupyter-repl-lang-mode 'julia-mode))
|
|
|
|
(cons prefix (length prefix))
|
|
|
|
(let ((code (jupyter-repl-cell-code))
|
|
|
|
(pos (jupyter-repl-cell-code-position)))
|
|
|
|
;; Consider the case when completing
|
|
|
|
;;
|
|
|
|
;; In [1]: foo|
|
|
|
|
;;
|
2018-01-18 16:40:27 -06:00
|
|
|
;; The cell code position will be at position 4, i.e. where the
|
|
|
|
;; cursor is at, but the cell code will only be 3 characters long.
|
|
|
|
;; This is the reason for the check on pos.
|
2018-01-13 22:57:50 -06:00
|
|
|
(cons code (if (> pos (length code)) (length code) pos))))
|
|
|
|
(cons (buffer-substring (line-beginning-position)
|
|
|
|
(cl-case type
|
|
|
|
(inspect (line-end-position))
|
|
|
|
(otherwise (point))))
|
|
|
|
(- (point) (line-beginning-position)))))
|
|
|
|
|
|
|
|
(defun jupyter-repl-completion-prefix ()
|
|
|
|
"Return the prefix for the current completion context.
|
|
|
|
Note that the prefix returned is not the content sent to the
|
|
|
|
kernel. The prefix is the symbol (including punctuation) just
|
|
|
|
before `point'. See `jupyter-repl-code-context-at-point' for what
|
|
|
|
is actually sent to the kernel."
|
|
|
|
(when jupyter-repl-current-client
|
|
|
|
(let ((lang-mode (with-jupyter-repl-buffer
|
|
|
|
jupyter-repl-current-client
|
|
|
|
jupyter-repl-lang-mode)))
|
|
|
|
(and (memq major-mode `(,lang-mode jupyter-repl-mode))
|
|
|
|
;; No completion in finalized cells
|
|
|
|
(not (get-text-property (point) 'read-only))
|
|
|
|
(if (or (looking-at "\\_>")
|
2018-01-16 11:40:44 -06:00
|
|
|
;; TODO: What about other operators like :: and ->, this
|
|
|
|
;; most likely will depend on the kernel in use.
|
|
|
|
;; `jupyter-repl-lang-mode' can be used here with some alist
|
|
|
|
;; mapping modes to operators.
|
|
|
|
(looking-back "\\." 2))
|
2018-01-13 22:57:50 -06:00
|
|
|
(buffer-substring
|
|
|
|
(save-excursion
|
|
|
|
(skip-syntax-backward "w_.")
|
|
|
|
(point))
|
|
|
|
(point))
|
|
|
|
(unless (and (char-after)
|
|
|
|
(memq (char-syntax (char-after))
|
|
|
|
'(?w ?_ ?.)))
|
|
|
|
""))))))
|
|
|
|
|
|
|
|
(defun jupyter-repl-construct-completion-candidates (prefix matches metadata start end)
|
|
|
|
"Construct candidates for `company-mode' completion.
|
|
|
|
PREFIX is the prefix used to start the current completion.
|
|
|
|
MATCHES are the completion matches returned by the kernel,
|
|
|
|
METADATA is any extra data associated with MATCHES and is
|
|
|
|
currently used for adding annotations to each candidate. START
|
2018-01-18 16:40:27 -06:00
|
|
|
and END are the start and end of text that the elements of
|
|
|
|
MATCHES should be replace as reported by the kernel. Note that
|
|
|
|
START and END are relative to the
|
|
|
|
`jupyter-repl-code-context-at-point' and not to PREFIX. See
|
|
|
|
`jupyter-repl-completion-prefix' for the value that PREFIX
|
|
|
|
takes."
|
2018-01-13 22:57:50 -06:00
|
|
|
(let ((types (plist-get metadata :_jupyter_types_experimental)))
|
2018-01-16 11:41:38 -06:00
|
|
|
(let ((matches matches)
|
|
|
|
;; TODO: This may not be the most general way to use start and end
|
|
|
|
(prefix (seq-subseq prefix 0 (- (length prefix)
|
|
|
|
(- end start))))
|
|
|
|
match)
|
2018-01-13 22:57:50 -06:00
|
|
|
(while (setq match (car matches))
|
|
|
|
;; TODO: Maybe set the match property when it doesn't have the prefix,
|
|
|
|
;; indicating that it should replace part of the prefix?
|
|
|
|
(unless (string-prefix-p prefix match)
|
2018-01-16 11:41:38 -06:00
|
|
|
;; FIXME: Note that prefix is not the code sent to the kernel in some
|
2018-01-13 22:57:50 -06:00
|
|
|
;; cases, but the symbol behind point
|
2018-01-16 11:41:38 -06:00
|
|
|
(setcar matches (concat prefix (car matches))))
|
2018-01-13 22:57:50 -06:00
|
|
|
;; (put-text-property 0 1 'match match-start (car matches))
|
|
|
|
(setq matches (cdr matches))))
|
|
|
|
(when types
|
|
|
|
(let ((max-len (apply #'max (mapcar #'length matches))))
|
|
|
|
(cl-mapcar
|
|
|
|
(lambda (match meta)
|
|
|
|
(put-text-property
|
|
|
|
0 1 'annot
|
|
|
|
(concat (make-string (1+ (- max-len (length match))) ? )
|
|
|
|
(plist-get meta :type))
|
|
|
|
match)
|
|
|
|
match)
|
|
|
|
matches types)))
|
|
|
|
matches))
|
|
|
|
|
|
|
|
(defun company-jupyter-repl (command &optional arg &rest _)
|
|
|
|
"`company-mode' backend using a `jupyter-repl-client'.
|
|
|
|
COMMAND and ARG have the same meaning as the elements of
|
|
|
|
`company-backends'."
|
2017-12-23 15:34:28 -06:00
|
|
|
(interactive (list 'interactive))
|
|
|
|
(cl-case command
|
|
|
|
(interactive (company-begin-backend 'company-jupyter-repl))
|
|
|
|
(sorted t)
|
2018-01-13 22:57:50 -06:00
|
|
|
(prefix (or (jupyter-repl-completion-prefix) 'stop))
|
2018-01-16 11:27:53 -06:00
|
|
|
(candidates
|
|
|
|
(cons
|
|
|
|
:async
|
|
|
|
(lambda (cb)
|
|
|
|
(let* ((ctx (jupyter-repl-code-context-at-point 'complete arg))
|
|
|
|
(code (car ctx))
|
|
|
|
(pos (cdr ctx)))
|
|
|
|
(jupyter-add-callback
|
|
|
|
;; Ignore errors during completion
|
2018-01-21 01:06:49 -06:00
|
|
|
(let ((jupyter-inhibit-handlers t))
|
|
|
|
(jupyter-complete-request
|
|
|
|
jupyter-repl-current-client
|
|
|
|
:code code :pos pos))
|
2018-01-16 11:27:53 -06:00
|
|
|
:complete-reply
|
|
|
|
(lambda (msg)
|
|
|
|
(cl-destructuring-bind (&key status
|
|
|
|
matches metadata
|
|
|
|
cursor_start cursor_end
|
|
|
|
&allow-other-keys)
|
|
|
|
(jupyter-message-content msg)
|
|
|
|
(funcall
|
|
|
|
cb (when (equal status "ok")
|
|
|
|
(jupyter-repl-construct-completion-candidates
|
|
|
|
arg matches metadata cursor_start cursor_end))))))))))
|
|
|
|
(ignore-case t)
|
2018-01-13 22:57:50 -06:00
|
|
|
(annotation (get-text-property 0 'annot arg))
|
2018-01-16 11:26:10 -06:00
|
|
|
(doc-buffer (let* ((inhibit-read-only t)
|
|
|
|
(buf (jupyter-repl--inspect
|
|
|
|
arg (length arg) (company-doc-buffer)
|
|
|
|
company-async-timeout)))
|
|
|
|
(when buf
|
|
|
|
(with-current-buffer buf
|
|
|
|
(remove-text-properties
|
|
|
|
(point-max) (point-min) '(read-only)))
|
|
|
|
buf)))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-16 11:44:13 -06:00
|
|
|
;;; Inspection
|
|
|
|
|
2018-01-16 11:25:07 -06:00
|
|
|
(defun jupyter-repl--inspect (code pos &optional buffer timeout)
|
|
|
|
"Send an inspect request to a Jupyter kernel.
|
|
|
|
CODE and POS are the code to send and the position within the
|
|
|
|
code, respectively. TIMEOUT is how long to wait (in seconds) for
|
|
|
|
the kernel to respond. If the kernel does not respond within
|
|
|
|
TIMEOUT, return nil. Otherwise if a reply was received within
|
2018-01-18 16:40:27 -06:00
|
|
|
TIMEOUT, return either the inspection text or BUFFER depending on
|
|
|
|
if BUFFER is non-nil. When buffer is nil, return the inspection
|
|
|
|
text, otherwise return BUFFER after inserting the inspection text
|
|
|
|
in BUFFER. In both cases, the inspection text will already be in
|
|
|
|
a form ready for display."
|
2018-01-17 20:28:46 -06:00
|
|
|
(let* ((jupyter-inhibit-handlers t)
|
|
|
|
(msg (jupyter-wait-until-received :inspect-reply
|
2018-01-13 23:04:04 -06:00
|
|
|
(jupyter-inspect-request jupyter-repl-current-client
|
2018-01-20 23:13:23 -06:00
|
|
|
:code code :pos pos)
|
|
|
|
timeout)))
|
2018-01-13 23:04:04 -06:00
|
|
|
(when msg
|
|
|
|
(cl-destructuring-bind (&key status found data &allow-other-keys)
|
|
|
|
(jupyter-message-content msg)
|
|
|
|
(when (and (equal status "ok") found)
|
2018-01-16 11:25:07 -06:00
|
|
|
(if buffer (with-current-buffer buffer
|
|
|
|
(jupyter-repl-insert-data data)
|
|
|
|
buffer)
|
|
|
|
(with-temp-buffer
|
|
|
|
(jupyter-repl-insert-data data)
|
|
|
|
(buffer-string))))))))
|
2018-01-16 11:44:13 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-inspect-at-point ()
|
|
|
|
(interactive)
|
|
|
|
(cl-destructuring-bind (code . pos)
|
|
|
|
(jupyter-repl-code-context-at-point 'inspect)
|
|
|
|
;; Set the default value of the client for
|
|
|
|
;; `jupyter-jupyter-repl-doc-buffer'
|
|
|
|
(let ((client jupyter-repl-current-client))
|
|
|
|
(with-jupyter-repl-doc-buffer "inspect"
|
|
|
|
;; TODO: Cleanup how `jupyter-repl--inspect' works, make it more clear
|
|
|
|
;; that `current-buffer' here means to insert the inspected result in
|
|
|
|
;; the current buffer.
|
|
|
|
;;
|
|
|
|
;; TODO: Figure out why setting this outside
|
|
|
|
;; `with-jupyter-repl-doc-buffer' doesn't work. Possibly to do with
|
|
|
|
;; this variable being `permanent-local'?
|
|
|
|
(let ((jupyter-repl-current-client client))
|
|
|
|
(if (not (jupyter-repl--inspect code pos (current-buffer)))
|
|
|
|
(message "Inspect timed out")
|
|
|
|
;; TODO: Customizable action
|
|
|
|
(display-buffer (current-buffer))
|
|
|
|
(set-window-start (get-buffer-window) (point-min))))))))
|
2018-01-16 11:44:48 -06:00
|
|
|
|
|
|
|
;;; Evaluation
|
|
|
|
|
2018-01-17 20:47:05 -06:00
|
|
|
(defun jupyter-repl-eval-string (str &optional silently)
|
2018-01-16 11:44:48 -06:00
|
|
|
"Evaluate STR with the `jupyter-repl-current-client'.
|
|
|
|
The contents of the last cell in the REPL buffer will be replaced
|
|
|
|
with STR and the last cell executed with the
|
|
|
|
`juptyer-repl-current-client'. After execution, the execution
|
|
|
|
result is echoed to the *Message* buffer or a new buffer showing
|
|
|
|
the result is open if the result output is larger than 10 lines
|
|
|
|
long."
|
|
|
|
(interactive)
|
|
|
|
(unless (buffer-local-value
|
|
|
|
'jupyter-repl-current-client (current-buffer))
|
|
|
|
(user-error "No `jupyter-repl-current-client' set, see `jupyter-repl-associate-buffer'"))
|
|
|
|
(with-jupyter-repl-buffer jupyter-repl-current-client
|
|
|
|
(goto-char (point-max))
|
|
|
|
(unless (= (save-excursion (jupyter-repl-previous-cell)) 0)
|
|
|
|
(jupyter-repl-insert-prompt 'in))
|
2018-01-17 20:47:05 -06:00
|
|
|
(setq str (strim-trim str))
|
|
|
|
(let* ((code (if silently (string-trim str)
|
|
|
|
(prog1 nil
|
|
|
|
(jupyter-repl-replace-cell-code str))))
|
|
|
|
(req (jupyter-execute-request jupyter-repl-current-client
|
|
|
|
:code code)))
|
|
|
|
(setf (jupyter-request-run-handlers-p req) (not silently))
|
2018-01-16 11:44:48 -06:00
|
|
|
(jupyter-add-callback req
|
2018-01-17 20:47:05 -06:00
|
|
|
:error (lambda (msg)
|
|
|
|
(cl-destructuring-bind (&key ename evalue &allow-other-keys)
|
|
|
|
(jupyter-message-content msg)
|
|
|
|
(message "jupyter (%s): %s" ename
|
|
|
|
(xterm-color-filter evalue))))
|
2018-01-16 11:44:48 -06:00
|
|
|
:execute-result
|
|
|
|
(lambda (msg)
|
|
|
|
(let ((res (jupyter-message-data msg :text/plain)))
|
|
|
|
(when res
|
|
|
|
(if (and (jupyter-repl-multiline-p res)
|
|
|
|
(cl-loop
|
|
|
|
with nlines = 0
|
|
|
|
for c across res when (eq c ?\n) do (cl-incf nlines)
|
|
|
|
thereis (> nlines 10)))
|
|
|
|
(with-current-buffer
|
|
|
|
(get-buffer-create "*jupyter-repl-result*")
|
|
|
|
(erase-buffer)
|
|
|
|
(insert res)
|
|
|
|
(goto-char (point-min))
|
2018-01-18 22:02:04 -06:00
|
|
|
(display-buffer (current-buffer)))
|
2018-01-16 11:44:48 -06:00
|
|
|
(if (equal res "") (message "jupyter: eval done")
|
|
|
|
(message res)))))))
|
|
|
|
req)))
|
|
|
|
|
2018-01-17 20:48:23 -06:00
|
|
|
(defun jupyter-repl-eval-file (file)
|
|
|
|
"Send the contents of FILE using `jupyter-repl-current-client'."
|
|
|
|
(interactive
|
|
|
|
(list (read-file-name "File name: " nil nil nil
|
|
|
|
(file-name-nondirectory
|
|
|
|
(or (buffer-file-name) "")))))
|
|
|
|
(message "Evaluating %s..." file)
|
|
|
|
(setq file (expand-file-name file))
|
|
|
|
(if (file-exists-p file)
|
|
|
|
(let ((buf (find-buffer-visiting file)))
|
|
|
|
(jupyter-repl-eval-string
|
|
|
|
(if buf (with-current-buffer buf
|
|
|
|
(buffer-string))
|
|
|
|
(with-current-buffer (delay-mode-hooks (find-file-noselect file))
|
|
|
|
(prog1 (buffer-string)
|
|
|
|
(kill-buffer))))
|
|
|
|
'silently))
|
|
|
|
(error "Not a file (%s)" file)))
|
|
|
|
|
2018-01-17 20:48:53 -06:00
|
|
|
(defun jupyter-repl-eval-region (beg end &optional silently)
|
2018-01-16 11:44:48 -06:00
|
|
|
"Evaluate a region with the `jupyter-repl-current-client'.
|
|
|
|
BEG and END are the beginning and end of the region to evaluate.
|
|
|
|
See `jupyter-repl-eval-string' for how the results of evaluation
|
|
|
|
are displayed."
|
|
|
|
(interactive "r")
|
|
|
|
(jupyter-repl-eval-string
|
2018-01-17 20:48:53 -06:00
|
|
|
(buffer-substring-no-properties beg end) silently))
|
2018-01-16 11:44:48 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-eval-line-or-region ()
|
|
|
|
"Evaluate the current line or region with the `jupyter-repl-current-client'.
|
|
|
|
If the current region is active send the current region using
|
|
|
|
`jupyter-repl-eval-region', otherwise send the current line."
|
|
|
|
(interactive)
|
|
|
|
(if (use-region-p)
|
|
|
|
(jupyter-repl-eval-region (region-beginning) (region-end))
|
|
|
|
(jupyter-repl-eval-region (line-beginning-position) (line-end-position))))
|
|
|
|
|
2018-01-16 11:45:39 -06:00
|
|
|
;;; Kernel management
|
|
|
|
|
2018-01-18 21:18:08 -06:00
|
|
|
(defun jupyter-repl-client-has-manager-p ()
|
|
|
|
"Does the `jupyter-repl-current-client' have a `jupyter-kernel-manager'?
|
|
|
|
Checks to see if the REPL client of the `current-buffer' has a
|
|
|
|
kernel manager as its parent-instance slot."
|
|
|
|
(and jupyter-repl-current-client
|
|
|
|
(slot-boundp jupyter-repl-current-client 'parent-instance)
|
|
|
|
(obj-of-class-p (oref jupyter-repl-current-client parent-instance)
|
|
|
|
'jupyter-kernel-manager)))
|
|
|
|
|
2018-01-16 11:45:39 -06:00
|
|
|
(defun jupyter-repl-interrupt-kernel ()
|
|
|
|
"Interrupt the kernel if possible.
|
|
|
|
A kernel can be interrupted if it was started using a
|
|
|
|
`jupyter-kernel-manager'. See `jupyter-start-new-kernel'."
|
|
|
|
(interactive)
|
2018-01-18 21:18:08 -06:00
|
|
|
(if (not (jupyter-repl-client-has-manager-p))
|
|
|
|
(user-error "Cannot interrupt non-subprocess kernels")
|
|
|
|
(message "Interrupting kernel")
|
|
|
|
(jupyter-interrupt-kernel
|
|
|
|
(oref jupyter-repl-current-client parent-instance))))
|
2018-01-16 11:45:39 -06:00
|
|
|
|
|
|
|
;; TODO: Make timeouts configurable
|
|
|
|
;; TODO: Handle all consequences of a shutdown
|
|
|
|
(defun jupyter-repl-restart-kernel (shutdown)
|
|
|
|
"Restart the kernel.
|
|
|
|
With a prefix argument, SHUTDOWN the kernel completely instead."
|
|
|
|
(interactive "P")
|
|
|
|
(unless shutdown
|
|
|
|
;; Gets reset to default value in
|
|
|
|
;; `jupyter-repl-insert-prompt-when-starting'
|
|
|
|
(jupyter-set
|
|
|
|
jupyter-repl-current-client
|
|
|
|
'jupyter-include-other-output
|
|
|
|
(list (jupyter-get
|
|
|
|
jupyter-repl-current-client
|
|
|
|
'jupyter-include-other-output)))
|
|
|
|
(setq-local jupyter-include-other-output
|
|
|
|
(list jupyter-include-other-output))
|
|
|
|
;; This may have been set to t due to a non-responsive kernel so make sure
|
|
|
|
;; that we try again when restarting.
|
|
|
|
(setq-local jupyter-repl-use-builtin-is-complete nil))
|
2018-01-18 21:18:08 -06:00
|
|
|
(if (jupyter-repl-client-has-manager-p)
|
|
|
|
(let ((manager (oref jupyter-repl-current-client parent-instance)))
|
|
|
|
(if (jupyter-kernel-alive-p manager)
|
|
|
|
(progn
|
|
|
|
(message "%s kernel..." (if shutdown "Shutting down"
|
|
|
|
"Restarting"))
|
|
|
|
(jupyter-shutdown-kernel manager (not shutdown)))
|
|
|
|
(message "Starting dead kernel...")
|
|
|
|
(jupyter-start-kernel manager)))
|
2018-01-16 11:45:39 -06:00
|
|
|
(when (null (jupyter-wait-until-received :shutdown-reply
|
|
|
|
(jupyter-shutdown-request jupyter-repl-current-client
|
|
|
|
(not shutdown))))
|
|
|
|
(message "Kernel did not respond to shutdown request"))))
|
|
|
|
|
|
|
|
(defun jupyter-repl-display-kernel-buffer ()
|
|
|
|
"Display the kernel processes stdout."
|
|
|
|
(interactive)
|
2018-01-18 21:18:08 -06:00
|
|
|
(if (jupyter-repl-client-has-manager-p)
|
|
|
|
(let ((manager (oref jupyter-repl-current-client parent-instance)))
|
|
|
|
(display-buffer (process-buffer (oref manager kernel))))
|
2018-01-16 11:45:39 -06:00
|
|
|
(user-error "Kernel not a subprocess")))
|
|
|
|
|
|
|
|
(defun jupyter-repl-restart-channels ()
|
|
|
|
(interactive)
|
|
|
|
(message "Restarting client channels...")
|
|
|
|
(jupyter-stop-channels jupyter-repl-current-client)
|
|
|
|
(jupyter-start-channels jupyter-repl-current-client))
|
2018-01-16 11:46:05 -06:00
|
|
|
|
|
|
|
;;; Isearch
|
|
|
|
;; Adapted from isearch in `comint', see `comint-history-isearch-search' for
|
|
|
|
;; details
|
|
|
|
|
|
|
|
(defun jupyter-repl-isearch-setup ()
|
|
|
|
(setq-local isearch-search-fun-function
|
|
|
|
#'jupyter-repl-history-isearch-search)
|
|
|
|
(setq-local isearch-wrap-function
|
|
|
|
#'jupyter-repl-history-isearch-wrap)
|
|
|
|
(setq-local isearch-push-state-function
|
|
|
|
#'jupyter-repl-history-isearch-push-state))
|
|
|
|
|
|
|
|
;; Adapted from `comint-history-isearch-search'
|
|
|
|
(defun jupyter-repl-history-isearch-search ()
|
|
|
|
(lambda (string bound noerror)
|
|
|
|
(let ((search-fun (isearch-search-fun-default)) found)
|
|
|
|
(unless isearch-forward (goto-char (point-max)))
|
|
|
|
(or
|
|
|
|
;; 1. First try searching in the initial cell text
|
|
|
|
(funcall search-fun string
|
|
|
|
(if isearch-forward bound
|
|
|
|
(jupyter-repl-cell-code-beginning-position))
|
|
|
|
noerror)
|
|
|
|
;; 2. If the above search fails, start putting next/prev history
|
|
|
|
;; elements in the cell successively, and search the string in them. Do
|
|
|
|
;; this only when bound is nil (i.e. not while lazy-highlighting search
|
|
|
|
;; strings in the current cell text).
|
|
|
|
(unless bound
|
|
|
|
(condition-case err
|
|
|
|
(progn
|
|
|
|
(while (not found)
|
|
|
|
(cond (isearch-forward
|
|
|
|
;; See the comment in
|
|
|
|
;; `jupyter-repl-history-isearch-wrap'
|
|
|
|
(if (eq (ring-ref jupyter-repl-history -1)
|
|
|
|
'jupyter-repl-history)
|
|
|
|
(error "End of history")
|
|
|
|
(jupyter-repl-history-next))
|
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position)))
|
|
|
|
(t
|
|
|
|
(jupyter-repl-history-previous)
|
|
|
|
(goto-char (point-max))))
|
|
|
|
(setq isearch-barrier (point) isearch-opoint (point))
|
|
|
|
;; After putting the next/prev history element, search the
|
|
|
|
;; string in them again, until an error is thrown at the
|
|
|
|
;; beginning/end of history.
|
|
|
|
(setq found (funcall search-fun string
|
|
|
|
(unless isearch-forward
|
|
|
|
(jupyter-repl-cell-code-beginning-position))
|
|
|
|
noerror)))
|
|
|
|
;; Return point of the new search result
|
|
|
|
(point))
|
|
|
|
;; Return nil on the error "no next/preceding item"
|
|
|
|
(error nil)))))))
|
|
|
|
|
|
|
|
(defun jupyter-repl-history-isearch-wrap ()
|
|
|
|
(condition-case nil
|
|
|
|
(if isearch-forward
|
|
|
|
(jupyter-repl-history-next (ring-length jupyter-repl-history) t)
|
|
|
|
(jupyter-repl-history-previous (ring-length jupyter-repl-history) t))
|
|
|
|
(error nil))
|
|
|
|
(jupyter-repl-replace-cell-code (ring-ref jupyter-repl-history 0))
|
|
|
|
(goto-char (if isearch-forward (jupyter-repl-cell-code-beginning-position)
|
|
|
|
(point-max))))
|
|
|
|
|
|
|
|
(defun jupyter-repl-history-isearch-push-state ()
|
|
|
|
(let ((elem (ring-ref jupyter-repl-history 0)))
|
|
|
|
(lambda (_cmd)
|
|
|
|
(while (not (eq (ring-ref jupyter-repl-history 0) elem))
|
|
|
|
(if isearch-forward (jupyter-repl-history-next 1 t)
|
|
|
|
(jupyter-repl-history-previous 1 t)))
|
|
|
|
(jupyter-repl-replace-cell-code (ring-ref jupyter-repl-history 0)))))
|
|
|
|
|
2018-01-13 23:03:22 -06:00
|
|
|
;;; `jupyter-repl-mode'
|
|
|
|
|
|
|
|
(defvar jupyter-repl-mode-map
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
(define-key map "q" nil)
|
|
|
|
(define-key map [remap backward-sentence] #'jupyter-repl-backward-cell)
|
|
|
|
(define-key map [remap forward-sentence] #'jupyter-repl-forward-cell)
|
|
|
|
(define-key map (kbd "RET") #'jupyter-repl-ret)
|
|
|
|
(define-key map (kbd "C-n") #'jupyter-repl-history-next)
|
|
|
|
(define-key map (kbd "C-p") #'jupyter-repl-history-previous)
|
|
|
|
(define-key map (kbd "M-n") #'jupyter-repl-history-next)
|
|
|
|
(define-key map (kbd "M-p") #'jupyter-repl-history-previous)
|
|
|
|
map))
|
2017-12-31 11:41:53 -06:00
|
|
|
|
2018-01-13 23:04:52 -06:00
|
|
|
(put 'jupyter-repl-mode 'mode-class 'special)
|
2017-12-23 15:34:28 -06:00
|
|
|
(define-derived-mode jupyter-repl-mode fundamental-mode
|
|
|
|
"Jupyter-REPL"
|
2018-01-18 22:07:09 -06:00
|
|
|
"A Jupyter REPL major mode."
|
|
|
|
(cl-check-type jupyter-repl-current-client jupyter-repl-client)
|
|
|
|
(setq-local jupyter-repl-current-client jupyter-repl-current-client)
|
2017-12-27 21:21:33 -06:00
|
|
|
(setq-local indent-line-function #'jupyter-repl-indent-line)
|
2018-01-13 23:04:52 -06:00
|
|
|
(setq-local left-margin-width jupyter-repl-prompt-margin-width)
|
2018-01-18 22:07:09 -06:00
|
|
|
;; Initialize a buffer using the major-mode correponding to the kernel's
|
|
|
|
;; language. This will be used for indentation and to capture font lock
|
|
|
|
;; properties.
|
2018-01-21 01:07:33 -06:00
|
|
|
(let ((info (oref jupyter-repl-current-client kernel-info)))
|
|
|
|
(cl-destructuring-bind (mode syntax)
|
|
|
|
(jupyter-repl-kernel-mode-info info)
|
|
|
|
(setq-local jupyter-repl-lang-mode mode)
|
|
|
|
(setq-local jupyter-repl-lang-buffer
|
|
|
|
(get-buffer-create
|
|
|
|
(format " *jupyter-repl-lang-%s*"
|
|
|
|
(plist-get (plist-get info :language_info)
|
|
|
|
:language))))
|
|
|
|
(set-syntax-table syntax)
|
|
|
|
(with-jupyter-repl-lang-buffer
|
|
|
|
(unless (eq major-mode mode)
|
|
|
|
(funcall mode)))))
|
2018-01-18 22:07:09 -06:00
|
|
|
;; Get history from kernel
|
2018-01-13 23:04:52 -06:00
|
|
|
(setq-local jupyter-repl-history
|
|
|
|
(make-ring (1+ jupyter-repl-history-maximum-length)))
|
2018-01-18 22:07:09 -06:00
|
|
|
;; The sentinel value keeps track of the newest/oldest elements of the
|
|
|
|
;; history since next/previous navigation is implemented by rotations on the
|
|
|
|
;; ring.
|
2018-01-13 23:04:52 -06:00
|
|
|
(ring-insert jupyter-repl-history 'jupyter-repl-history)
|
2018-01-18 22:07:09 -06:00
|
|
|
(jupyter-history-request jupyter-repl-current-client
|
|
|
|
:n jupyter-repl-history-maximum-length :raw nil :unique t)
|
2018-01-13 23:04:52 -06:00
|
|
|
(erase-buffer)
|
2018-01-18 22:07:09 -06:00
|
|
|
;; Add local hooks
|
2018-01-17 20:56:06 -06:00
|
|
|
(add-hook 'kill-buffer-query-functions #'jupyter-repl-kill-buffer-query-function nil t)
|
2018-01-12 18:45:08 -06:00
|
|
|
(add-hook 'after-change-functions 'jupyter-repl-after-buffer-change nil t)
|
2018-01-18 22:07:09 -06:00
|
|
|
(add-hook 'pre-redisplay-functions 'jupyter-repl-preserve-window-margins nil t)
|
|
|
|
;; Initialize the REPL
|
|
|
|
(jupyter-set jupyter-repl-current-client 'jupyter-include-other-output t)
|
|
|
|
(jupyter-repl-initialize-fontification)
|
|
|
|
(jupyter-repl-isearch-setup)
|
|
|
|
(jupyter-repl-sync-execution-state)
|
|
|
|
(jupyter-repl-interaction-mode))
|
2017-12-31 11:41:53 -06:00
|
|
|
|
2018-01-11 03:28:04 -06:00
|
|
|
(defun jupyter-repl-initialize-fontification ()
|
2017-12-31 11:41:53 -06:00
|
|
|
(let (fld)
|
|
|
|
(with-jupyter-repl-lang-buffer
|
|
|
|
(setq fld font-lock-defaults))
|
|
|
|
(setq font-lock-defaults fld)
|
2018-01-11 03:28:04 -06:00
|
|
|
(font-lock-mode)))
|
2017-12-31 11:41:53 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-insert-banner (banner)
|
2018-01-04 17:21:59 -06:00
|
|
|
"Insert BANNER into the `current-buffer'.
|
|
|
|
Make the text of BANNER read only and apply the `shadow' face to
|
|
|
|
it."
|
2018-01-13 23:07:07 -06:00
|
|
|
(jupyter-repl-without-continuation-prompts
|
|
|
|
(let ((start (point)))
|
|
|
|
(jupyter-repl-insert banner)
|
|
|
|
(jupyter-repl-newline)
|
|
|
|
(add-text-properties start (point) '(font-lock-face shadow fontified t)))))
|
2017-12-31 11:41:53 -06:00
|
|
|
|
2018-01-17 21:22:12 -06:00
|
|
|
(defun jupyter-repl-sync-execution-state ()
|
2018-01-08 18:11:08 -06:00
|
|
|
"Synchronize the execution count of `jupyter-repl-current-client'.
|
|
|
|
Set the execution-count slot of `jupyter-repl-current-client' to
|
|
|
|
1+ the execution count of the client's kernel."
|
2018-01-11 03:28:04 -06:00
|
|
|
(let* ((client jupyter-repl-current-client)
|
|
|
|
(req (jupyter-execute-request client :code "" :silent t)))
|
2018-01-17 20:28:46 -06:00
|
|
|
(setf (jupyter-request-run-handlers-p req) nil)
|
2018-01-06 15:31:39 -06:00
|
|
|
(jupyter-add-callback req
|
2018-01-17 21:22:12 -06:00
|
|
|
:status (lambda (msg)
|
|
|
|
(oset client execution-state
|
|
|
|
(jupyter-message-get msg :execution_state)))
|
2018-01-11 03:28:04 -06:00
|
|
|
:execute-reply (lambda (msg)
|
|
|
|
(oset client execution-count
|
|
|
|
(1+ (jupyter-message-get msg :execution_count)))))
|
2018-01-04 17:23:48 -06:00
|
|
|
(jupyter-wait-until-idle req)))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-16 11:49:29 -06:00
|
|
|
;;; `jupyter-repl-interaction-mode'
|
|
|
|
|
|
|
|
(defun jupyter-repl-pop-to-buffer ()
|
|
|
|
"Switch to the REPL buffer associated with `jupyter-repl-current-client'."
|
|
|
|
(interactive)
|
|
|
|
(with-jupyter-repl-buffer jupyter-repl-current-client
|
|
|
|
(goto-char (point-max))
|
|
|
|
(pop-to-buffer (current-buffer))))
|
|
|
|
|
|
|
|
(defun jupyter-repl-available-repl-buffers (&optional mode)
|
|
|
|
"Get a list of REPL buffers that are connected to live kernels.
|
|
|
|
If MODE is non-nil, return REPL buffers connected to MODE's
|
|
|
|
language. MODE should be the `major-mode' used to edit files of
|
|
|
|
one of the Jupyter kernel languages."
|
2018-01-18 16:43:04 -06:00
|
|
|
(delq
|
|
|
|
nil
|
|
|
|
(mapcar (lambda (b)
|
|
|
|
(with-current-buffer b
|
|
|
|
(and (eq major-mode 'jupyter-repl-mode)
|
|
|
|
(if mode (eq mode jupyter-repl-lang-mode) t)
|
2018-01-18 21:18:08 -06:00
|
|
|
(or (and (jupyter-repl-client-has-manager-p)
|
|
|
|
;; Check if the kernel is local
|
|
|
|
(jupyter-kernel-alive-p
|
|
|
|
(oref jupyter-repl-current-client parent-instance)))
|
2018-01-18 16:43:04 -06:00
|
|
|
(let ((hb (oref jupyter-repl-current-client hb-channel)))
|
|
|
|
(and (and (jupyter-channel-alive-p hb))
|
|
|
|
(jupyter-hb-beating-p hb))))
|
|
|
|
(buffer-name b))))
|
|
|
|
(buffer-list))))
|
2018-01-16 11:49:29 -06:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun jupyter-repl-associate-buffer (client)
|
|
|
|
"Associate the `current-buffer' with a REPL CLIENT.
|
|
|
|
The `current-buffer's `major-mode' must be the
|
|
|
|
`jupyter-repl-lang-mode' of the CLIENT. CLIENT can either be a
|
|
|
|
`jupyter-repl-client' or a buffer with a non-nil
|
|
|
|
`jupyter-repl-current-client'. The buffer-local value of
|
|
|
|
`jupyter-repl-current-client' in the `current-buffer' is set to
|
|
|
|
that of CLIENT."
|
|
|
|
(interactive
|
|
|
|
(list
|
|
|
|
(completing-read
|
|
|
|
"jupyter-repl: "
|
|
|
|
(or (jupyter-repl-available-repl-buffers major-mode)
|
|
|
|
(error "No live REPL for `current-buffer's `major-mode'"))
|
|
|
|
nil t)))
|
|
|
|
(setq client (if (or (bufferp client) (stringp client))
|
|
|
|
(with-current-buffer client
|
|
|
|
jupyter-repl-current-client)
|
|
|
|
client))
|
|
|
|
(cl-check-type client jupyter-repl-client)
|
|
|
|
(setq-local jupyter-repl-current-client client)
|
|
|
|
(jupyter-repl-interaction-mode))
|
|
|
|
|
|
|
|
(defvar jupyter-repl-interaction-map
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
(define-key map (kbd "C-c C-c") #'jupyter-repl-eval-line-or-region)
|
|
|
|
(define-key map (kbd "C-c C-f") #'jupyter-repl-inspect-at-point)
|
|
|
|
(define-key map (kbd "C-c C-r") #'jupyter-repl-restart-kernel)
|
|
|
|
(define-key map (kbd "C-c R") #'jupyter-repl-restart-channels)
|
|
|
|
(define-key map (kbd "C-c C-i") #'jupyter-repl-interrupt-kernel)
|
|
|
|
(define-key map (kbd "C-c C-z") #'jupyter-repl-pop-to-buffer)
|
|
|
|
map))
|
|
|
|
|
|
|
|
(define-minor-mode jupyter-repl-interaction-mode
|
|
|
|
"Minor mode for interacting with a Jupyter REPL."
|
|
|
|
:group 'jupyter-repl
|
|
|
|
:lighter " JuPy"
|
|
|
|
:init-value nil
|
|
|
|
:keymap jupyter-repl-interaction-map
|
|
|
|
(if jupyter-repl-interaction-mode
|
|
|
|
(when (boundp 'company-mode)
|
|
|
|
(unless (cl-find-if
|
|
|
|
(lambda (x) (or (and (listp x) (memq 'company-jupyter-repl x))
|
|
|
|
(eq x 'company-jupyter-repl)))
|
|
|
|
company-backends)
|
|
|
|
(setq-local company-backends
|
|
|
|
(cons 'company-jupyter-repl company-backends))))
|
|
|
|
(when (boundp 'company-mode)
|
|
|
|
(setq-local company-backends
|
|
|
|
(delq 'company-jupyter-repl company-backends)))))
|
|
|
|
|
2018-01-18 22:07:09 -06:00
|
|
|
(defun jupyter-repl-kernel-mode-info (kernel-info)
|
|
|
|
"Get the `major-mode' for a kernel based on its KERNEL-INFO.
|
|
|
|
Currently this returns a list of information required to
|
|
|
|
initialize a REPL buffer such as the `major-mode' used for syntax
|
|
|
|
highlighting and indentation purposes. The list has the following
|
|
|
|
elements
|
|
|
|
|
|
|
|
(MODE SYNTAX-TABLE)
|
|
|
|
|
|
|
|
Where MODE is the `major-mode' to use for syntax highlighting
|
|
|
|
purposes and SYNTAX-TABLE is the syntax table of the mode. MODE
|
|
|
|
is found by consulting `auto-mode-alist' for the file extension
|
|
|
|
found in KERNEL-INFO."
|
|
|
|
(cl-destructuring-bind (&key file_extension &allow-other-keys)
|
|
|
|
(plist-get kernel-info :language_info)
|
|
|
|
(let (mode syntax)
|
|
|
|
(with-temp-buffer
|
|
|
|
(let ((buffer-file-name
|
|
|
|
(concat "jupyter-repl-lang" file_extension)))
|
|
|
|
(delay-mode-hooks (set-auto-mode))
|
|
|
|
(setq mode major-mode)
|
|
|
|
(setq syntax (syntax-table))))
|
|
|
|
(list mode syntax))))
|
|
|
|
|
|
|
|
(defun jupyter-repl-same-lang-mode-p (buffer client)
|
|
|
|
"Is BUFFER's `major-mode' the same as CLIENT's `jupyter-repl-lang-mode'?"
|
|
|
|
(with-jupyter-repl-buffer client
|
|
|
|
(eq jupyter-repl-lang-mode (with-current-buffer buffer major-mode))))
|
|
|
|
|
|
|
|
(defun jupyter-repl--new-repl (client)
|
|
|
|
"Initialize a new REPL buffer based on CLIENT.
|
|
|
|
CLIENT should be a `jupyter-repl-client' already connected to its
|
|
|
|
kernel and should have a non-nil kernel-info slot.
|
|
|
|
|
|
|
|
A new REPL buffer communicating with CLIENT's kernel is created
|
|
|
|
and set as CLIENT'sthis case, if MANAGER will be the buffer slot.
|
|
|
|
If CLIENT already has a non-nil buffer slot, raise an error."
|
|
|
|
(cl-check-type client jupyter-repl-client)
|
|
|
|
(if (slot-boundp client 'buffer) (error "Client already has a REPL buffer")
|
|
|
|
(unless (ignore-errors (oref client kernel-info))
|
|
|
|
(error "Client needs to have valid kernel-info"))
|
|
|
|
(cl-destructuring-bind (&key language_info
|
|
|
|
banner
|
|
|
|
&allow-other-keys)
|
|
|
|
(oref client kernel-info)
|
|
|
|
(let ((language-name (plist-get language_info :name))
|
|
|
|
(language-version (plist-get language_info :version)))
|
|
|
|
(oset client buffer
|
|
|
|
(generate-new-buffer
|
|
|
|
(format "*jupyter-repl[%s]*"
|
|
|
|
(concat language-name " " language-version))))
|
|
|
|
(let ((jupyter-repl-current-client client))
|
|
|
|
(with-jupyter-repl-buffer client
|
|
|
|
(jupyter-repl-mode)
|
|
|
|
(jupyter-repl-insert-banner banner)
|
|
|
|
(jupyter-repl-insert-prompt 'in)))))))
|
|
|
|
|
2018-01-17 21:00:00 -06:00
|
|
|
;;;###autoload
|
|
|
|
(defun run-jupyter-repl (kernel-name &optional associate-buffer)
|
|
|
|
"Run a Jupyter REPL connected to a kernel with name, KERNEL-NAME.
|
2018-01-08 18:11:08 -06:00
|
|
|
KERNEL-NAME can be the prefix of an available kernel name, in
|
|
|
|
this case the first kernel in `jupyter-available-kernelspecs'
|
|
|
|
that has KERNEL-NAME as a prefix will be used to start a new
|
2018-01-17 21:00:00 -06:00
|
|
|
kernel.
|
|
|
|
|
|
|
|
Optional argument ASSOCIATE-BUFFER, if non-nil, means to enable
|
|
|
|
`jupyter-repl-interaction-mode' in the `current-buffer' and
|
|
|
|
associate it with the REPL created. When called interactivel,
|
|
|
|
ASSOCIATE-BUFFER is set to t unless `current-prefix-arg' is
|
|
|
|
non-nil. If the `current-buffer's `major-mode' does not
|
|
|
|
correspond to the language of the kernel started,
|
|
|
|
ASSOCIATE-BUFFER has no effect."
|
|
|
|
(interactive (list (jupyter-completing-read-kernelspec)
|
|
|
|
(not current-prefix-arg)))
|
2018-01-18 22:07:09 -06:00
|
|
|
(setq kernel-name (car (if (called-interactively-p 'interactive)
|
|
|
|
kernel-name
|
|
|
|
(jupyter-find-kernelspec kernel-name))))
|
|
|
|
(unless kernel-name
|
|
|
|
(error "No kernel found for prefix (%s)" kernel-name))
|
|
|
|
(cl-destructuring-bind (_manager . client)
|
2018-01-06 16:46:12 -06:00
|
|
|
(jupyter-start-new-kernel kernel-name 'jupyter-repl-client)
|
2018-01-18 22:07:09 -06:00
|
|
|
(jupyter-repl--new-repl client)
|
2018-01-17 21:00:00 -06:00
|
|
|
(when (and associate-buffer
|
2018-01-18 22:07:09 -06:00
|
|
|
(jupyter-repl-same-lang-mode-p
|
|
|
|
(current-buffer) client))
|
|
|
|
(jupyter-repl-associate-buffer client))
|
|
|
|
(pop-to-buffer (oref client buffer))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-08 21:38:32 -06:00
|
|
|
(provide 'jupyter-repl-client)
|
|
|
|
|
|
|
|
;;; jupyter-repl-client.el ends here
|
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
;; Local Variables:
|
|
|
|
;; byte-compile-warnings: (not free-vars)
|
|
|
|
;; End:
|