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
|
|
|
|
|
|
|
|
;; 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:
|
|
|
|
|
2018-02-04 18:18:19 -06:00
|
|
|
;; A Jupyter REPL for Emacs.
|
2018-01-08 21:38:32 -06:00
|
|
|
;;
|
2018-02-04 18:18:19 -06:00
|
|
|
;; The main entry points are `run-jupyter-repl' and `connect-jupyter-repl'.
|
2018-05-12 14:52:35 -05:00
|
|
|
;;
|
|
|
|
;; When called interactively, `run-jupyter-repl' asks for a kernel to start
|
|
|
|
;; (based on the kernels found using `jupyter-available-kernelspecs'), connects
|
|
|
|
;; a `jupyter-repl-client' to the selected kernel, and pops up a REPL buffer.
|
2018-06-14 21:07:22 -05:00
|
|
|
;; The main difference of `connect-jupyter-repl' is that it will obtain the
|
|
|
|
;; kernel's connection info by asking for the JSON file containing it to start
|
|
|
|
;; connection to a kernel.
|
2018-05-12 14:52:35 -05:00
|
|
|
;;
|
|
|
|
;; Additionally, `jupyter-repl-associate-buffer' associates the
|
|
|
|
;; `current-buffer' with a REPL client appropriate for the buffer's
|
|
|
|
;; `major-mode'. Associating a buffer with a REPL client enables the minor mode
|
|
|
|
;; `jupyter-repl-interaction-mode' and, if `company-mode' is installed, enables
|
2018-06-14 21:07:22 -05:00
|
|
|
;; code completion using the associated REPL client.
|
2018-05-12 14:52:35 -05:00
|
|
|
;;
|
|
|
|
;; `jupyter-repl-interaction-mode' adds the following keybindings for
|
|
|
|
;; interacing a REPL client:
|
|
|
|
;;
|
|
|
|
;; C-c C-c `jupyter-repl-eval-line-or-region'
|
|
|
|
;; C-c C-l `jupyter-repl-eval-file'
|
2018-10-02 12:03:04 -05:00
|
|
|
;; C-c C-f `jupyter-inspect-at-point'
|
2018-05-12 14:52:35 -05:00
|
|
|
;; C-c C-r `jupyter-repl-restart-kernel'
|
|
|
|
;; C-c C-i `jupyter-repl-interrupt-kernel'
|
|
|
|
;; C-c C-z `jupyter-repl-pop-to-buffer'
|
2018-01-08 21:38:32 -06:00
|
|
|
|
|
|
|
;;; 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-05-22 21:58:33 -05:00
|
|
|
(require 'jupyter-widget-client)
|
2018-01-04 23:03:18 -06:00
|
|
|
(require 'jupyter-kernel-manager)
|
2017-12-27 21:52:19 -06:00
|
|
|
(require 'shr)
|
2018-01-13 22:53:19 -06:00
|
|
|
(require 'ring)
|
2018-05-30 22:15:17 -05:00
|
|
|
(require 'ansi-color)
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-22 19:42:36 -06:00
|
|
|
(declare-function company-begin-backend "company" (backend &optional callback))
|
|
|
|
(declare-function company-doc-buffer "company" (&optional string))
|
2018-05-30 22:15:17 -05:00
|
|
|
(declare-function company-grab-symbol-cons "company" (idle-begin-after-re &optional max-len))
|
2018-01-22 19:42:36 -06:00
|
|
|
(declare-function org-format-latex "org" (prefix &optional beg end dir overlays msg forbuffer processing-type))
|
2018-05-30 22:15:17 -05:00
|
|
|
(declare-function markdown-link-at-pos "markdown-mode" (pos))
|
|
|
|
(declare-function markdown-follow-link-at-point "markdown-mode")
|
2018-01-22 19:42:36 -06:00
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
;; TODO: Fallbacks for when the language doesn't have a major mode installed.
|
|
|
|
|
2018-02-04 18:18:19 -06:00
|
|
|
;; TODO: Define `jupyter-kernel-manager-after-restart-hook' to update the
|
2018-05-15 17:47:33 -05:00
|
|
|
;; execution count after a restart. More generally, define more ways to hook
|
|
|
|
;; into differnt events of the client/kernel interaction.
|
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."
|
2018-05-26 20:02:16 -05:00
|
|
|
:type 'integer
|
2017-12-23 15:34:28 -06:00
|
|
|
:group 'jupyter-repl)
|
|
|
|
|
2018-01-17 21:04:22 -06:00
|
|
|
(defcustom jupyter-repl-maximum-is-complete-timeout 2
|
2018-02-12 11:03:41 -06:00
|
|
|
"Maximum number of seconds to wait for an is-complete reply.
|
|
|
|
When no is-complete reply is received from the kernel within this
|
|
|
|
timeout, the built-in is-complete handler is used."
|
2018-05-26 20:02:16 -05:00
|
|
|
:type 'integer
|
2018-01-17 21:04:22 -06:00
|
|
|
: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."
|
2018-05-26 20:02:16 -05:00
|
|
|
:type 'integer
|
2018-01-13 02:17:26 -06:00
|
|
|
: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."
|
2018-05-26 20:02:16 -05:00
|
|
|
:type 'integer
|
2018-01-13 22:59:14 -06:00
|
|
|
:group 'jupyter-repl)
|
|
|
|
|
2018-01-13 23:18:15 -06:00
|
|
|
;;; Implementation
|
|
|
|
|
2018-05-22 21:58:33 -05:00
|
|
|
(defclass jupyter-repl-client (jupyter-widget-client)
|
2018-05-26 20:04:02 -05:00
|
|
|
((buffer
|
|
|
|
:type (or null buffer)
|
|
|
|
:initform nil
|
|
|
|
:documentation "The REPL buffer whose
|
2018-09-16 23:01:54 -05:00
|
|
|
`jupyter-current-client' is this client.")
|
2018-05-22 21:58:33 -05:00
|
|
|
(wait-to-clear
|
2018-06-14 21:07:22 -05:00
|
|
|
:type boolean
|
|
|
|
:initform nil
|
2018-05-22 21:58:33 -05:00
|
|
|
:documentation "Whether or not we should wait to clear the
|
|
|
|
current output of the cell. Set when the kernel sends a
|
|
|
|
`:clear-output' message.")
|
2018-05-26 20:04:02 -05:00
|
|
|
(execution-state
|
|
|
|
:type string
|
|
|
|
:initform "idle"
|
|
|
|
:documentation "The current state of the kernel. Can be
|
|
|
|
either \"idle\", \"busy\", or \"starting\".")
|
|
|
|
(execution-count
|
|
|
|
:type integer
|
|
|
|
:initform 1
|
|
|
|
:documentation "The current execution count of the kernel.")))
|
2018-01-17 21:22:12 -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-10-01 23:09:48 -05:00
|
|
|
(make-variable-buffer-local 'jupyter-repl-lang-buffer)
|
|
|
|
|
2018-01-11 12:12:52 -06:00
|
|
|
(defvar jupyter-repl-lang-mode nil
|
2018-10-01 23:09:48 -05:00
|
|
|
"The `major-mode' corresponding to the REPL's language.")
|
|
|
|
|
|
|
|
(make-variable-buffer-local 'jupyter-repl-lang-mode)
|
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-09-05 09:50:34 -05:00
|
|
|
(defvar-local jupyter-repl-use-builtin-is-complete nil
|
2018-05-26 20:04:02 -05: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-05-26 17:45:41 -05:00
|
|
|
(defvar jupyter-repl-display-ids nil
|
|
|
|
"A hash table of display IDs.
|
|
|
|
Display IDs are implemented by setting the text property,
|
|
|
|
`jupyter-display', to the display ID requested by a
|
|
|
|
`:display-data' message. When a display is updated from an
|
|
|
|
`:update-display-data' message, the display ID from the initial
|
2018-05-26 20:04:02 -05:00
|
|
|
`:display-data' message is retrieved from this table and used to
|
|
|
|
find the display in the REPL buffer. See
|
|
|
|
`jupyter-repl-update-display'.")
|
2018-05-26 17:45:41 -05:00
|
|
|
|
2018-09-18 12:28:42 -05:00
|
|
|
(cl-generic-define-context-rewriter jupyter-repl-mode (mode &rest modes)
|
|
|
|
`(jupyter-repl-lang-mode (derived-mode ,mode ,@modes)))
|
|
|
|
|
2018-01-16 11:34:22 -06:00
|
|
|
;;; Macros
|
2017-12-27 21:55:58 -06:00
|
|
|
|
2018-10-02 22:09:59 -05:00
|
|
|
(defmacro jupyter-with-repl-buffer (client &rest body)
|
|
|
|
"Switch to CLIENT's REPL buffer before running BODY.
|
2018-01-08 18:11:08 -06:00
|
|
|
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."
|
2018-09-30 23:56:33 -05:00
|
|
|
(declare (debug (&rest form)))
|
2018-01-13 23:07:07 -06:00
|
|
|
`(let ((inhibit-modification-hooks t))
|
|
|
|
,@body))
|
|
|
|
|
2018-09-30 23:56:33 -05:00
|
|
|
;; Taken from `eshell-handle-control-codes'
|
|
|
|
(defun jupyter-repl-handle-control-codes (beg end)
|
|
|
|
"Handle any control sequences between BEG and END."
|
|
|
|
(save-excursion
|
|
|
|
(goto-char beg)
|
|
|
|
(while (< (point) end)
|
|
|
|
(let ((char (char-after)))
|
|
|
|
(cond
|
|
|
|
((eq char ?\r)
|
|
|
|
(if (< (1+ (point)) end)
|
|
|
|
(if (memq (char-after (1+ (point)))
|
|
|
|
'(?\n ?\r))
|
|
|
|
(delete-char 1)
|
|
|
|
(let ((end (1+ (point))))
|
|
|
|
(beginning-of-line)
|
|
|
|
(delete-region (point) end)))
|
|
|
|
(add-text-properties (point) (1+ (point))
|
|
|
|
'(invisible t))
|
|
|
|
(forward-char)))
|
|
|
|
((eq char ?\a)
|
|
|
|
(delete-char 1)
|
|
|
|
(beep))
|
|
|
|
((eq char ?\C-h)
|
|
|
|
(delete-region (1- (point)) (1+ (point))))
|
|
|
|
(t
|
|
|
|
(forward-char)))))))
|
|
|
|
|
2018-09-10 00:21:06 -05:00
|
|
|
(defmacro jupyter-repl-append-output (client req &rest body)
|
2018-05-25 20:42:59 -05:00
|
|
|
"Switch to CLIENT's buffer, move to the end of REQ, and run BODY.
|
2018-06-14 21:07:22 -05:00
|
|
|
REQ is a `jupyter-request' previously made using CLIENT, a
|
|
|
|
`jupyter-repl-client'.
|
|
|
|
|
|
|
|
`point' is moved to the `jupyter-repl-cell-beginning-position' of
|
|
|
|
the cell *after* REQ, this position is where any newly generated
|
2018-09-30 23:56:33 -05:00
|
|
|
output of REQ should be inserted.
|
|
|
|
|
|
|
|
Also handles any terminal control codes in the appended output."
|
2017-12-31 10:11:50 -06:00
|
|
|
(declare (indent 2) (debug (symbolp &rest form)))
|
2018-10-02 22:09:59 -05:00
|
|
|
`(jupyter-with-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)
|
2018-09-30 23:56:33 -05:00
|
|
|
(let ((beg (point-marker))
|
|
|
|
(end (point-marker)))
|
|
|
|
(set-marker-insertion-type end t)
|
|
|
|
,@body
|
|
|
|
(jupyter-repl-handle-control-codes beg end)
|
|
|
|
(set-marker beg nil)
|
|
|
|
(set-marker end nil))))))
|
2017-12-27 21:52:19 -06:00
|
|
|
|
2018-10-02 22:09:59 -05:00
|
|
|
(defmacro jupyter-with-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)))
|
2018-09-17 19:15:28 -05:00
|
|
|
(let ((client (make-symbol "clientvar")))
|
|
|
|
`(let ((,client jupyter-current-client))
|
|
|
|
(with-current-buffer jupyter-repl-lang-buffer
|
|
|
|
(let ((inhibit-read-only t)
|
|
|
|
(jupyter-current-client ,client))
|
|
|
|
(erase-buffer)
|
|
|
|
,@body)))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-10-02 22:09:59 -05:00
|
|
|
(defmacro jupyter-with-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
|
2018-06-14 21:07:22 -05:00
|
|
|
be at the `jupyter-repl-cell-code-beginning-position'."
|
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-10-02 12:03:04 -05:00
|
|
|
;; TODO: Rename to `jupyter-get-doc-buffer' since they are
|
|
|
|
;; not limited to the REPL.
|
2018-10-02 22:09:59 -05:00
|
|
|
(defun jupyter-get-doc-buffer (name)
|
2018-01-14 14:32:33 -06:00
|
|
|
"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>).
|
|
|
|
|
2018-06-14 21:07:22 -05:00
|
|
|
The buffer returned will have a `buffer-name' of
|
|
|
|
\"*jupyter-repl-NAME*\". If a buffer with this name already
|
|
|
|
exists, it is returned."
|
2018-01-16 11:34:22 -06:00
|
|
|
(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))
|
|
|
|
|
2018-10-02 22:09:59 -05:00
|
|
|
(defmacro jupyter-with-doc-buffer (name &rest body)
|
2018-01-14 14:32:33 -06:00
|
|
|
"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
|
2018-09-10 00:22:20 -05:00
|
|
|
obtained by a call to `jupyter-repl-get-doc-buffer'. Before
|
2018-01-14 14:32:33 -06:00
|
|
|
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")))
|
2018-10-02 22:09:59 -05:00
|
|
|
`(let ((,buffer (jupyter-get-doc-buffer ,name)))
|
2018-08-26 15:28:05 -05:00
|
|
|
(setq other-window-scroll-buffer ,buffer)
|
2018-01-16 11:18:54 -06:00
|
|
|
(with-current-buffer ,buffer
|
2018-08-26 15:28:05 -05:00
|
|
|
(let ((inhibit-read-only t))
|
2018-01-14 14:32:33 -06:00
|
|
|
(erase-buffer)
|
|
|
|
,@body)))))
|
|
|
|
|
2018-02-09 17:21:10 -06:00
|
|
|
;;; Convenience functions
|
|
|
|
|
2018-06-14 21:07:22 -05:00
|
|
|
(defun jupyter-repl-language-mode (client)
|
|
|
|
"Return the `major-mode' of CLIENT's kernel language."
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-02-09 17:21:10 -06:00
|
|
|
jupyter-repl-lang-mode))
|
|
|
|
|
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-09-18 14:10:02 -05:00
|
|
|
(jupyter-repl-fixup-font-lock-properties start end object)
|
2018-01-12 18:34:10 -06:00
|
|
|
(add-text-properties
|
2018-09-18 14:10:02 -05:00
|
|
|
start end '(fontified t font-lock-fontified t font-lock-multiline t) object))
|
2018-01-12 18:34:10 -06:00
|
|
|
|
2018-09-17 20:09:52 -05:00
|
|
|
(defun jupyter-repl-fixup-font-lock-properties (beg end &optional object)
|
|
|
|
"Fixup the text properties in the `current-buffer' between BEG END.
|
|
|
|
If OBJECT is non-nil, fixup the text properties of OBJECT. Fixing
|
2018-09-18 14:10:02 -05:00
|
|
|
the text properties involves substituting any `face' property
|
|
|
|
with `font-lock-face' for insertion into the REPL buffer."
|
2018-10-01 23:09:11 -05:00
|
|
|
(let ((next beg) val)
|
|
|
|
(while (/= beg end)
|
|
|
|
(setq val (get-text-property beg 'face object)
|
|
|
|
next (next-single-property-change beg 'face object end))
|
|
|
|
(remove-text-properties beg next '(face) object)
|
|
|
|
(put-text-property beg next 'font-lock-face (or val 'default) object)
|
2018-09-17 20:09:52 -05:00
|
|
|
(setq beg next))))
|
2018-01-17 21:23:52 -06:00
|
|
|
|
2018-01-12 18:28:23 -06:00
|
|
|
(defun jupyter-repl-get-fontify-buffer (mode)
|
2018-06-14 21:07:22 -05:00
|
|
|
"Return the buffer used to fontify text for MODE.
|
|
|
|
Retrieve the buffer for MODE from `jupyter-repl-fontify-buffers'.
|
|
|
|
If no buffer for MODE exists, create a new one."
|
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
|
|
|
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)
|
2018-09-17 20:09:52 -05:00
|
|
|
(font-lock-ensure))
|
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)))
|
2018-05-06 10:51:18 -05:00
|
|
|
(fill-region (point-min) (point-max) t 'nosqueeze))
|
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.
|
|
|
|
|
2018-09-05 23:32:11 -05:00
|
|
|
- `:inherit' :: A non-nil value will use
|
2017-12-27 21:13:23 -06:00
|
|
|
`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)))
|
2018-09-05 23:32:11 -05:00
|
|
|
(:inherit
|
2017-12-27 21:13:23 -06:00
|
|
|
(setq insert-fun (if (cadr args) #'insert-and-inherit #'insert)))
|
|
|
|
(otherwise
|
2018-09-05 23:32:11 -05:00
|
|
|
(error "Keyword not one of `:read-only', `:properties', `:inherit-' (`%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"))
|
|
|
|
|
2018-05-13 09:07:56 -05:00
|
|
|
;;; Handling rich output
|
|
|
|
|
2018-05-26 17:43:35 -05:00
|
|
|
(defvar jupyter-repl-graphic-mimetypes '(:image/png :image/svg+xml :text/latex)
|
|
|
|
"Mimetypes that display graphics in the REPL buffer.")
|
|
|
|
|
|
|
|
(defun jupyter-repl-graphic-data-p (msg)
|
|
|
|
"Check to see if MSG has mimetypes for graphics."
|
|
|
|
(cl-loop
|
|
|
|
with graphic-types = jupyter-repl-graphic-mimetypes
|
|
|
|
for (mimetype _value) on (jupyter-message-get msg :data) by #'cddr
|
|
|
|
thereis (memq mimetype graphic-types)))
|
|
|
|
|
2017-12-27 22:17:38 -06:00
|
|
|
(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-09-17 20:09:52 -05:00
|
|
|
(jupyter-repl-fixup-font-lock-properties (point-min) (point-max))
|
2018-01-12 18:39:27 -06:00
|
|
|
(string-trim (buffer-string))))))
|
|
|
|
|
2018-05-06 10:51:45 -05:00
|
|
|
;; Markdown integration
|
|
|
|
|
2018-05-06 10:51:18 -05:00
|
|
|
(defvar markdown-hide-markup)
|
|
|
|
(defvar markdown-hide-urls)
|
|
|
|
(defvar markdown-fontify-code-blocks-natively)
|
2018-05-06 10:51:45 -05:00
|
|
|
|
|
|
|
(defvar jupyter-repl-markdown-mouse-map
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
(define-key map [return] 'jupyter-repl-markdown-follow-link-at-point)
|
|
|
|
(define-key map [follow-link] 'mouse-face)
|
|
|
|
(define-key map [mouse-2] 'jupyter-repl-markdown-follow-link-at-point)
|
|
|
|
map))
|
|
|
|
|
2018-09-17 19:15:28 -05:00
|
|
|
(cl-defgeneric jupyter-markdown-follow-link (_link-text _url _ref-label _title-text _bang)
|
|
|
|
"Follow the markdown link at `point'."
|
|
|
|
(markdown-follow-link-at-point))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-markdown-follow-link (link-text url _ref-label _title-text _bang
|
|
|
|
&context (jupyter-lang julia))
|
|
|
|
"Send a help query to the Julia REPL for LINK-TEXT if URL is \"@ref\".
|
|
|
|
Otherwise follow the link normally."
|
|
|
|
(if (string= url "@ref")
|
|
|
|
;; Links have the form `fun`
|
|
|
|
(let ((fun (substring link-text 1 -1)))
|
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-replace-cell-code (concat "?" fun))
|
|
|
|
(jupyter-repl-ret))
|
|
|
|
(cl-call-next-method)))
|
|
|
|
|
2018-05-06 10:51:45 -05:00
|
|
|
(defun jupyter-repl-markdown-follow-link-at-point ()
|
|
|
|
"Handle markdown links specially."
|
|
|
|
(interactive)
|
|
|
|
(let ((link (markdown-link-at-pos (point))))
|
2018-09-17 19:15:28 -05:00
|
|
|
(when (car link)
|
|
|
|
(apply #'jupyter-markdown-follow-link (cddr link)))))
|
2018-05-06 10:51:45 -05:00
|
|
|
|
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-05-06 10:51:45 -05:00
|
|
|
(let ((pos (point)))
|
|
|
|
(jupyter-repl-insert
|
|
|
|
(let ((markdown-hide-markup t)
|
|
|
|
(markdown-hide-urls t)
|
|
|
|
(markdown-fontify-code-blocks-natively t))
|
|
|
|
(jupyter-repl-fontify-according-to-mode 'markdown-mode text)))
|
|
|
|
;; Update keymaps
|
|
|
|
(let ((limit (point)) next)
|
|
|
|
(setq pos (next-single-property-change pos 'keymap nil limit))
|
|
|
|
(while (/= pos limit)
|
|
|
|
(setq next (next-single-property-change pos 'keymap nil limit))
|
|
|
|
(when (eq (get-text-property pos 'keymap) markdown-mode-mouse-map)
|
|
|
|
(put-text-property pos next 'keymap jupyter-repl-markdown-mouse-map))
|
|
|
|
(setq pos next)))))
|
2017-12-27 22:17:38 -06:00
|
|
|
|
2018-05-06 10:51:18 -05:00
|
|
|
(defvar org-format-latex-options)
|
2018-09-17 20:10:54 -05:00
|
|
|
(defvar org-preview-latex-image-directory)
|
2018-05-06 10:51:18 -05:00
|
|
|
|
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."
|
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
|
2018-09-17 20:10:54 -05:00
|
|
|
org-preview-latex-image-directory
|
|
|
|
beg end org-babel-jupyter-resource-directory
|
2017-12-27 22:17:38 -06:00
|
|
|
'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."
|
2018-05-13 09:09:13 -05:00
|
|
|
(setq text (ansi-color-apply text))
|
2018-01-16 11:17:10 -06:00
|
|
|
(jupyter-repl-add-font-lock-properties 0 (length text) text)
|
2018-05-06 11:04:22 -05:00
|
|
|
;; NOTE: Mark text with a specific syntax class so that string characters do
|
|
|
|
;; not get registered as strings. This requires
|
|
|
|
;; `parse-sexp-lookup-properties' to be non-nil.
|
|
|
|
(add-text-properties 0 (length text) '(syntax-table (3)) text)
|
2018-01-16 11:17:10 -06:00
|
|
|
(jupyter-repl-insert text))
|
|
|
|
|
2018-05-26 17:43:35 -05:00
|
|
|
(defun jupyter-repl-insert-data (data metadata)
|
2018-01-22 19:35:52 -06:00
|
|
|
"Insert DATA into the REPL buffer in order of decreasing richness.
|
2018-05-27 22:37:30 -05:00
|
|
|
DATA is a plist mapping mimetypes to their content. METADATA is a
|
|
|
|
plist similar to data, but with values describing extra
|
|
|
|
information for inserting each kind of mimetype. For example the
|
|
|
|
value of `image/png' can be a plist with the keys `:width',
|
|
|
|
`:height'.
|
2018-01-22 19:35:52 -06:00
|
|
|
|
2018-05-27 22:37:30 -05:00
|
|
|
Attempt to insert a recognized mimetype, trying each one in order
|
|
|
|
of decreasing richness of the mimetype. The current order is
|
|
|
|
|
|
|
|
- application/vnd.jupyter.widget-view+json
|
2018-01-22 19:35:52 -06:00
|
|
|
- text/html
|
|
|
|
- text/markdown (only if `markdown-mode' is available)
|
|
|
|
- text/latex
|
|
|
|
- image/png
|
|
|
|
- image/svg+xml
|
|
|
|
- text/plain
|
|
|
|
|
2018-05-27 22:37:30 -05:00
|
|
|
As a special case, since Emacs is currently unable to embed a web
|
|
|
|
browser in a sufficient way, inserting a widget does not actually
|
|
|
|
insert it into the buffer. Instead the widget is displayed in a
|
|
|
|
browser.
|
2018-05-26 17:43:35 -05:00
|
|
|
|
2018-05-27 22:37:30 -05:00
|
|
|
When no valid mimetype is present in DATA, a warning is shown."
|
2018-01-18 16:35:16 -06:00
|
|
|
(let ((mimetypes (cl-loop
|
|
|
|
for (k d) on data by #'cddr
|
2018-05-26 17:43:35 -05:00
|
|
|
when
|
|
|
|
(and d (not (equal d ""))
|
|
|
|
(or (display-graphic-p)
|
|
|
|
(not (memq k jupyter-repl-graphic-mimetypes))))
|
2018-01-18 16:35:16 -06:00
|
|
|
collect k)))
|
2017-12-31 09:37:56 -06:00
|
|
|
(cond
|
2018-05-27 22:37:30 -05:00
|
|
|
((memq :application/vnd.jupyter.widget-view+json mimetypes)
|
|
|
|
(jupyter-widgets-display-model
|
2018-09-16 23:01:54 -05:00
|
|
|
jupyter-current-client
|
2018-05-27 22:37:30 -05:00
|
|
|
(plist-get (plist-get data :application/vnd.jupyter.widget-view+json)
|
|
|
|
:model_id)))
|
2018-05-13 09:07:56 -05:00
|
|
|
((and (memq :text/html mimetypes)
|
|
|
|
(functionp 'libxml-parse-html-region))
|
2018-01-22 19:35:52 -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)))
|
2018-05-13 09:07:56 -05:00
|
|
|
((and (memq :text/markdown mimetypes)
|
|
|
|
(require 'markdown-mode nil t))
|
2018-01-22 19:35:52 -06:00
|
|
|
(jupyter-repl-insert-markdown (plist-get data :text/markdown)))
|
2018-05-13 09:07:56 -05:00
|
|
|
((and (memq :text/latex mimetypes)
|
|
|
|
(require 'org nil t))
|
2018-01-22 19:35:52 -06:00
|
|
|
(jupyter-repl-insert-latex (plist-get data :text/latex))
|
|
|
|
(jupyter-repl-newline))
|
2017-12-31 09:37:56 -06:00
|
|
|
((memq :image/png mimetypes)
|
2018-05-26 17:43:35 -05:00
|
|
|
(cl-destructuring-bind (&key width height)
|
|
|
|
(plist-get metadata :image/png)
|
|
|
|
(let* ((data (base64-decode-string (plist-get data :image/png)))
|
|
|
|
(img (create-image data nil 'data :width width :height height)))
|
|
|
|
(insert-image img (propertize " " 'read-only t)))))
|
2018-05-13 09:07:56 -05:00
|
|
|
((and (memq :image/svg+xml mimetypes)
|
|
|
|
(image-type-available-p 'svg))
|
2018-05-26 17:43:35 -05:00
|
|
|
(cl-destructuring-bind (&key width height)
|
|
|
|
(plist-get metadata :image/svg+xml)
|
|
|
|
(let* ((data (plist-get data :image/svg+xml))
|
2018-10-04 18:31:44 -05:00
|
|
|
(img (create-image data 'svg 'data :width width :height height)))
|
2018-05-26 17:43:35 -05:00
|
|
|
(insert-image img (propertize " " 'read-only t)))))
|
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
|
|
|
|
2018-05-26 17:45:41 -05:00
|
|
|
(defun jupyter-repl-insert-data-with-id (display-id data metadata)
|
|
|
|
"Associate DISPLAY-ID with DATA when inserting DATA.
|
|
|
|
DATA and METADATA have the same meaning as in
|
2018-05-27 22:37:30 -05:00
|
|
|
`jupyter-repl-insert-data'.
|
|
|
|
|
|
|
|
Currently there is no support for associating a DISPLAY-ID if
|
|
|
|
DATA is displayed as a widget."
|
2018-05-26 17:45:41 -05:00
|
|
|
(unless jupyter-repl-display-ids
|
|
|
|
(setq-local jupyter-repl-display-ids
|
|
|
|
(make-hash-table :test #'equal
|
|
|
|
:weakness 'value)))
|
|
|
|
(let ((beg (point))
|
2018-05-27 22:37:30 -05:00
|
|
|
(id (gethash display-id jupyter-repl-display-ids))
|
|
|
|
(widget-p (memq :application/vnd.jupyter.widget-view+json data)))
|
|
|
|
(or id widget-p (setq id (puthash display-id
|
|
|
|
display-id
|
|
|
|
jupyter-repl-display-ids)))
|
2018-05-26 17:45:41 -05:00
|
|
|
(jupyter-repl-insert-data data metadata)
|
2018-05-27 22:37:30 -05:00
|
|
|
(unless widget-p
|
|
|
|
(put-text-property beg (point) 'jupyter-display id))))
|
2018-05-26 17:45:41 -05: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-06-14 21:07:22 -05:00
|
|
|
"Return the margin display value for a prompt STR.
|
|
|
|
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)))
|
|
|
|
|
2018-08-26 14:55:03 -05:00
|
|
|
(defun jupyter-repl--make-prompt (str face props)
|
|
|
|
"Make a prompt overlay for the character at `point'.
|
|
|
|
STR is used as the prompt string and FACE is its
|
|
|
|
`font-lock-face'. Add PROPS as text properties to the character."
|
2017-12-31 10:09:45 -06:00
|
|
|
(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)
|
2018-08-26 14:55:03 -05:00
|
|
|
(add-text-properties (overlay-start ov) (overlay-end ov) props)))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-insert-prompt (&optional type)
|
2018-06-14 21:07:22 -05:00
|
|
|
"Insert a REPL prompt according to TYPE.
|
2018-08-26 14:55:03 -05:00
|
|
|
TYPE can either be `in', `out', or `continuation'. A nil TYPE is
|
|
|
|
interpreted as `in'."
|
2017-12-23 15:34:28 -06:00
|
|
|
(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')"))
|
2018-01-22 19:34:28 -06:00
|
|
|
(jupyter-repl-without-continuation-prompts
|
2018-08-26 14:55:03 -05:00
|
|
|
(let ((inhibit-read-only t))
|
2018-09-09 21:33:05 -05:00
|
|
|
;; The newline that `jupyter-repl--make-prompt' will overlay.
|
2018-08-26 14:55:03 -05:00
|
|
|
(jupyter-repl-newline)
|
|
|
|
(overlay-recenter (point))
|
2018-01-22 19:34:28 -06:00
|
|
|
(cond
|
|
|
|
((eq type 'in)
|
2018-09-16 23:01:54 -05:00
|
|
|
(let ((count (oref jupyter-current-client execution-count)))
|
2018-08-26 14:55:03 -05:00
|
|
|
(jupyter-repl--make-prompt
|
|
|
|
(format "In [%d] " count) 'jupyter-repl-input-prompt
|
|
|
|
`(jupyter-cell (beginning ,count))))
|
|
|
|
;; Prevent prompt overlay from inheriting text properties of code at the
|
|
|
|
;; beginning of a cell.
|
|
|
|
;;
|
|
|
|
;; rear-nonsticky is to prevent code inserted after this character to
|
|
|
|
;; inherit any of its text properties.
|
2018-01-22 19:34:28 -06:00
|
|
|
;;
|
2018-08-26 14:55:03 -05:00
|
|
|
;; front-sticky is to prevent `point' from being trapped between the
|
|
|
|
;; newline of the prompt overlay and this invisible character.
|
2018-01-22 19:34:28 -06:00
|
|
|
;;
|
2018-08-26 14:55:03 -05:00
|
|
|
;; field is so that text motions will not move past this invisible
|
2018-01-22 19:34:28 -06:00
|
|
|
;; character.
|
|
|
|
(jupyter-repl-insert
|
2018-09-05 23:32:11 -05:00
|
|
|
:properties '(invisible t rear-nonsticky t front-sticky t) " "))
|
2018-01-22 19:34:28 -06:00
|
|
|
((eq type 'out)
|
|
|
|
;; 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
|
2018-05-30 11:07:49 -05:00
|
|
|
;; beginning of the next cell after the request which is why we get the
|
|
|
|
;; cell count of the previous cell
|
|
|
|
(let ((count (save-excursion
|
|
|
|
(jupyter-repl-previous-cell)
|
|
|
|
(jupyter-repl-cell-count))))
|
2018-08-26 14:55:03 -05:00
|
|
|
(jupyter-repl--make-prompt
|
|
|
|
(format "Out [%d] " count) 'jupyter-repl-output-prompt
|
|
|
|
`(jupyter-cell (out ,count))))
|
|
|
|
;; See the note above about the invisible character for input prompts
|
2018-01-22 19:34:28 -06:00
|
|
|
(jupyter-repl-insert
|
2018-05-06 10:55:19 -05:00
|
|
|
:properties '(invisible t front-sticky t) " "))
|
2018-01-22 19:34:28 -06:00
|
|
|
((eq type 'continuation)
|
2018-08-26 14:55:03 -05:00
|
|
|
(jupyter-repl--make-prompt
|
|
|
|
" " 'jupyter-repl-input-prompt
|
|
|
|
`(read-only nil rear-nonsticky t))
|
|
|
|
;; Prevent prompt overlay from inheriting text properities of cell code.
|
|
|
|
;; See the note for input prompts.
|
|
|
|
(jupyter-repl-insert
|
|
|
|
:read-only nil
|
2018-09-05 23:32:11 -05:00
|
|
|
:properties '(invisible t rear-nonsticky t front-sticky t) " "))))))
|
2017-12-31 10:09:45 -06:00
|
|
|
|
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-06-14 21:07:22 -05:00
|
|
|
"Mark the current cell as busy."
|
2018-05-27 23:29:58 -05: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-06-14 21:07:22 -05:00
|
|
|
"Un-mark the current cell as busy."
|
2018-01-04 17:22:30 -06:00
|
|
|
(jupyter-repl-cell-update-prompt
|
2018-05-27 23:29:58 -05:00
|
|
|
(format "In [%d] " (jupyter-repl-cell-count))))
|
2017-12-31 10:09:45 -06:00
|
|
|
|
2018-05-30 11:07:49 -05:00
|
|
|
(defun jupyter-repl-cell-count ()
|
2018-06-14 21:07:22 -05:00
|
|
|
"Return the cell count of the cell at `point'."
|
2018-05-30 11:07:49 -05:00
|
|
|
(let ((pos (if (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-06-14 21:07:22 -05:00
|
|
|
"Return 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,
|
2018-06-14 21:07:22 -05:00
|
|
|
return `point'.
|
|
|
|
|
|
|
|
If the end of a cell is found before the beginning of one, 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"))
|
2018-02-09 17:18:29 -06:00
|
|
|
(if (jupyter-repl-cell-beginning-p (point-min))
|
|
|
|
(setq pos (point-min))
|
|
|
|
(signal 'beginning-of-buffer nil))))
|
2017-12-27 21:25:29 -06:00
|
|
|
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
|
2018-06-14 21:07:22 -05:00
|
|
|
end.
|
|
|
|
|
|
|
|
Note: 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)
|
2018-06-14 21:07:22 -05:00
|
|
|
"Go to the beginning of the next cell.
|
|
|
|
Move N times where N defaults to 1. Return the count of cells
|
|
|
|
left to move."
|
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-06-14 21:07:22 -05:00
|
|
|
"Go to the beginning of the previous cell.
|
|
|
|
Move N times where N defaults to 1. Return the count of cells
|
|
|
|
left to move.
|
|
|
|
|
|
|
|
Note, if `point' is not at the beginning of the current cell, the
|
|
|
|
first move is to the beginning of the current cell."
|
2018-01-16 11:11:30 -06:00
|
|
|
(or N (setq N 1))
|
|
|
|
(catch 'done
|
2018-08-27 20:41:59 -05:00
|
|
|
(let ((starting-pos (point)))
|
|
|
|
(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))
|
|
|
|
;; Handle edge case when the first cell is at the beginning of the
|
|
|
|
;; buffer. This happens, for example, when erasing the buffer.
|
|
|
|
(when (and (/= (point) starting-pos)
|
|
|
|
(jupyter-repl-cell-beginning-p (point)))
|
|
|
|
(setq N (1- N)))
|
|
|
|
(throw 'done t))))))
|
2018-01-16 11:11:30 -06:00
|
|
|
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
|
2018-05-15 23:40:09 -05:00
|
|
|
`jupyter-send-execute-request's created by a cell in the
|
2018-01-13 23:02:12 -06:00
|
|
|
`current-buffer'. Note that the `current-buffer' is assumed to be
|
|
|
|
a Jupyter REPL buffer."
|
|
|
|
(goto-char (point-max))
|
2018-02-09 17:18:29 -06:00
|
|
|
(unless (catch 'done
|
|
|
|
(while (= (jupyter-repl-previous-cell) 0)
|
|
|
|
(when (eq (jupyter-repl-cell-request) req)
|
|
|
|
(throw 'done t))))
|
2018-01-17 21:29:54 -06:00
|
|
|
(error "Cell for request not found")))
|
2018-01-13 23:02:12 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-forward-cell (&optional arg)
|
2018-06-14 21:07:22 -05:00
|
|
|
"Go to the code beginning of the cell after the current one.
|
2018-01-13 23:02:12 -06:00
|
|
|
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)
|
2018-06-14 21:07:22 -05:00
|
|
|
"Go to the code beginning of the cell before the current one.
|
2018-01-13 23:02:12 -06:00
|
|
|
ARG is the number of cells to move and defaults to 1."
|
|
|
|
(interactive "^p")
|
|
|
|
(or arg (setq arg 1))
|
2018-05-06 11:55:02 -05:00
|
|
|
;; Ignore the case when `point' is in the output of a cell, in this case
|
|
|
|
;; `jupyter-repl-previous-cell' will go to the previous cell.
|
|
|
|
(ignore-errors (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)))
|
2018-08-27 20:41:24 -05:00
|
|
|
(or (= pos (point-max))
|
|
|
|
(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?"
|
2018-05-30 22:17:43 -05:00
|
|
|
(string-match-p "\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-02-06 17:45:12 -06:00
|
|
|
(let ((pos (point)))
|
2018-02-12 10:56:12 -06:00
|
|
|
(ignore-errors
|
|
|
|
(save-excursion
|
|
|
|
(unless (= pos (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 ()
|
2018-06-14 21:07:22 -05:00
|
|
|
"Has the current cell been finalized?"
|
2018-09-18 12:25:07 -05:00
|
|
|
(or (not (jupyter-repl-cell-line-p))
|
|
|
|
(/= (jupyter-repl-cell-end-position) (point-max))))
|
2018-01-16 11:31:58 -06:00
|
|
|
|
2018-02-03 21:49:46 -06:00
|
|
|
(defun jupyter-repl-client-has-manager-p ()
|
2018-09-16 23:01:54 -05:00
|
|
|
"Does the `jupyter-current-client' have a `jupyter-kernel-manager'?"
|
|
|
|
(and jupyter-current-client
|
|
|
|
(oref jupyter-current-client manager)))
|
2018-02-03 21:49:46 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-connected-p ()
|
2018-09-16 23:01:54 -05:00
|
|
|
"Is the `jupyter-current-client' connected to its kernel?"
|
|
|
|
(when jupyter-current-client
|
2018-02-12 10:56:12 -06:00
|
|
|
(or (and (jupyter-repl-client-has-manager-p)
|
|
|
|
;; Check if the kernel is local
|
|
|
|
(jupyter-kernel-alive-p
|
2018-09-16 23:01:54 -05:00
|
|
|
(oref jupyter-current-client manager)))
|
|
|
|
(let ((hb (oref jupyter-current-client hb-channel)))
|
2018-02-12 10:56:12 -06:00
|
|
|
(and (jupyter-channel-alive-p hb)
|
|
|
|
(jupyter-hb-beating-p hb))))))
|
2018-02-03 21:49:46 -06:00
|
|
|
|
2018-08-27 20:46:58 -05:00
|
|
|
;;; Modifying cell code, truncating REPL buffer
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-09-30 23:57:05 -05:00
|
|
|
(defun jupyter-repl-filter-substring (beg end delete)
|
|
|
|
"Return text between BEG and END with invisible text removed.
|
|
|
|
If DELETE is non-nil, delete text between BEG and END also."
|
|
|
|
;; Include the last character at END. `buffer-substring'
|
|
|
|
;; excludes the last character.
|
|
|
|
(setq end (min (point-max) (1+ end)))
|
|
|
|
(let ((next beg)
|
|
|
|
(str ""))
|
|
|
|
(while (/= beg end)
|
|
|
|
(setq next (next-single-property-change beg 'invisible nil end))
|
|
|
|
(unless (invisible-p beg)
|
|
|
|
(setq str (concat str (buffer-substring beg next))))
|
|
|
|
(setq beg next))
|
|
|
|
(prog1 str
|
|
|
|
(when delete (delete-region beg end)))))
|
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
(defun jupyter-repl-cell-code ()
|
2018-08-26 14:55:03 -05:00
|
|
|
"Return the code of the current cell."
|
2018-09-30 23:57:05 -05:00
|
|
|
(filter-buffer-substring
|
|
|
|
(jupyter-repl-cell-code-beginning-position)
|
|
|
|
(jupyter-repl-cell-code-end-position)))
|
2017-12-27 21:46:15 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-cell-code-position ()
|
2018-06-14 21:07:22 -05:00
|
|
|
"Return the relative position of `point' with respect to the cell code."
|
2017-12-31 10:09:45 -06:00
|
|
|
(unless (jupyter-repl-cell-line-p)
|
2018-01-08 22:31:33 -06:00
|
|
|
(error "Not in code of cell"))
|
2018-05-15 16:20:53 -05:00
|
|
|
(1+ (- (point) (jupyter-repl-cell-code-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.
|
2018-06-14 21:07:22 -05:00
|
|
|
REQ is the `jupyter-request' to associate with the current cell."
|
2018-01-13 02:42:29 -06:00
|
|
|
(let ((beg (jupyter-repl-cell-beginning-position))
|
|
|
|
(count (jupyter-repl-cell-count)))
|
2017-12-31 10:09:45 -06:00
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-newline)
|
2018-01-22 19:46:10 -06:00
|
|
|
(put-text-property (1- (point)) (point) 'jupyter-cell `(end ,count))
|
|
|
|
(put-text-property beg (1+ beg) 'jupyter-request req)
|
2018-05-06 10:55:19 -05:00
|
|
|
;; Remove this property so that text can't be inserted at the start of the
|
|
|
|
;; cell or after any continuation prompts. See
|
|
|
|
;; `jupyter-repl-insert-prompt'.
|
|
|
|
(remove-text-properties beg (point) '(rear-nonsticky))
|
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
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position))
|
|
|
|
(delete-region (point) (jupyter-repl-cell-code-end-position))
|
2018-09-05 23:32:11 -05:00
|
|
|
(jupyter-repl-insert :inherit t :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'
|
2018-06-14 21:07:22 -05:00
|
|
|
lines, truncate it to something less than
|
2018-01-08 18:11:08 -06:00
|
|
|
`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
|
|
|
|
2018-05-15 23:40:09 -05:00
|
|
|
(cl-defmethod jupyter-send-execute-request ((client jupyter-repl-client)
|
|
|
|
&key code
|
|
|
|
(silent nil)
|
|
|
|
(store-history t)
|
|
|
|
(user-expressions nil)
|
|
|
|
(allow-stdin t)
|
|
|
|
(stop-on-error nil))
|
2018-06-14 21:02:28 -05:00
|
|
|
(if code (cl-call-next-method)
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-06-14 21:02:28 -05:00
|
|
|
(jupyter-repl-truncate-buffer)
|
2018-02-04 18:21:12 -06:00
|
|
|
(setq code (string-trim (jupyter-repl-cell-code)))
|
2017-12-31 10:09:45 -06:00
|
|
|
;; 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-22 20:36:14 -06:00
|
|
|
(defun jupyter-repl--handle-payload (payload)
|
|
|
|
"Do the client actions in PAYLOAD."
|
|
|
|
(cl-loop
|
2018-05-22 21:43:08 -05:00
|
|
|
for pl across payload
|
2018-01-22 20:36:14 -06:00
|
|
|
do (pcase (plist-get pl :source)
|
|
|
|
("page"
|
|
|
|
(let ((text (plist-get (plist-get pl :data) :text/plain))
|
|
|
|
(line (or (plist-get pl :start) 0)))
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-doc-buffer "pager"
|
2018-01-22 20:36:14 -06:00
|
|
|
(jupyter-repl-insert-ansi-coded-text text)
|
|
|
|
(goto-char (point-min))
|
|
|
|
(forward-line line)
|
|
|
|
(display-buffer (current-buffer)))))
|
|
|
|
((or "edit" "edit_magic")
|
|
|
|
(with-current-buffer (find-file-other-window
|
|
|
|
(plist-get pl :filename))
|
2018-08-30 14:26:18 -05:00
|
|
|
(goto-char (point-min))
|
2018-01-22 20:36:14 -06:00
|
|
|
(forward-line (plist-get pl :line_number))
|
|
|
|
(set-window-start (selected-window) (point))))
|
|
|
|
("set_next_input"
|
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-previous-cell)
|
|
|
|
(jupyter-repl-replace-cell-code (plist-get pl :text))))))
|
|
|
|
|
2018-01-02 00:38:46 -06:00
|
|
|
(cl-defmethod jupyter-handle-execute-reply ((client jupyter-repl-client)
|
2018-05-27 10:28:06 -05:00
|
|
|
req
|
2018-06-01 23:13:50 -05:00
|
|
|
_status
|
2018-01-02 00:38:46 -06:00
|
|
|
execution-count
|
2018-01-22 20:22:33 -06:00
|
|
|
_user-expressions
|
2018-01-02 00:38:46 -06:00
|
|
|
payload)
|
2017-12-28 09:45:03 -06:00
|
|
|
(oset client execution-count (1+ execution-count))
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-05-27 10:28:06 -05:00
|
|
|
(save-excursion
|
|
|
|
(jupyter-repl-goto-cell req)
|
|
|
|
(jupyter-repl-cell-unmark-busy))
|
2018-02-08 12:13:07 -06:00
|
|
|
(when payload
|
|
|
|
(jupyter-repl--handle-payload payload))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-execute-input ((client jupyter-repl-client)
|
2018-01-22 20:22:33 -06:00
|
|
|
_req
|
|
|
|
_code
|
2017-12-23 15:34:28 -06:00
|
|
|
execution-count)
|
|
|
|
(oset client execution-count (1+ execution-count)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-execute-result ((client jupyter-repl-client)
|
|
|
|
req
|
2018-01-22 20:22:33 -06:00
|
|
|
_execution-count
|
2017-12-23 15:34:28 -06:00
|
|
|
data
|
2018-05-26 17:43:35 -05:00
|
|
|
metadata)
|
2018-01-22 19:37:25 -06:00
|
|
|
;; Only handle our results
|
|
|
|
(when req
|
2018-09-10 00:21:06 -05:00
|
|
|
(jupyter-repl-append-output client req
|
2018-01-22 19:37:25 -06:00
|
|
|
(jupyter-repl-insert-prompt 'out)
|
2018-05-26 17:43:35 -05:00
|
|
|
(jupyter-repl-insert-data data metadata))))
|
2018-05-26 17:45:41 -05:00
|
|
|
|
|
|
|
(defun jupyter-repl-next-display-with-id (id)
|
2018-06-14 21:07:22 -05:00
|
|
|
"Go to the start of the next display matching ID.
|
2018-05-26 17:45:41 -05:00
|
|
|
Return non-nil if successful. If no display with ID is found,
|
|
|
|
return nil without moving `point'."
|
|
|
|
(let ((pos (next-single-property-change (point) 'jupyter-display)))
|
|
|
|
(while (and pos (not (eq (get-text-property pos 'jupyter-display) id)))
|
|
|
|
(setq pos (next-single-property-change pos 'jupyter-display)))
|
|
|
|
(and pos (goto-char pos))))
|
|
|
|
|
|
|
|
(defun jupyter-repl-update-display (id data metadata)
|
|
|
|
"Update the display with ID using DATA.
|
|
|
|
DATA and METADATA have the same meaning as in a `:display-data'
|
2018-06-14 21:07:22 -05:00
|
|
|
message."
|
|
|
|
;; Updating a display involves finding and clearing the data that is
|
|
|
|
;; currently associated with the ID and inserting DATA at the same location.
|
|
|
|
;; If multiple locations have the same display ID, all of them are updated.
|
|
|
|
;; Raise an error if no display with ID could be found.
|
2018-05-26 17:45:41 -05:00
|
|
|
(save-excursion
|
|
|
|
(goto-char (point-min))
|
|
|
|
(let (str)
|
|
|
|
(while (jupyter-repl-next-display-with-id id)
|
|
|
|
(or str (setq str (with-temp-buffer
|
|
|
|
(jupyter-repl-insert-data data metadata)
|
|
|
|
(put-text-property
|
|
|
|
(point-min) (point-max) 'jupyter-display id)
|
|
|
|
(buffer-string))))
|
|
|
|
(delete-region (point) (next-single-property-change
|
|
|
|
(point) 'jupyter-display))
|
|
|
|
(let ((beg (point)) ov)
|
|
|
|
(insert str)
|
|
|
|
(setq ov (make-overlay (1+ beg) (point)))
|
|
|
|
(overlay-put ov 'face 'secondary-selection)
|
|
|
|
(run-at-time 0.3 nil (lambda () (delete-overlay ov)))))
|
|
|
|
(when (= (point) (point-min))
|
|
|
|
(error "No display matching id (%s)" id)))))
|
|
|
|
|
|
|
|
;; NOTE: Info on display_id
|
|
|
|
;; https://github.com/jupyter/jupyter_client/issues/209
|
2017-12-31 09:37:56 -06:00
|
|
|
(cl-defmethod jupyter-handle-display-data ((client jupyter-repl-client)
|
2018-01-22 20:22:33 -06:00
|
|
|
req
|
|
|
|
data
|
2018-05-26 17:43:35 -05:00
|
|
|
metadata
|
2018-02-04 18:19:08 -06:00
|
|
|
transient)
|
2018-05-22 21:58:33 -05:00
|
|
|
(let ((clear (prog1 (oref client wait-to-clear)
|
|
|
|
(oset client wait-to-clear nil)))
|
2018-05-27 22:37:30 -05:00
|
|
|
(req (if (eq (jupyter-message-parent-type
|
|
|
|
(jupyter-request-last-message req))
|
|
|
|
:comm-msg)
|
|
|
|
;; For comm messages which produce a `:display-data' message,
|
|
|
|
;; the request is assumed to be the most recently completed
|
|
|
|
;; one.
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-05-27 22:37:30 -05:00
|
|
|
(save-excursion
|
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-previous-cell 2)
|
|
|
|
(jupyter-repl-cell-request)))
|
|
|
|
req)))
|
2018-09-10 00:21:06 -05:00
|
|
|
(jupyter-repl-append-output client req
|
2018-05-27 22:37:30 -05:00
|
|
|
(cl-destructuring-bind (&key display_id &allow-other-keys)
|
|
|
|
transient
|
|
|
|
(if display_id
|
|
|
|
(jupyter-repl-insert-data-with-id display_id data metadata)
|
|
|
|
(let ((inhibit-redisplay t))
|
|
|
|
(when clear
|
|
|
|
(jupyter-repl-clear-last-cell-output client)
|
|
|
|
;; Prevent slight flickering of prompt margin and text, this is
|
|
|
|
;; needed in addition to `inhibit-redisplay'. It also seems that
|
|
|
|
;; it can be placed anywhere within this let and it will prevent
|
|
|
|
;; flickering.
|
|
|
|
(sit-for 0.1 t))
|
|
|
|
(jupyter-repl-insert-data data metadata)))))))
|
2018-05-22 21:58:33 -05:00
|
|
|
|
2018-05-26 17:45:41 -05:00
|
|
|
(cl-defmethod jupyter-handle-update-display-data ((client jupyter-repl-client)
|
|
|
|
_req
|
|
|
|
data
|
|
|
|
metadata
|
|
|
|
transient)
|
|
|
|
(cl-destructuring-bind (&key display_id &allow-other-keys)
|
|
|
|
transient
|
|
|
|
(unless display_id
|
|
|
|
(error "No display ID in `:update-display-data' message"))
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-05-26 17:45:41 -05:00
|
|
|
(let ((id (gethash display_id jupyter-repl-display-ids)))
|
|
|
|
(unless id
|
|
|
|
(error "Display ID not found (%s)" id))
|
|
|
|
(jupyter-repl-update-display id data metadata)))))
|
|
|
|
|
2018-05-22 21:58:33 -05:00
|
|
|
(defun jupyter-repl-clear-last-cell-output (client)
|
|
|
|
"In CLIENT's REPL buffer, clear the output of the last completed cell."
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-05-22 21:58:33 -05:00
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-previous-cell 2)
|
|
|
|
(delete-region (1+ (jupyter-repl-cell-end-position))
|
|
|
|
(progn
|
|
|
|
(jupyter-repl-next-cell)
|
|
|
|
(point)))))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-handle-clear-output ((client jupyter-repl-client)
|
|
|
|
req
|
|
|
|
wait)
|
|
|
|
(unless (oset client wait-to-clear (eq wait t))
|
|
|
|
(cond
|
2018-05-27 14:49:05 -05:00
|
|
|
((eq (jupyter-message-parent-type
|
2018-05-22 21:58:33 -05:00
|
|
|
(jupyter-request-last-message req))
|
|
|
|
:comm-msg)
|
|
|
|
(with-current-buffer (get-buffer-create "*jupyter-repl-output*")
|
|
|
|
(erase-buffer)))
|
|
|
|
(t
|
|
|
|
(jupyter-repl-clear-last-cell-output client)))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-05-27 10:28:06 -05:00
|
|
|
(cl-defmethod jupyter-handle-status ((client jupyter-repl-client) _req execution-state)
|
|
|
|
(oset client execution-state execution-state))
|
2018-01-17 21:22:12 -06:00
|
|
|
|
2018-01-22 19:32:27 -06:00
|
|
|
(defvar jupyter-repl--output-marker nil)
|
|
|
|
|
|
|
|
(defun jupyter-repl-display-other-output (client stream text)
|
|
|
|
"Display output not originating from CLIENT.
|
|
|
|
STREAM is the name of a stream which will be used to select the
|
|
|
|
buffer to display TEXT."
|
|
|
|
(let* ((bname (buffer-name (oref client buffer)))
|
|
|
|
(inhibit-read-only t)
|
|
|
|
(stream-buffer
|
|
|
|
(concat (substring bname 0 (1- (length bname)))
|
|
|
|
"-" stream "*")))
|
|
|
|
(with-current-buffer (get-buffer-create stream-buffer)
|
|
|
|
(unless jupyter-repl--output-marker
|
|
|
|
(setq-local jupyter-repl--output-marker (set-marker (make-marker) (point-max))))
|
|
|
|
(goto-char jupyter-repl--output-marker)
|
|
|
|
(let ((pos (point)))
|
|
|
|
(jupyter-repl-insert-ansi-coded-text text)
|
|
|
|
(fill-region pos (point)))
|
|
|
|
(set-marker jupyter-repl--output-marker (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-stream ((client jupyter-repl-client) req name text)
|
2018-05-22 21:58:33 -05:00
|
|
|
(if (null req)
|
|
|
|
(jupyter-repl-display-other-output client name text)
|
|
|
|
(cond
|
2018-05-27 14:49:05 -05:00
|
|
|
((eq (jupyter-message-parent-type
|
2018-05-22 21:58:33 -05:00
|
|
|
(jupyter-request-last-message req))
|
|
|
|
:comm-msg)
|
|
|
|
(with-current-buffer (get-buffer-create "*jupyter-repl-output*")
|
2018-05-27 22:04:31 -05:00
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
(jupyter-repl-insert-ansi-coded-text text)
|
|
|
|
(display-buffer (current-buffer)))))
|
2018-05-22 21:58:33 -05:00
|
|
|
(t
|
2018-09-10 00:21:06 -05:00
|
|
|
(jupyter-repl-append-output client req
|
2018-05-22 21:58:33 -05:00
|
|
|
(jupyter-repl-insert-ansi-coded-text text))))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-05-26 19:50:01 -05:00
|
|
|
(defun jupyter-repl-fix-python-traceback-spacing (ename)
|
|
|
|
"Add spacing between the first occurance of ENAME and \"Traceback\".
|
|
|
|
Do this for the current cell."
|
|
|
|
(save-excursion
|
|
|
|
(jupyter-repl-previous-cell)
|
|
|
|
(when (and (search-forward ename nil t)
|
|
|
|
(looking-at "Traceback"))
|
|
|
|
(let ((len (- fill-column
|
|
|
|
jupyter-repl-prompt-margin-width
|
|
|
|
(- (point) (line-beginning-position))
|
|
|
|
(- (line-end-position) (point)))))
|
|
|
|
(jupyter-repl-insert
|
|
|
|
(make-string (if (> len 4) len 4) ? ))))))
|
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
(cl-defmethod jupyter-handle-error ((client jupyter-repl-client)
|
2018-08-27 20:46:03 -05:00
|
|
|
req ename _evalue traceback)
|
2018-05-26 19:50:01 -05:00
|
|
|
(when req
|
|
|
|
(setq traceback (concat (mapconcat #'identity traceback "\n") "\n"))
|
|
|
|
(cond
|
|
|
|
((eq (jupyter-message-parent-type
|
|
|
|
(jupyter-request-last-message req))
|
|
|
|
:comm-msg)
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-doc-buffer "traceback"
|
2018-05-26 19:50:01 -05:00
|
|
|
(jupyter-repl-insert-ansi-coded-text traceback)
|
2018-09-30 23:57:05 -05:00
|
|
|
(goto-char (point-min))
|
2018-05-26 19:50:01 -05:00
|
|
|
(pop-to-buffer (current-buffer))))
|
|
|
|
(t
|
2018-09-10 00:21:06 -05:00
|
|
|
(jupyter-repl-append-output client req
|
2018-05-26 19:50:01 -05:00
|
|
|
(jupyter-repl-insert-ansi-coded-text traceback)
|
2018-09-16 22:53:18 -05:00
|
|
|
(when (equal (jupyter-kernel-language client) "python")
|
2018-05-26 19:50:01 -05:00
|
|
|
(jupyter-repl-fix-python-traceback-spacing ename)))))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-22 20:22:33 -06:00
|
|
|
(cl-defmethod jupyter-handle-input-reply ((client jupyter-repl-client) req prompt _password)
|
2018-09-10 00:21:06 -05:00
|
|
|
(jupyter-repl-append-output 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-06-14 20:57:02 -05:00
|
|
|
(defun jupyter-repl-history--next (n)
|
|
|
|
"Helper function for `jupyter-repl-history-next'.
|
|
|
|
Rotates `jupyter-repl-history' N times in the forward direction,
|
|
|
|
towards newer history elements and returns the Nth history
|
|
|
|
element in that direction relative to the current REPL history.
|
|
|
|
If the sentinel value is found before rotating N times, return
|
|
|
|
nil."
|
|
|
|
(if (> n 0)
|
|
|
|
(if (eq (ring-ref jupyter-repl-history -1) 'jupyter-repl-history)
|
|
|
|
nil
|
|
|
|
(ring-insert jupyter-repl-history
|
|
|
|
(ring-remove jupyter-repl-history -1))
|
|
|
|
(jupyter-repl-history--next (1- n)))
|
|
|
|
(ring-ref jupyter-repl-history 0)))
|
|
|
|
|
|
|
|
(defun jupyter-repl-history-next (&optional n)
|
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-06-14 20:57:02 -05:00
|
|
|
older history elements."
|
2017-12-31 10:16:41 -06:00
|
|
|
(interactive "p")
|
2018-01-14 00:04:05 -06:00
|
|
|
(or n (setq n 1))
|
2018-06-14 20:57:02 -05:00
|
|
|
(if (< n 0) (jupyter-repl-history-previous (- n))
|
2018-01-14 00:04:05 -06:00
|
|
|
(goto-char (point-max))
|
2018-06-14 20:57:02 -05:00
|
|
|
(let ((elem (jupyter-repl-history--next n)))
|
|
|
|
(if (and (null elem) (equal (jupyter-repl-cell-code) ""))
|
|
|
|
(error "End of history")
|
|
|
|
(if (null elem)
|
|
|
|
;; When we have reached the last history element in the forward
|
|
|
|
;; direction and the cell code is not empty, make it empty.
|
|
|
|
(jupyter-repl-replace-cell-code "")
|
|
|
|
(jupyter-repl-replace-cell-code
|
|
|
|
(ring-ref jupyter-repl-history 0)))))))
|
|
|
|
|
|
|
|
(defun jupyter-repl-history--previous (n)
|
|
|
|
"Helper function for `jupyter-repl-history-previous'.
|
|
|
|
Rotates `jupyter-repl-history' N times in the backward direction,
|
|
|
|
towards older history elements and returns the Nth history
|
|
|
|
element in that direction relative to the current REPL history.
|
|
|
|
If the sentinel value is found before rotating N times, return
|
|
|
|
nil."
|
|
|
|
(if (> n 0)
|
|
|
|
(if (eq (ring-ref jupyter-repl-history 1) 'jupyter-repl-history)
|
|
|
|
nil
|
|
|
|
(ring-insert-at-beginning
|
|
|
|
jupyter-repl-history (ring-remove jupyter-repl-history 0))
|
|
|
|
(jupyter-repl-history--previous (1- n)))
|
|
|
|
(ring-ref jupyter-repl-history 0)))
|
|
|
|
|
|
|
|
(defun jupyter-repl-history-previous (&optional n)
|
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-06-14 20:57:02 -05:00
|
|
|
elements."
|
2017-12-31 10:16:41 -06:00
|
|
|
(interactive "p")
|
2018-01-14 00:04:05 -06:00
|
|
|
(or n (setq n 1))
|
2018-06-14 20:57:02 -05:00
|
|
|
(if (< n 0) (jupyter-repl-history-next (- n))
|
2018-01-14 00:04:05 -06:00
|
|
|
(goto-char (point-max))
|
2018-06-14 20:57:02 -05:00
|
|
|
(unless (equal (jupyter-repl-cell-code)
|
|
|
|
(ring-ref jupyter-repl-history 0))
|
2018-02-08 12:29:08 -06:00
|
|
|
(setq n (1- n)))
|
2018-06-14 20:57:02 -05:00
|
|
|
(let ((elem (jupyter-repl-history--previous n)))
|
|
|
|
(if (null elem)
|
|
|
|
(error "Beginning of history")
|
2018-02-08 12:29:08 -06:00
|
|
|
(jupyter-repl-replace-cell-code
|
|
|
|
(ring-ref jupyter-repl-history 0))))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-22 20:22:33 -06:00
|
|
|
(cl-defmethod jupyter-handle-history-reply ((client jupyter-repl-client) _req history)
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-05-22 21:43:08 -05:00
|
|
|
(cl-loop for elem across history
|
|
|
|
for input-output = (aref elem 2)
|
2018-01-14 00:04:05 -06:00
|
|
|
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)
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2017-12-23 15:34:28 -06:00
|
|
|
(pcase status
|
|
|
|
("complete"
|
2018-05-15 23:40:09 -05:00
|
|
|
(jupyter-send-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
|
2018-05-15 23:40:09 -05:00
|
|
|
(jupyter-send-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)
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-01-13 23:10:40 -06:00
|
|
|
(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
|
2018-06-14 21:07:22 -05:00
|
|
|
at some position within the last cell, 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 a prefix argument is given, FORCE the kernel to execute the
|
|
|
|
current cell code without sending an `:is-complete-request'. See
|
2018-01-13 23:11:11 -06:00
|
|
|
`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-05-26 19:50:01 -05:00
|
|
|
(condition-case nil
|
|
|
|
(let ((cell-beginning (save-excursion
|
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-cell-beginning-position))))
|
|
|
|
(if (< (point) cell-beginning)
|
2018-02-12 10:55:22 -06:00
|
|
|
(goto-char (point-max))
|
2018-05-26 19:50:01 -05:00
|
|
|
(unless (jupyter-repl-connected-p)
|
|
|
|
(error "Kernel not alive"))
|
|
|
|
;; 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.
|
2018-09-16 23:01:54 -05:00
|
|
|
(unless (member (oref jupyter-current-client execution-state)
|
2018-05-26 19:50:01 -05:00
|
|
|
'("starting" "idle"))
|
|
|
|
(jupyter-repl-sync-execution-state)
|
|
|
|
(error "Kernel busy"))
|
2018-09-16 23:01:54 -05:00
|
|
|
(if force (jupyter-send-execute-request jupyter-current-client)
|
2018-05-26 19:50:01 -05:00
|
|
|
(if (not jupyter-repl-use-builtin-is-complete)
|
|
|
|
(let* ((jupyter-inhibit-handlers '(:status))
|
|
|
|
(res (jupyter-wait-until-received :is-complete-reply
|
|
|
|
(jupyter-send-is-complete-request
|
2018-09-16 23:01:54 -05:00
|
|
|
jupyter-current-client
|
2018-05-26 19:50:01 -05:00
|
|
|
:code (jupyter-repl-cell-code))
|
|
|
|
jupyter-repl-maximum-is-complete-timeout)))
|
|
|
|
(unless res
|
|
|
|
(message "Kernel did not respond to is-complete-request, using built-in is-complete.
|
|
|
|
Reset `jupyter-repl-use-builtin-is-complete' to nil if this is only temporary.")
|
2018-09-05 09:50:34 -05:00
|
|
|
(setq jupyter-repl-use-builtin-is-complete t)
|
2018-05-26 19:50:01 -05:00
|
|
|
(jupyter-repl-ret force)))
|
|
|
|
(goto-char (point-max))
|
2018-09-16 23:11:00 -05:00
|
|
|
(let ((complete-p (equal (buffer-substring-no-properties
|
2018-05-26 19:50:01 -05:00
|
|
|
(line-beginning-position) (point))
|
|
|
|
"")))
|
|
|
|
(jupyter-handle-is-complete-reply
|
2018-09-16 23:01:54 -05:00
|
|
|
jupyter-current-client
|
2018-05-26 19:50:01 -05:00
|
|
|
nil (if complete-p "complete" "incomplete") ""))))))
|
|
|
|
(beginning-of-buffer
|
|
|
|
;; No cells in the current buffer, just insert one
|
|
|
|
(jupyter-repl-insert-prompt 'in))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-09-17 19:15:28 -05:00
|
|
|
(cl-defgeneric jupyter-indent-line ()
|
|
|
|
(call-interactively #'indent-for-tab-command))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-indent-line (&context (major-mode julia-mode))
|
|
|
|
(call-interactively #'julia-latexsub-or-indent))
|
|
|
|
|
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))
|
2018-10-02 22:09:59 -05:00
|
|
|
(replacement (jupyter-with-repl-lang-buffer
|
2018-01-16 11:21:10 -06:00
|
|
|
(insert code)
|
|
|
|
(goto-char pos)
|
2018-09-17 19:15:28 -05:00
|
|
|
(jupyter-indent-line)
|
2018-01-16 11:21:10 -06:00
|
|
|
(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
|
|
|
|
|
2018-09-05 23:32:11 -05:00
|
|
|
(defun jupyter-repl-insert-continuation-prompts (bound)
|
|
|
|
"Insert continuation prompts if needed, stopping at BOUND.
|
|
|
|
Return the new BOUND since inserting continuation prompts may add
|
|
|
|
more characters than were initially in the buffer."
|
|
|
|
(setq bound (set-marker (make-marker) bound))
|
|
|
|
(set-marker-insertion-type bound t)
|
|
|
|
(while (and (< (point) bound)
|
|
|
|
(search-forward "\n" bound 'noerror))
|
|
|
|
(delete-char -1)
|
|
|
|
(jupyter-repl-insert-prompt 'continuation))
|
|
|
|
(prog1 (marker-position bound)
|
|
|
|
(set-marker bound nil)))
|
|
|
|
|
|
|
|
(defun jupyter-repl-mark-as-cell-code (beg end)
|
|
|
|
"Add the field property to text between (BEG . END) if within a code cell."
|
|
|
|
;; Handle field boundary at the front of the cell code
|
|
|
|
(when (= beg (jupyter-repl-cell-code-beginning-position))
|
|
|
|
(put-text-property beg (1+ beg) 'front-sticky t))
|
|
|
|
(when (text-property-not-all beg end 'field 'cell-code)
|
|
|
|
(font-lock-fillin-text-property beg end 'field 'cell-code)))
|
|
|
|
|
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.
|
2018-06-14 21:07:22 -05:00
|
|
|
BEG, END, and LEN have the same meaning as in
|
2018-08-26 14:55:03 -05:00
|
|
|
`after-change-functions'."
|
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)
|
2018-09-05 23:32:11 -05:00
|
|
|
(setq end (jupyter-repl-insert-continuation-prompts end))
|
|
|
|
(jupyter-repl-mark-as-cell-code beg end))
|
|
|
|
(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.
|
2018-08-27 20:46:58 -05:00
|
|
|
If the REPL buffer is killed, stop the client. If the REPL client
|
|
|
|
is connected to a kernel with a `jupyter-kernel-manager', kill
|
|
|
|
the kernel.
|
|
|
|
|
|
|
|
In addition, exit `jupyter-repl-interaction-mode' in all buffers
|
|
|
|
associated with the REPL. See `jupyter-repl-associate-buffer'."
|
2018-05-25 21:16:22 -05:00
|
|
|
(when (eq major-mode 'jupyter-repl-mode)
|
2018-09-16 23:01:54 -05:00
|
|
|
(if (not (jupyter-channels-running-p jupyter-current-client)) t
|
2018-05-25 21:16:22 -05:00
|
|
|
(when (y-or-n-p
|
|
|
|
(format "Jupyter REPL (%s) still connected. Kill it? "
|
|
|
|
(buffer-name (current-buffer))))
|
|
|
|
;; 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'.
|
|
|
|
(prog1 t
|
2018-09-16 23:01:54 -05:00
|
|
|
(jupyter-stop-channels jupyter-current-client)
|
|
|
|
(destructor jupyter-current-client)
|
2018-05-25 21:16:22 -05:00
|
|
|
(when (jupyter-repl-client-has-manager-p)
|
2018-09-16 23:01:54 -05:00
|
|
|
(jupyter-shutdown-kernel (oref jupyter-current-client manager))
|
|
|
|
(destructor (oref jupyter-current-client manager)))
|
2018-05-25 21:16:22 -05:00
|
|
|
(cl-loop
|
2018-09-16 23:01:54 -05:00
|
|
|
with client = jupyter-current-client
|
2018-05-25 21:16:22 -05:00
|
|
|
for buffer in (buffer-list)
|
|
|
|
do (with-current-buffer buffer
|
2018-09-16 23:01:54 -05:00
|
|
|
(when (eq jupyter-current-client client)
|
2018-05-25 21:16:22 -05:00
|
|
|
(jupyter-repl-interaction-mode -1)))))))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-17 20:56:42 -06:00
|
|
|
(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
|
2018-05-12 14:52:35 -05:00
|
|
|
ensure that a REPL windows margins are present.
|
|
|
|
|
|
|
|
If WINDOW is showing a REPL buffer and the margins are not set to
|
2018-01-17 20:56:42 -06:00
|
|
|
`jupyter-repl-prompt-margin-width', set them to the proper
|
|
|
|
value."
|
2018-05-13 11:40:08 -05:00
|
|
|
;; NOTE: Sometimes the margins will disappear after the window configuration
|
|
|
|
;; changes which is why `window-configuration-change-hook' is not used.
|
2018-01-17 20:56:42 -06:00
|
|
|
(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-09-07 04:29:29 -05:00
|
|
|
(defconst jupyter-completion-argument-regexp
|
2018-09-05 09:48:34 -05:00
|
|
|
(rx
|
|
|
|
(group "(" (zero-or-more anything) ")")
|
|
|
|
(one-or-more anything) " "
|
|
|
|
(group (one-or-more anything)) ?: (group (one-or-more digit)))
|
|
|
|
"Regular expression to match arguments and file locations.")
|
|
|
|
|
|
|
|
;;; Helpers for completion interface
|
|
|
|
|
|
|
|
(defun jupyter-completion-symbol-beginning (&optional pos)
|
|
|
|
"Return the starting position of a completion symbol.
|
|
|
|
If POS is non-nil return the position of the symbol before POS
|
|
|
|
otherwise return the position of the symbol before point."
|
|
|
|
(save-excursion
|
|
|
|
(and pos (goto-char pos))
|
|
|
|
(+ (point) (skip-syntax-backward "w_"))))
|
|
|
|
|
2018-09-07 04:29:29 -05:00
|
|
|
;; Adapted from `company-grab-symbol-cons'
|
2018-09-05 09:48:34 -05:00
|
|
|
(defun jupyter-completion-grab-symbol-cons (re &optional max-len)
|
|
|
|
"Return the current completion prefix before point.
|
|
|
|
Return either a STRING or a (STRING . t) pair. If RE matches the
|
|
|
|
beginning of the current symbol before point, return the latter.
|
|
|
|
Otherwise return the symbol before point. If no completion can be
|
|
|
|
done at point, return nil.
|
|
|
|
|
|
|
|
MAX-LEN is the maximum number of characters to search behind the
|
|
|
|
begiining of the symbol at point to look for a match of RE."
|
|
|
|
(let ((symbol (if (looking-at "\\>")
|
2018-09-16 23:11:00 -05:00
|
|
|
(buffer-substring-no-properties
|
2018-09-05 09:48:34 -05:00
|
|
|
(point) (jupyter-completion-symbol-beginning))
|
|
|
|
(unless (and (char-after)
|
|
|
|
(memq (char-syntax (char-after)) '(?w ?_)))
|
|
|
|
""))))
|
|
|
|
(when symbol
|
|
|
|
(save-excursion
|
|
|
|
(forward-char (- (length symbol)))
|
|
|
|
(if (looking-back re (if max-len
|
|
|
|
(- (point) max-len)
|
|
|
|
(line-beginning-position)))
|
|
|
|
(cons symbol t)
|
|
|
|
symbol)))))
|
|
|
|
|
2018-09-16 23:36:06 -05:00
|
|
|
(defun jupyter-completion-number-p ()
|
|
|
|
"Return non-nil if the text before `point' may be a floating point number."
|
2018-09-30 18:00:59 -05:00
|
|
|
(and (char-before)
|
|
|
|
(or (<= ?0 (char-before) ?9)
|
|
|
|
(eq (char-before) ?.))
|
2018-09-16 23:36:06 -05:00
|
|
|
(save-excursion
|
|
|
|
(skip-syntax-backward "w.")
|
|
|
|
(looking-at-p "[0-9]+\\.?[0-9]*"))))
|
2018-09-05 09:48:34 -05:00
|
|
|
|
|
|
|
;;; Extracting arguments from argument strings
|
|
|
|
|
|
|
|
(defun jupyter-completion--arg-extract-1 (pos)
|
|
|
|
"Helper function for `arg-extract-top'.
|
2018-10-01 23:13:07 -05:00
|
|
|
Extract the arguments starting at POS, narrowing to the first
|
|
|
|
SEXP before extraction."
|
2018-09-05 09:48:34 -05:00
|
|
|
(save-restriction
|
|
|
|
(goto-char pos)
|
|
|
|
(narrow-to-region
|
|
|
|
pos (save-excursion (forward-sexp) (point)))
|
|
|
|
(jupyter-completion--arg-extract)))
|
|
|
|
|
|
|
|
(defun jupyter-completion--arg-extract ()
|
|
|
|
"Extract arguments from an argument string.
|
|
|
|
Works for Julia and Python."
|
|
|
|
(let (arg-info
|
|
|
|
inner-args ppss depth inner
|
|
|
|
(start (1+ (point-min)))
|
|
|
|
(get-sexp
|
|
|
|
(lambda ()
|
|
|
|
(buffer-substring-no-properties
|
|
|
|
(point) (progn (forward-sexp) (point)))))
|
|
|
|
(get-string
|
|
|
|
(lambda (start)
|
|
|
|
(string-trim
|
|
|
|
(buffer-substring-no-properties
|
|
|
|
start (1- (point)))))))
|
|
|
|
(while (re-search-forward ",\\|::" nil t)
|
|
|
|
(setq ppss (syntax-ppss)
|
|
|
|
depth (nth 0 ppss)
|
|
|
|
inner (nth 1 ppss))
|
|
|
|
(cl-case (char-before)
|
|
|
|
(?:
|
|
|
|
(if (eq (char-after) ?{)
|
|
|
|
(push (jupyter-completion--arg-extract-1 (point)) inner-args)
|
|
|
|
(push (list (list (funcall get-sexp))) inner-args)))
|
|
|
|
(?,
|
|
|
|
(if (/= depth 1)
|
|
|
|
(push (jupyter-completion--arg-extract-1 inner) inner-args)
|
|
|
|
(push (cons (funcall get-string start) (pop inner-args))
|
|
|
|
arg-info)
|
|
|
|
(setq start (1+ (point)))))))
|
|
|
|
(goto-char (point-max))
|
|
|
|
(push (cons (funcall get-string start) (pop inner-args)) arg-info)
|
|
|
|
(nreverse arg-info)))
|
|
|
|
|
|
|
|
(defun jupyter-completion--make-arg-snippet (args)
|
|
|
|
"Construct a snippet from ARGS."
|
|
|
|
(cl-loop
|
|
|
|
with i = 1
|
|
|
|
for top-args in args
|
|
|
|
;; TODO: Handle nested arguments
|
|
|
|
for (arg . inner-args) = top-args
|
|
|
|
collect (format "${%d:%s}" i arg) into constructs
|
|
|
|
and do (setq i (1+ i))
|
|
|
|
finally return
|
|
|
|
(concat "(" (mapconcat #'identity constructs ", ") ")")))
|
|
|
|
|
|
|
|
;;; Getting the completion context
|
|
|
|
|
2018-09-16 20:09:55 -05:00
|
|
|
(cl-defmethod jupyter-code-context ((_type (eql inspect))
|
|
|
|
&context (major-mode jupyter-repl-mode))
|
|
|
|
(jupyter-line-context (next-single-property-change
|
|
|
|
(line-beginning-position) 'invisible)))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-code-context ((_type (eql completion))
|
|
|
|
&context (major-mode jupyter-repl-mode))
|
|
|
|
(list (jupyter-repl-cell-code)
|
|
|
|
(1- (jupyter-repl-cell-code-position))))
|
2018-01-13 22:57:50 -06:00
|
|
|
|
2018-09-17 19:15:28 -05:00
|
|
|
(cl-defgeneric jupyter-completion-prefix (&optional (re string) max-len)
|
2018-01-13 22:57:50 -06:00
|
|
|
"Return the prefix for the current completion context.
|
2018-09-17 19:15:28 -05:00
|
|
|
The default method calls `jupyter-completion-grab-symbol-cons'
|
|
|
|
with RE and MAX-LEN as arguments, RE defaulting to \"\\\\.\". It
|
|
|
|
also handles argument lists surrounded by parentheses specially
|
|
|
|
by considering an open parentheses and the symbol before it as a
|
|
|
|
completion prefix since some kernels will complete argument lists
|
|
|
|
if given such a prefix.
|
2018-09-05 09:48:34 -05:00
|
|
|
|
2018-01-13 22:57:50 -06:00
|
|
|
Note that the prefix returned is not the content sent to the
|
2018-09-17 19:15:28 -05:00
|
|
|
kernel, but the prefix used by `jupyter-completion-at-point'. See
|
|
|
|
`jupyter-code-context' for what is actually sent to the kernel."
|
|
|
|
(or re (setq re "\\."))
|
|
|
|
(cond
|
|
|
|
;; Completing argument lists
|
|
|
|
((and (char-before)
|
|
|
|
(eq (char-syntax (char-before)) ?\()
|
|
|
|
(or (not (char-after))
|
|
|
|
(looking-at-p "\\_>")
|
|
|
|
(not (memq (char-syntax (char-after)) '(?w ?_)))))
|
|
|
|
(buffer-substring-no-properties
|
|
|
|
(jupyter-completion-symbol-beginning (1- (point)))
|
|
|
|
(point)))
|
|
|
|
;; FIXME: Needed for cases where all completions are retrieved
|
|
|
|
;; from Base.| and the prefix turns empty again after
|
|
|
|
;; Base.REPLCompletions)|
|
|
|
|
;;
|
|
|
|
;; Actually the problem stems from stting the prefix length to 0
|
|
|
|
;; in company in the case Base.| and we have not selected a
|
|
|
|
;; completion and just pass over it.
|
|
|
|
((and (looking-at-p "\\_>")
|
|
|
|
(eq (char-syntax (char-before)) ?\)))
|
|
|
|
nil)
|
|
|
|
(t
|
|
|
|
(unless (jupyter-completion-number-p)
|
|
|
|
(jupyter-completion-grab-symbol-cons re max-len)))))
|
2018-09-05 09:48:34 -05:00
|
|
|
|
2018-09-05 09:48:34 -05:00
|
|
|
(cl-defmethod jupyter-completion-prefix (&context (major-mode jupyter-repl-mode))
|
|
|
|
(and (not (get-text-property (point) 'read-only))
|
2018-10-02 12:55:43 -05:00
|
|
|
(cl-call-next-method)))
|
2018-09-17 19:15:28 -05:00
|
|
|
|
|
|
|
(cl-defmethod jupyter-completion-prefix (&context (jupyter-lang julia))
|
|
|
|
(cl-call-next-method "\\\\\\|\\.\\|::\\|->" 2))
|
2018-09-05 09:48:34 -05:00
|
|
|
|
2018-09-05 09:48:34 -05:00
|
|
|
(defun jupyter-completion-construct-candidates (matches metadata)
|
2018-05-13 11:22:20 -05:00
|
|
|
"Construct candidates for completion.
|
2018-01-13 22:57:50 -06:00
|
|
|
MATCHES are the completion matches returned by the kernel,
|
2018-05-13 11:22:20 -05:00
|
|
|
METADATA is any extra data associated with MATCHES that was
|
2018-09-05 09:48:34 -05:00
|
|
|
supplied by the kernel."
|
2018-05-22 21:43:08 -05:00
|
|
|
(let* ((matches (append matches nil))
|
|
|
|
(tail matches)
|
|
|
|
(types (append (plist-get metadata :_jupyter_types_experimental) nil))
|
2018-09-05 09:48:34 -05:00
|
|
|
(buf))
|
|
|
|
(save-current-buffer
|
|
|
|
(unwind-protect
|
|
|
|
(while tail
|
2018-10-02 12:51:22 -05:00
|
|
|
(cond
|
|
|
|
((string-match jupyter-completion-argument-regexp (car tail))
|
2018-09-05 09:48:34 -05:00
|
|
|
(let* ((str (car tail))
|
|
|
|
(args-str (match-string 1 str))
|
|
|
|
(end (match-end 1))
|
|
|
|
(path (match-string 2 str))
|
|
|
|
(line (string-to-number (match-string 3 str)))
|
|
|
|
(snippet (progn
|
|
|
|
(unless buf
|
|
|
|
(setq buf (generate-new-buffer " *temp*"))
|
|
|
|
(set-buffer buf))
|
|
|
|
(insert args-str)
|
|
|
|
(goto-char (point-min))
|
|
|
|
(prog1 (jupyter-completion--make-arg-snippet
|
|
|
|
(jupyter-completion--arg-extract))
|
|
|
|
(erase-buffer)))))
|
2018-09-07 04:29:29 -05:00
|
|
|
(setcar tail (substring (car tail) 0 end))
|
2018-09-05 09:48:34 -05:00
|
|
|
(put-text-property 0 1 'snippet snippet (car tail))
|
|
|
|
(put-text-property 0 1 'location (cons path line) (car tail))
|
|
|
|
(put-text-property 0 1 'docsig (car tail) (car tail))))
|
2018-10-02 12:51:22 -05:00
|
|
|
;; TODO: This is specific to the results that
|
|
|
|
;; the python kernel returns, make a support
|
|
|
|
;; function?
|
|
|
|
((string-match-p "\\." (car tail))
|
|
|
|
(setcar tail (car (last (split-string (car tail) "\\."))))))
|
2018-09-05 09:48:34 -05:00
|
|
|
(setq tail (cdr tail)))
|
|
|
|
(when buf (kill-buffer buf))))
|
2018-05-13 11:22:20 -05:00
|
|
|
;; When a type is supplied add it as an annotation
|
2018-01-13 22:57:50 -06:00
|
|
|
(when types
|
|
|
|
(let ((max-len (apply #'max (mapcar #'length matches))))
|
2018-05-13 09:06:55 -05:00
|
|
|
(cl-mapc
|
2018-01-13 22:57:50 -06:00
|
|
|
(lambda (match meta)
|
2018-05-20 12:09:00 -05:00
|
|
|
(let* ((prefix (make-string (1+ (- max-len (length match))) ? ))
|
|
|
|
(annot (concat prefix (plist-get meta :type))))
|
|
|
|
(put-text-property 0 1 'annot annot match)))
|
2018-01-13 22:57:50 -06:00
|
|
|
matches types)))
|
|
|
|
matches))
|
|
|
|
|
2018-09-05 09:48:34 -05:00
|
|
|
;;; Completion at point interface
|
|
|
|
|
|
|
|
(defvar jupyter-completion-last-prefix nil
|
|
|
|
"The last prefix used to fetch candidates.")
|
|
|
|
|
|
|
|
(defvar jupyter-completion-cache nil
|
|
|
|
"The cache for completion candidates.
|
|
|
|
Can either be a Jupyter message plist or a list of candidates")
|
|
|
|
|
|
|
|
(defun jupyter-completion-prefetch (fun)
|
|
|
|
"Get completions for the current completion context.
|
|
|
|
Run FUN when the completions are available."
|
|
|
|
(cl-destructuring-bind (code pos)
|
2018-09-16 20:09:55 -05:00
|
|
|
(jupyter-code-context 'completion)
|
2018-09-05 09:48:34 -05:00
|
|
|
(let ((req (let ((jupyter-inhibit-handlers t))
|
|
|
|
(jupyter-send-complete-request
|
2018-09-16 23:01:54 -05:00
|
|
|
jupyter-current-client
|
2018-09-05 09:48:34 -05:00
|
|
|
:code code :pos pos))))
|
|
|
|
(prog1 req
|
|
|
|
(jupyter-add-callback req :complete-reply fun)))))
|
|
|
|
|
|
|
|
(defvar jupyter-completion--company-timer nil)
|
|
|
|
|
|
|
|
(defun jupyter-completion-at-point ()
|
|
|
|
"Function to add to `completion-at-point-functions'."
|
2018-09-29 21:22:02 -05:00
|
|
|
(when jupyter-current-client
|
|
|
|
(let ((prefix (jupyter-completion-prefix)) req)
|
|
|
|
(when jupyter-completion--company-timer
|
|
|
|
(cancel-timer jupyter-completion--company-timer))
|
|
|
|
(when prefix
|
|
|
|
(when (consp prefix)
|
|
|
|
(setq prefix (car prefix))
|
|
|
|
(when (and (bound-and-true-p company-mode)
|
|
|
|
(not company-candidates)
|
|
|
|
(< (length prefix) company-minimum-prefix-length))
|
|
|
|
;; Trigger completion similar to `company' when
|
|
|
|
;; `jupyter-completion-prefix' returns a cons cell.
|
|
|
|
(setq jupyter-completion--company-timer
|
|
|
|
;; NOTE: When we reach here `company-idle-delay' is `now' since
|
|
|
|
;; we are already inside a company completion so we can't use
|
|
|
|
;; it, just use a sensible time value instead.
|
|
|
|
(run-with-idle-timer
|
|
|
|
0.01 nil
|
|
|
|
(lambda ()
|
|
|
|
(let ((company-minimum-prefix-length 0)
|
|
|
|
(this-command 'company-manual-begin))
|
|
|
|
(unless company-candidates
|
|
|
|
(company-auto-begin))
|
|
|
|
;; Only call frontends when there are
|
|
|
|
;; completions from the kernel for
|
|
|
|
;; syntax based auto completion
|
|
|
|
(when jupyter-completion-cache
|
|
|
|
(company-post-command))))))))
|
|
|
|
;; Prefetch candidates
|
|
|
|
(when (or (not jupyter-completion-last-prefix)
|
|
|
|
(not jupyter-completion-cache)
|
|
|
|
(and
|
|
|
|
;; FIXME: We would like to manually get
|
|
|
|
;; completions for empty prefixes because
|
|
|
|
;; of things like from foo import |
|
|
|
|
;; Only when there is no whitespace before point since some
|
|
|
|
;; kernel's would give a list of all completions on every
|
|
|
|
;; space.
|
|
|
|
;; (not (eq (char-syntax (char-before)) ? ))
|
|
|
|
(or
|
|
|
|
;; This case happens when completing things like foo.|
|
|
|
|
(string= jupyter-completion-last-prefix "")
|
|
|
|
;; The obvious condition...
|
|
|
|
(not (string-prefix-p jupyter-completion-last-prefix prefix))))
|
|
|
|
;; Invalidate the cache when completing argument lists
|
|
|
|
(and (not (string= prefix ""))
|
|
|
|
(eq (aref prefix (1- (length prefix))) ?\()))
|
|
|
|
(setq
|
|
|
|
jupyter-completion-last-prefix prefix
|
|
|
|
req (jupyter-completion-prefetch
|
|
|
|
(lambda (msg) (setq jupyter-completion-cache
|
|
|
|
(cons 'fetched msg))))))
|
|
|
|
(list
|
|
|
|
(- (point) (length prefix)) (point)
|
|
|
|
(completion-table-dynamic
|
|
|
|
(lambda (_)
|
|
|
|
(when (and req (not (jupyter-request-idle-received-p req))
|
|
|
|
(not (eq (jupyter-message-type
|
|
|
|
(jupyter-request-last-message req))
|
|
|
|
:complete-reply)))
|
|
|
|
(jupyter-wait-until-received :complete-reply req))
|
|
|
|
(when (eq (car jupyter-completion-cache) 'fetched)
|
|
|
|
(cl-destructuring-bind (&key status
|
|
|
|
matches metadata
|
|
|
|
&allow-other-keys)
|
|
|
|
(jupyter-message-content (cdr jupyter-completion-cache))
|
|
|
|
(setq jupyter-completion-cache
|
|
|
|
(when (equal status "ok")
|
|
|
|
(jupyter-completion-construct-candidates
|
|
|
|
matches metadata)))))
|
|
|
|
jupyter-completion-cache))
|
|
|
|
:exit-function
|
|
|
|
#'jupyter-completion--post-completion
|
|
|
|
:company-location
|
|
|
|
(lambda (arg) (get-text-property 0 'location arg))
|
|
|
|
:annotation-function
|
|
|
|
(lambda (arg) (get-text-property 0 'annot arg))
|
|
|
|
:company-docsig
|
|
|
|
(lambda (arg) (get-text-property 0 'docsig arg))
|
|
|
|
:company-doc-buffer
|
|
|
|
#'jupyter-completion--company-doc-buffer)))))
|
2018-09-05 09:48:34 -05:00
|
|
|
|
|
|
|
(defun jupyter-completion--company-doc-buffer (arg)
|
2018-10-02 12:03:04 -05:00
|
|
|
"Send an inspect request for ARG to the kernel.
|
|
|
|
Use the `company-doc-buffer' to insert the results."
|
|
|
|
(let ((buf (company-doc-buffer)))
|
|
|
|
(jupyter-inspect arg buf)
|
|
|
|
(with-current-buffer buf
|
|
|
|
(when (> (point-max) (point-min))
|
|
|
|
(let ((inhibit-read-only t))
|
2018-09-05 09:48:34 -05:00
|
|
|
(remove-text-properties
|
2018-10-02 12:03:04 -05:00
|
|
|
(point-min) (point-max) '(read-only))
|
|
|
|
(current-buffer))))))
|
2018-09-05 09:48:34 -05:00
|
|
|
|
|
|
|
(defun jupyter-completion--post-completion (arg status)
|
|
|
|
"If ARG is a completion with a snippet, expand the snippet.
|
|
|
|
Do this only if STATUS is sole or finished."
|
|
|
|
(when (and (memq status '(sole finished))
|
2018-09-09 21:41:35 -05:00
|
|
|
(get-text-property 0 'snippet arg))
|
|
|
|
(when (and (require 'yasnippet nil t)
|
|
|
|
(not yas-minor-mode))
|
|
|
|
(yas-minor-mode 1))
|
2018-10-02 12:49:30 -05:00
|
|
|
;; Due to packages like smartparens
|
|
|
|
(when (eq (char-after) ?\))
|
|
|
|
(delete-char 1))
|
2018-09-05 09:48:34 -05:00
|
|
|
(yas-expand-snippet
|
|
|
|
(get-text-property 0 'snippet arg)
|
|
|
|
(save-excursion
|
|
|
|
(forward-sexp -1)
|
|
|
|
(point))
|
|
|
|
(point))))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-16 11:44:13 -06:00
|
|
|
;;; Inspection
|
2018-10-02 12:03:04 -05:00
|
|
|
;; TODO: How to add hover documentation support
|
2018-01-16 11:44:13 -06:00
|
|
|
|
2018-10-02 12:03:04 -05:00
|
|
|
(defun jupyter-inspect (code &optional pos buffer detail)
|
|
|
|
"Inspect CODE.
|
|
|
|
Send an `:inspect-request' to the `jupyter-current-client' of the
|
|
|
|
`current-buffer' and display the results in a BUFFER.
|
2018-01-22 19:55:14 -06:00
|
|
|
|
2018-10-02 12:03:04 -05:00
|
|
|
CODE is the code to inspect and POS is your position in the CODE.
|
|
|
|
If POS is nil, it defaults to the length of CODE.
|
2018-06-14 21:01:40 -05:00
|
|
|
|
2018-10-02 12:03:04 -05:00
|
|
|
If BUFFER is nil, display the results in an inspect buffer.
|
|
|
|
Otherwise insert the results in BUFFER but do not display it.
|
2018-01-22 19:55:14 -06:00
|
|
|
|
2018-10-02 12:03:04 -05:00
|
|
|
DETAIL is the detail level to use for the request and defaults to
|
|
|
|
0."
|
2018-02-12 10:58:36 -06:00
|
|
|
(let* ((jupyter-inhibit-handlers '(:status))
|
2018-01-17 20:28:46 -06:00
|
|
|
(msg (jupyter-wait-until-received :inspect-reply
|
2018-09-16 23:01:54 -05:00
|
|
|
(jupyter-send-inspect-request jupyter-current-client
|
2018-10-02 12:03:04 -05:00
|
|
|
:code code :pos (or pos (length code)) :detail detail))))
|
|
|
|
(if msg
|
|
|
|
(cl-destructuring-bind
|
|
|
|
(&key status found data metadata &allow-other-keys)
|
|
|
|
(jupyter-message-content msg)
|
|
|
|
(if (and (equal status "ok") found)
|
|
|
|
(let ((client jupyter-current-client)
|
|
|
|
(display-p
|
|
|
|
(unless buffer
|
2018-10-02 22:09:59 -05:00
|
|
|
(setq buffer (jupyter-with-doc-buffer "inspect"
|
|
|
|
(current-buffer))))))
|
2018-10-02 12:03:04 -05:00
|
|
|
(with-current-buffer buffer
|
|
|
|
(setq jupyter-current-client client)
|
2018-05-26 17:43:35 -05:00
|
|
|
(jupyter-repl-insert-data data metadata)
|
2018-09-05 09:51:06 -05:00
|
|
|
(goto-char (point-min))
|
2018-10-02 12:03:04 -05:00
|
|
|
(when display-p
|
|
|
|
(display-buffer (current-buffer))
|
|
|
|
(set-window-start (get-buffer-window) (point-min)))))
|
|
|
|
(message "Nothing found for %s" code)))
|
|
|
|
(message "Inspect timed out"))))
|
2018-01-16 11:44:13 -06:00
|
|
|
|
2018-10-02 12:03:04 -05:00
|
|
|
(defun jupyter-inspect-at-point (&optional buffer detail)
|
2018-01-22 19:47:53 -06:00
|
|
|
"Inspect the code at point.
|
2018-10-02 12:03:04 -05:00
|
|
|
Send an `:inspect-request' to the `jupyter-current-client' of the
|
|
|
|
`current-buffer' and display the results in a BUFFER. If BUFFER
|
|
|
|
is nil, display the results in an inspect buffer. Otherwise
|
|
|
|
insert the results in BUFFER but do not display them.
|
|
|
|
|
|
|
|
DETAIL is the detail level to use for the request and defaults to
|
|
|
|
0."
|
|
|
|
(interactive (list nil 0))
|
2018-05-16 21:19:49 -05:00
|
|
|
(cl-destructuring-bind (code pos)
|
2018-09-16 20:09:55 -05:00
|
|
|
(jupyter-code-context 'inspect)
|
2018-10-02 12:03:04 -05:00
|
|
|
(jupyter-inspect code pos buffer detail)))
|
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-09-16 23:01:54 -05:00
|
|
|
"Evaluate STR with the `jupyter-current-client's REPL.
|
2018-06-14 21:07:22 -05:00
|
|
|
Replaces the contents of the last cell in the REPL buffer with
|
|
|
|
STR before evaluating.
|
|
|
|
|
|
|
|
If the result of evaluation is more than 10 lines long, a buffer
|
|
|
|
displaying the results is shown. For results less than 10 lines
|
|
|
|
long, the result is displayed in the minibuffer.
|
|
|
|
|
|
|
|
If a prefix argument is given, SILENTLY evaluate STR without any
|
|
|
|
modification to the REPL buffer. Only the results of evaluation
|
|
|
|
are displayed."
|
2018-02-04 18:22:56 -06:00
|
|
|
(interactive (list (read-string "Jupyter Eval: ") current-prefix-arg))
|
2018-01-16 11:44:48 -06:00
|
|
|
(unless (buffer-local-value
|
2018-09-16 23:01:54 -05:00
|
|
|
'jupyter-current-client (current-buffer))
|
|
|
|
(user-error "No `jupyter-current-client' set, see `jupyter-repl-associate-buffer'"))
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer jupyter-current-client
|
2018-01-16 11:44:48 -06:00
|
|
|
(goto-char (point-max))
|
|
|
|
(unless (= (save-excursion (jupyter-repl-previous-cell)) 0)
|
|
|
|
(jupyter-repl-insert-prompt 'in))
|
2018-08-26 15:10:32 -05:00
|
|
|
(setq str (if silently (string-trim str)
|
|
|
|
(prog1 nil
|
|
|
|
(jupyter-repl-replace-cell-code str))))
|
|
|
|
(let* ((jupyter-inhibit-handlers (or (and silently t) '(:execute-result)))
|
2018-09-16 23:01:54 -05:00
|
|
|
(req (jupyter-send-execute-request jupyter-current-client
|
2018-08-26 15:10:32 -05:00
|
|
|
:code str)))
|
2018-01-16 11:44:48 -06:00
|
|
|
(jupyter-add-callback req
|
2018-01-22 20:21:24 -06:00
|
|
|
:execute-reply (lambda (msg)
|
2018-02-04 17:57:18 -06:00
|
|
|
(cl-destructuring-bind (&key status ename evalue
|
|
|
|
&allow-other-keys)
|
2018-01-22 20:21:24 -06:00
|
|
|
(jupyter-message-content msg)
|
|
|
|
(unless (equal status "ok")
|
|
|
|
(message "jupyter (%s): %s" ename
|
2018-05-13 09:09:13 -05:00
|
|
|
(ansi-color-apply evalue)))))
|
2018-01-16 11:44:48 -06:00
|
|
|
:execute-result
|
|
|
|
(lambda (msg)
|
2018-05-06 11:52:46 -05:00
|
|
|
(let ((res (jupyter-message-data msg :text/plain))
|
|
|
|
(inhibit-read-only t))
|
|
|
|
;; Prioritize the text representation
|
|
|
|
(if 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)))
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-doc-buffer "result"
|
2018-05-06 11:52:46 -05:00
|
|
|
(insert res)
|
|
|
|
(goto-char (point-min))
|
2018-05-15 17:47:33 -05:00
|
|
|
(display-buffer (current-buffer)))
|
2018-05-06 11:52:46 -05:00
|
|
|
(if (equal res "") (message "jupyter: eval done")
|
|
|
|
(message res)))
|
|
|
|
(with-current-buffer
|
|
|
|
(get-buffer-create "*jupyter-repl-result*")
|
|
|
|
(erase-buffer)
|
2018-05-26 17:43:35 -05:00
|
|
|
(jupyter-repl-insert-data
|
|
|
|
(jupyter-message-get msg :data)
|
|
|
|
(jupyter-message-get msg :metadata))
|
2018-05-06 11:52:46 -05:00
|
|
|
(goto-char (point-min))
|
|
|
|
(switch-to-buffer-other-window (current-buffer)))))))
|
2018-01-16 11:44:48 -06:00
|
|
|
req)))
|
|
|
|
|
2018-01-17 20:48:23 -06:00
|
|
|
(defun jupyter-repl-eval-file (file)
|
2018-09-16 23:01:54 -05:00
|
|
|
"Send the contents of FILE using `jupyter-current-client'."
|
2018-01-17 20:48:23 -06:00
|
|
|
(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)
|
2018-08-26 15:47:49 -05:00
|
|
|
(let* ((buf (find-buffer-visiting file))
|
|
|
|
(killp (null buf)))
|
|
|
|
(when (null buf)
|
|
|
|
(setq buf (delay-mode-hooks (find-file-noselect file))))
|
|
|
|
(with-current-buffer buf
|
|
|
|
(jupyter-repl-eval-string (buffer-string) 'silently))
|
|
|
|
(when killp
|
|
|
|
(kill-buffer)))
|
2018-01-17 20:48:23 -06:00
|
|
|
(error "Not a file (%s)" file)))
|
|
|
|
|
2018-01-17 20:48:53 -06:00
|
|
|
(defun jupyter-repl-eval-region (beg end &optional silently)
|
2018-09-16 23:01:54 -05:00
|
|
|
"Evaluate a region with the `jupyter-current-client'.
|
2018-01-16 11:44:48 -06:00
|
|
|
BEG and END are the beginning and end of the region to evaluate.
|
2018-01-22 19:55:14 -06:00
|
|
|
SILENTLY has the same meaning as in `jupyter-repl-eval-string'."
|
2018-02-04 18:22:56 -06:00
|
|
|
(interactive "rP")
|
2018-01-16 11:44:48 -06:00
|
|
|
(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 ()
|
2018-09-16 23:01:54 -05:00
|
|
|
"Evaluate the current line or region with the `jupyter-current-client'.
|
2018-01-16 11:44:48 -06:00
|
|
|
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-05-13 11:23:19 -05:00
|
|
|
(defun jupyter-repl-on-kernel-restart (client msg)
|
|
|
|
"Update the REPL buffer after CLIENT restarts.
|
|
|
|
If MSG is a startup message, insert the banner of the kernel,
|
2018-06-14 21:07:22 -05:00
|
|
|
synchronize the execution state, and insert a new input prompt."
|
2018-05-25 21:12:54 -05:00
|
|
|
(prog1 nil
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-05-25 21:12:54 -05:00
|
|
|
(when (jupyter-message-status-starting-p msg)
|
|
|
|
;; FIXME: Don't assume `jupyter-include-other-output' was previously nil
|
2018-09-16 23:01:54 -05:00
|
|
|
(jupyter-set jupyter-current-client 'jupyter-include-other-output nil)
|
2018-05-25 21:12:54 -05:00
|
|
|
(jupyter-repl-without-continuation-prompts
|
|
|
|
(goto-char (point-max))
|
|
|
|
(jupyter-repl-previous-cell)
|
2018-09-30 17:59:22 -05:00
|
|
|
(jupyter-repl-finalize-cell nil)
|
|
|
|
(jupyter-repl-newline)
|
|
|
|
(jupyter-repl-insert-banner
|
|
|
|
(plist-get (jupyter-kernel-info client) :banner))
|
|
|
|
(jupyter-repl-sync-execution-state)
|
|
|
|
(jupyter-repl-insert-prompt 'in))))))
|
2018-05-13 11:23:19 -05:00
|
|
|
|
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
|
2018-09-16 23:01:54 -05:00
|
|
|
(oref jupyter-current-client manager))))
|
2018-01-16 11:45:39 -06:00
|
|
|
|
|
|
|
;; TODO: Make timeouts configurable
|
2018-05-13 09:06:55 -05:00
|
|
|
(defun jupyter-repl-restart-kernel (&optional shutdown)
|
2018-01-16 11:45:39 -06:00
|
|
|
"Restart the kernel.
|
|
|
|
With a prefix argument, SHUTDOWN the kernel completely instead."
|
|
|
|
(interactive "P")
|
|
|
|
(unless shutdown
|
|
|
|
;; This may have been set to t due to a non-responsive kernel so make sure
|
|
|
|
;; that we try again when restarting.
|
2018-05-13 11:23:19 -05:00
|
|
|
(setq-local jupyter-repl-use-builtin-is-complete nil)
|
2018-08-30 14:38:28 -05:00
|
|
|
;; When restarting, the startup message is not associated with any request
|
|
|
|
;; so ensure that we are able to capture it.
|
2018-09-16 23:01:54 -05:00
|
|
|
(jupyter-set jupyter-current-client 'jupyter-include-other-output t))
|
2018-01-18 21:18:08 -06:00
|
|
|
(if (jupyter-repl-client-has-manager-p)
|
2018-09-16 23:01:54 -05:00
|
|
|
(let ((manager (oref jupyter-current-client manager)))
|
2018-08-30 14:38:28 -05:00
|
|
|
(cond
|
|
|
|
((jupyter-kernel-alive-p manager)
|
|
|
|
(message "%s kernel..." (if shutdown "Shutting down"
|
|
|
|
"Restarting"))
|
|
|
|
(jupyter-shutdown-kernel manager (not shutdown)))
|
|
|
|
(t
|
2018-01-18 21:18:08 -06:00
|
|
|
(message "Starting dead kernel...")
|
2018-08-30 14:38:28 -05:00
|
|
|
(jupyter-start-kernel manager))))
|
2018-05-13 09:06:55 -05:00
|
|
|
(unless (jupyter-wait-until-received :shutdown-reply
|
2018-09-16 23:01:54 -05:00
|
|
|
(jupyter-send-shutdown-request jupyter-current-client
|
2018-05-13 09:06:55 -05:00
|
|
|
:restart (not shutdown)))
|
2018-09-16 23:01:54 -05:00
|
|
|
(jupyter-set jupyter-current-client 'jupyter-include-other-output nil)
|
2018-01-16 11:45:39 -06:00
|
|
|
(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)
|
2018-09-16 23:01:54 -05:00
|
|
|
(let ((manager (oref jupyter-current-client manager)))
|
2018-01-18 21:18:08 -06:00
|
|
|
(display-buffer (process-buffer (oref manager kernel))))
|
2018-01-16 11:45:39 -06:00
|
|
|
(user-error "Kernel not a subprocess")))
|
|
|
|
|
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 ()
|
2018-01-22 19:55:14 -06:00
|
|
|
"Setup Isearch to search through the input history."
|
2018-01-16 11:46:05 -06:00
|
|
|
(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 ()
|
2018-01-22 19:55:14 -06:00
|
|
|
"Return a search function to search through a REPL's input history."
|
2018-01-16 11:46:05 -06:00
|
|
|
(lambda (string bound noerror)
|
|
|
|
(let ((search-fun (isearch-search-fun-default)) found)
|
2018-06-14 20:57:02 -05:00
|
|
|
(setq isearch-lazy-highlight-start-limit
|
|
|
|
(jupyter-repl-cell-beginning-position))
|
2018-01-16 11:46:05 -06:00
|
|
|
(or
|
|
|
|
;; 1. First try searching in the initial cell text
|
|
|
|
(funcall search-fun string
|
2018-06-14 20:57:02 -05:00
|
|
|
(or bound
|
|
|
|
(unless isearch-forward
|
|
|
|
(jupyter-repl-cell-code-beginning-position)))
|
2018-01-16 11:46:05 -06:00
|
|
|
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
|
2018-06-14 20:57:02 -05:00
|
|
|
(condition-case err
|
2018-01-16 11:46:05 -06:00
|
|
|
(progn
|
|
|
|
(while (not found)
|
|
|
|
(cond (isearch-forward
|
2018-02-04 18:18:19 -06:00
|
|
|
;; `jupyter-repl-history-next' clears the cell if the
|
|
|
|
;; last element is the sentinel, prevent that.
|
2018-01-16 11:46:05 -06:00
|
|
|
(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))))
|
|
|
|
;; 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))
|
2018-06-14 20:57:02 -05:00
|
|
|
'noerror)))
|
2018-01-16 11:46:05 -06:00
|
|
|
;; Return point of the new search result
|
|
|
|
(point))
|
2018-06-14 20:57:02 -05:00
|
|
|
(error
|
|
|
|
(unless noerror
|
|
|
|
(signal (car err) (cdr err))))))))))
|
2018-01-16 11:46:05 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-history-isearch-wrap ()
|
2018-01-22 19:55:14 -06:00
|
|
|
"Wrap the input history search when search fails.
|
2018-06-14 20:57:02 -05:00
|
|
|
Go to the oldest history element for a forward search or to the
|
|
|
|
newest history element for a backward search."
|
|
|
|
(if isearch-forward
|
|
|
|
(jupyter-repl-history--previous (ring-length jupyter-repl-history))
|
|
|
|
(jupyter-repl-history--next (ring-length jupyter-repl-history)))
|
2018-01-16 11:46:05 -06:00
|
|
|
(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 ()
|
2018-01-22 19:55:14 -06:00
|
|
|
"Save a function restoring the state of input history search.
|
|
|
|
Save the element at index 0 in `jupyter-repl-history'. When
|
|
|
|
restoring the state, the `jupyter-repl-history' ring is rotated,
|
|
|
|
in the appropriate direction, to the saved element."
|
2018-06-14 20:57:02 -05:00
|
|
|
(let ((code (jupyter-repl-cell-code)))
|
|
|
|
(cond
|
|
|
|
((equal code (ring-ref jupyter-repl-history 0))
|
|
|
|
(let ((elem (ring-ref jupyter-repl-history 0)))
|
|
|
|
(lambda (_cmd)
|
|
|
|
(when isearch-wrapped
|
|
|
|
(if isearch-forward
|
|
|
|
(jupyter-repl-history--next (ring-length jupyter-repl-history))
|
|
|
|
(jupyter-repl-history--previous (ring-length jupyter-repl-history))))
|
|
|
|
(while (not (eq (ring-ref jupyter-repl-history 0) elem))
|
|
|
|
(if isearch-forward
|
|
|
|
(jupyter-repl-history--previous 1)
|
|
|
|
(jupyter-repl-history--next 1)))
|
|
|
|
(jupyter-repl-replace-cell-code (ring-ref jupyter-repl-history 0)))))
|
|
|
|
(t
|
|
|
|
(let ((elem code))
|
|
|
|
(lambda (_cmd)
|
|
|
|
(jupyter-repl-replace-cell-code elem)))))))
|
2018-01-16 11:46:05 -06:00
|
|
|
|
2018-01-13 23:03:22 -06:00
|
|
|
;;; `jupyter-repl-mode'
|
|
|
|
|
2018-05-06 03:18:05 -05:00
|
|
|
(defun jupyter-repl-scratch-buffer ()
|
|
|
|
"Display a scratch buffer associated with the current REPL buffer."
|
|
|
|
(interactive)
|
|
|
|
(if (jupyter-repl-connected-p)
|
2018-09-16 23:01:54 -05:00
|
|
|
(let ((client jupyter-current-client))
|
2018-05-06 03:18:05 -05:00
|
|
|
(with-current-buffer (get-buffer-create
|
|
|
|
(concat "*jupyter-scratch*"))
|
|
|
|
(funcall (jupyter-repl-language-mode client))
|
|
|
|
(jupyter-repl-associate-buffer client)
|
|
|
|
(pop-to-buffer (current-buffer))))
|
|
|
|
(error "Not in a valid REPL buffer")))
|
|
|
|
|
2018-01-13 23:03:22 -06:00
|
|
|
(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-02-04 18:18:19 -06:00
|
|
|
;; TODO: Gaurd against a major mode change
|
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."
|
2018-09-16 23:01:54 -05:00
|
|
|
(cl-check-type jupyter-current-client jupyter-repl-client)
|
2018-05-27 23:29:58 -05:00
|
|
|
;; This is a better setting when rendering HTML tables
|
|
|
|
(setq-local truncate-lines t)
|
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-09-30 23:57:05 -05:00
|
|
|
(setq-local filter-buffer-substring-function #'jupyter-repl-filter-substring)
|
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-09-16 23:01:54 -05:00
|
|
|
(let* ((info (jupyter-kernel-info jupyter-current-client))
|
2018-09-16 21:17:10 -05:00
|
|
|
(language-info (plist-get info :language_info))
|
|
|
|
(language (plist-get language-info :name)))
|
2018-01-21 01:07:33 -06:00
|
|
|
(cl-destructuring-bind (mode syntax)
|
2018-01-22 19:41:47 -06:00
|
|
|
(jupyter-repl-kernel-language-mode-properties language-info)
|
2018-01-21 01:07:33 -06:00
|
|
|
(setq-local jupyter-repl-lang-mode mode)
|
|
|
|
(setq-local jupyter-repl-lang-buffer
|
|
|
|
(get-buffer-create
|
|
|
|
(format " *jupyter-repl-lang-%s*"
|
2018-05-15 16:21:08 -05:00
|
|
|
(plist-get language-info :name))))
|
2018-01-21 01:07:33 -06:00
|
|
|
(set-syntax-table syntax)
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-lang-buffer
|
2018-01-21 01:07:33 -06:00
|
|
|
(unless (eq major-mode mode)
|
2018-09-16 23:01:54 -05:00
|
|
|
(funcall mode))))
|
|
|
|
;; Get history from kernel
|
|
|
|
(setq-local jupyter-repl-history
|
|
|
|
(make-ring (1+ jupyter-repl-history-maximum-length)))
|
|
|
|
;; The sentinel value keeps track of the newest/oldest elements of the
|
|
|
|
;; history since next/previous navigation is implemented by rotations on the
|
|
|
|
;; ring.
|
|
|
|
(ring-insert jupyter-repl-history 'jupyter-repl-history)
|
|
|
|
(let ((jupyter-inhibit-handlers '(:status)))
|
|
|
|
(jupyter-send-history-request jupyter-current-client
|
|
|
|
:n jupyter-repl-history-maximum-length :raw nil :unique t))
|
|
|
|
(erase-buffer)
|
|
|
|
;; Add local hooks
|
|
|
|
(add-hook 'kill-buffer-query-functions #'jupyter-repl-kill-buffer-query-function nil t)
|
|
|
|
(add-hook 'after-change-functions 'jupyter-repl-after-buffer-change nil t)
|
|
|
|
(add-hook 'pre-redisplay-functions 'jupyter-repl-preserve-window-margins nil t)
|
|
|
|
;; Initialize the REPL
|
|
|
|
(buffer-disable-undo)
|
|
|
|
(jupyter-repl-initialize-hooks)
|
|
|
|
(jupyter-repl-initialize-fontification)
|
|
|
|
(jupyter-repl-isearch-setup)
|
|
|
|
(jupyter-repl-sync-execution-state)
|
2018-09-18 12:28:42 -05:00
|
|
|
(jupyter-repl-interaction-mode)))
|
|
|
|
|
|
|
|
(cl-defgeneric jupyter-repl-after-init ()
|
|
|
|
"Hook function called whenever `jupyter-repl-mode' is enabled/disabled.
|
|
|
|
You may override this function for a particular language using a
|
|
|
|
jupyter-lang &context specializer. For example, to do something
|
|
|
|
when the language if the REPL is python the method signature
|
|
|
|
would be
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-repl-after-init (&context (jupyter-lang python)))"
|
|
|
|
nil)
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-repl-after-init (&context (jupyter-lang javascript)
|
|
|
|
(jupyter-repl-mode js2-mode))
|
|
|
|
"If `js2-mode' is used for Javascript kernels, enable syntax highlighting.
|
|
|
|
`js2-mode' does not use `font-lock-defaults', but their own
|
|
|
|
custom method."
|
|
|
|
(add-hook 'after-change-functions
|
|
|
|
(lambda (_beg _end len)
|
|
|
|
;; Insertions only
|
|
|
|
(when (= len 0)
|
|
|
|
(unless (jupyter-repl-cell-finalized-p)
|
|
|
|
(let ((cbeg (jupyter-repl-cell-code-beginning-position))
|
|
|
|
(cend (jupyter-repl-cell-code-end-position)))
|
|
|
|
(save-restriction
|
|
|
|
(narrow-to-region cbeg cend)
|
|
|
|
(js2-parse)
|
|
|
|
(js2-mode-apply-deferred-properties))))))
|
|
|
|
t t))
|
|
|
|
|
|
|
|
(add-hook 'jupyter-repl-mode-hook 'jupyter-repl-after-init)
|
2017-12-31 11:41:53 -06:00
|
|
|
|
2018-05-13 11:23:19 -05:00
|
|
|
(defun jupyter-repl-initialize-hooks ()
|
|
|
|
"Initialize startup hooks.
|
|
|
|
When the kernel restarts, insert a new prompt."
|
|
|
|
;; NOTE: This hook will only run if `jupyter-include-other-output' is non-nil
|
|
|
|
;; during the restart.
|
2018-09-16 23:01:54 -05:00
|
|
|
(jupyter-add-hook jupyter-current-client 'jupyter-iopub-message-hook
|
2018-05-13 11:23:19 -05:00
|
|
|
(apply-partially
|
2018-09-16 23:01:54 -05:00
|
|
|
#'jupyter-repl-on-kernel-restart jupyter-current-client)))
|
2018-05-13 11:23:19 -05:00
|
|
|
|
2018-01-11 03:28:04 -06:00
|
|
|
(defun jupyter-repl-initialize-fontification ()
|
2018-06-14 21:07:22 -05:00
|
|
|
"Initialize fontification for the current REPL buffer."
|
2018-05-06 11:04:22 -05:00
|
|
|
(let (fld sff)
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-lang-buffer
|
2018-05-06 11:04:22 -05:00
|
|
|
(setq fld font-lock-defaults
|
|
|
|
sff font-lock-syntactic-face-function))
|
2018-08-26 15:49:30 -05:00
|
|
|
;; Set `font-lock-defaults' to a copy of the font lock defaults for the
|
|
|
|
;; REPL language but with a modified syntactic fontification function
|
|
|
|
(cl-destructuring-bind (kws &optional kws-only case-fold syntax-alist
|
|
|
|
&rest vars)
|
|
|
|
fld
|
|
|
|
(setq font-lock-defaults
|
|
|
|
(apply #'list kws kws-only case-fold syntax-alist
|
|
|
|
(append vars
|
|
|
|
(list
|
|
|
|
(cons 'font-lock-syntactic-face-function
|
|
|
|
;; Only fontify syntactically when the text
|
|
|
|
;; does not have a font-lock-face property
|
|
|
|
(lambda (state)
|
|
|
|
(unless (get-text-property
|
|
|
|
(nth 8 state) 'font-lock-face)
|
|
|
|
(when sff (funcall sff state))))))))))
|
2018-09-18 12:28:42 -05: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-09-16 23:01:54 -05:00
|
|
|
"Synchronize the `jupyter-current-client's kernel state."
|
|
|
|
(let* ((client jupyter-current-client)
|
2018-02-09 17:22:27 -06:00
|
|
|
(req (let ((jupyter-inhibit-handlers t))
|
2018-05-15 23:40:09 -05:00
|
|
|
(jupyter-send-execute-request client :code "" :silent t))))
|
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-05-13 11:40:08 -05:00
|
|
|
;; FIXME: Waiting longer here to account for initial startup of the Jupyter
|
|
|
|
;; kernel. Sometimes the idle message won't be received if another long
|
|
|
|
;; running execute request is sent right after.
|
|
|
|
(jupyter-wait-until-idle req 2)))
|
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 ()
|
2018-09-16 23:01:54 -05:00
|
|
|
"Switch to the REPL buffer of the `jupyter-current-client'."
|
2018-01-16 11:49:29 -06:00
|
|
|
(interactive)
|
2018-09-16 23:01:54 -05:00
|
|
|
(if jupyter-current-client
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer jupyter-current-client
|
2018-01-22 19:41:01 -06:00
|
|
|
(goto-char (point-max))
|
|
|
|
(pop-to-buffer (current-buffer)))
|
|
|
|
(error "Buffer not associated with a REPL, see `jupyter-repl-associate-buffer'")))
|
2018-01-16 11:49:29 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl-available-repl-buffers (&optional mode)
|
2018-06-14 21:07:22 -05:00
|
|
|
"Return a list of REPL buffers that are connected to live kernels.
|
2018-02-04 18:18:19 -06:00
|
|
|
If MODE is non-nil, return all REPL buffers whose
|
2018-06-14 21:07:22 -05:00
|
|
|
`jupyter-repl-lang-mode' is MODE."
|
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-02-03 21:49:46 -06:00
|
|
|
(jupyter-repl-connected-p)
|
2018-01-18 16:43:04 -06:00
|
|
|
(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.
|
2018-06-14 21:07:22 -05:00
|
|
|
If the `major-mode' of the `current-buffer' is the
|
|
|
|
`jupyter-repl-lang-mode' of CLIENT, enable
|
2018-09-30 18:04:23 -05:00
|
|
|
`jupyter-repl-interaction-mode'.
|
|
|
|
|
|
|
|
CLIENT should be a `jupyter-repl-client' or a subclass thereof.
|
|
|
|
If CLIENT is a buffer or the name of a buffer, use the
|
|
|
|
`jupyter-current-client' local to the buffer."
|
2018-01-16 11:49:29 -06:00
|
|
|
(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
|
2018-09-16 23:01:54 -05:00
|
|
|
jupyter-current-client)
|
2018-01-16 11:49:29 -06:00
|
|
|
client))
|
|
|
|
(cl-check-type client jupyter-repl-client)
|
2018-09-16 23:01:54 -05:00
|
|
|
(setq-local jupyter-current-client client)
|
2018-01-16 11:49:29 -06:00
|
|
|
(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)
|
2018-01-22 19:54:26 -06:00
|
|
|
(define-key map (kbd "C-c C-l") #'jupyter-repl-eval-file)
|
2018-10-02 12:03:04 -05:00
|
|
|
(define-key map (kbd "C-c C-f") #'jupyter-inspect-at-point)
|
2018-01-16 11:49:29 -06:00
|
|
|
(define-key map (kbd "C-c C-r") #'jupyter-repl-restart-kernel)
|
|
|
|
(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))
|
|
|
|
|
2018-05-15 17:46:45 -05:00
|
|
|
(defun jupyter-repl-propagate-client (orig-fun buffer-or-name &rest args)
|
2018-09-16 23:01:54 -05:00
|
|
|
"Propagate the `jupyter-current-client' to other buffers."
|
2018-05-15 17:46:45 -05:00
|
|
|
(when jupyter-repl-interaction-mode
|
2018-09-16 23:01:54 -05:00
|
|
|
(let ((client jupyter-current-client)
|
2018-05-15 17:46:45 -05:00
|
|
|
(buf (get-buffer buffer-or-name))
|
|
|
|
(mode major-mode))
|
|
|
|
(when buf
|
|
|
|
(with-current-buffer buf
|
|
|
|
(when (and (eq mode major-mode)
|
|
|
|
(not jupyter-repl-interaction-mode))
|
|
|
|
(jupyter-repl-associate-buffer client))))))
|
|
|
|
(apply orig-fun buffer-or-name args))
|
|
|
|
|
|
|
|
(advice-add 'switch-to-buffer :around #'jupyter-repl-propagate-client)
|
|
|
|
|
2018-01-16 11:49:29 -06:00
|
|
|
(define-minor-mode jupyter-repl-interaction-mode
|
2018-05-15 17:46:45 -05:00
|
|
|
"Minor mode for interacting with a Jupyter REPL.
|
2018-06-14 21:07:22 -05:00
|
|
|
When this minor mode is enabled you may evaluate code from the
|
|
|
|
current buffer using the associated REPL (see
|
|
|
|
`jupyter-repl-associate-buffer' to associate a REPL).
|
|
|
|
|
|
|
|
In addition any new buffers opened with the same `major-mode' as
|
|
|
|
the `current-buffer' will automatically have
|
|
|
|
`jupyter-repl-interaction-mode' enabled for them.
|
|
|
|
|
|
|
|
\\{jupyter-repl-interaction-map}"
|
2018-01-16 11:49:29 -06:00
|
|
|
:group 'jupyter-repl
|
|
|
|
:lighter " JuPy"
|
|
|
|
:init-value nil
|
|
|
|
:keymap jupyter-repl-interaction-map
|
|
|
|
(if jupyter-repl-interaction-mode
|
2018-09-05 09:48:34 -05:00
|
|
|
(add-hook 'completion-at-point-functions 'jupyter-completion-at-point nil t)
|
2018-09-30 17:59:52 -05:00
|
|
|
(remove-hook 'completion-at-point-functions 'jupyter-completion-at-point t)
|
2018-01-22 19:30:14 -06:00
|
|
|
(unless (eq major-mode 'jupyter-repl-mode)
|
2018-09-16 23:01:54 -05:00
|
|
|
(kill-local-variable 'jupyter-current-client))))
|
2018-01-16 11:49:29 -06:00
|
|
|
|
2018-01-22 19:28:50 -06:00
|
|
|
(defun jupyter-repl-kernel-language-mode-properties (language-info)
|
|
|
|
"Get the `major-mode' info of a kernel's language.
|
|
|
|
LANGUAGE-INFO should be the plist of the `:language_info' key in
|
|
|
|
a kernel's kernel-info. The `major-mode' is found by consulting
|
|
|
|
`auto-mode-alist' using the language's file extension found in
|
|
|
|
LANGUAGE-INFO. Return a list
|
2018-01-18 22:07:09 -06:00
|
|
|
|
2018-01-22 19:28:50 -06:00
|
|
|
(MODE SYNTAX-TABLE)
|
2018-01-18 22:07:09 -06:00
|
|
|
|
|
|
|
Where MODE is the `major-mode' to use for syntax highlighting
|
2018-01-22 19:28:50 -06:00
|
|
|
purposes and SYNTAX-TABLE is the syntax table of MODE."
|
2018-01-18 22:07:09 -06:00
|
|
|
(cl-destructuring-bind (&key file_extension &allow-other-keys)
|
2018-01-22 19:28:50 -06:00
|
|
|
language-info
|
2018-09-17 19:15:28 -05:00
|
|
|
(with-temp-buffer
|
|
|
|
(let ((buffer-file-name
|
|
|
|
(concat "jupyter-repl-lang" file_extension)))
|
|
|
|
(delay-mode-hooks (set-auto-mode)))
|
|
|
|
(list major-mode (syntax-table)))))
|
2018-01-18 22:07:09 -06:00
|
|
|
|
|
|
|
(defun jupyter-repl--new-repl (client)
|
|
|
|
"Initialize a new REPL buffer based on CLIENT.
|
2018-05-26 20:04:02 -05:00
|
|
|
CLIENT is a `jupyter-repl-client' already connected to its kernel
|
|
|
|
and has a non-nil kernel-info slot.
|
2018-01-18 22:07:09 -06:00
|
|
|
|
|
|
|
A new REPL buffer communicating with CLIENT's kernel is created
|
2018-05-26 20:04:02 -05:00
|
|
|
and set as CLIENT's buffer slot. If CLIENT already has a non-nil
|
|
|
|
buffer slot, raise an error."
|
2018-06-01 23:15:24 -05:00
|
|
|
(if (oref client buffer) (error "Client already has a REPL buffer")
|
2018-01-18 22:07:09 -06:00
|
|
|
(cl-destructuring-bind (&key language_info
|
|
|
|
banner
|
|
|
|
&allow-other-keys)
|
2018-09-16 21:17:10 -05:00
|
|
|
(jupyter-kernel-info client)
|
2018-01-18 22:07:09 -06:00
|
|
|
(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))))
|
2018-10-02 22:09:59 -05:00
|
|
|
(jupyter-with-repl-buffer client
|
2018-09-16 23:01:54 -05:00
|
|
|
(setq-local jupyter-current-client client)
|
2018-01-30 10:02:17 -06:00
|
|
|
(jupyter-repl-mode)
|
|
|
|
(jupyter-repl-insert-banner banner)
|
|
|
|
(jupyter-repl-insert-prompt 'in))))))
|
2018-01-18 22:07:09 -06:00
|
|
|
|
2018-01-17 21:00:00 -06:00
|
|
|
;;;###autoload
|
2018-06-01 23:14:38 -05:00
|
|
|
(defun run-jupyter-repl (kernel-name &optional associate-buffer client-class)
|
2018-01-17 21:00:00 -06:00
|
|
|
"Run a Jupyter REPL connected to a kernel with name, KERNEL-NAME.
|
2018-01-30 09:58:56 -06:00
|
|
|
KERNEL-NAME will be passed to `jupyter-find-kernelspecs' and the
|
|
|
|
first kernel found will be used to start the new kernel.
|
2018-01-17 21:00:00 -06:00
|
|
|
|
|
|
|
Optional argument ASSOCIATE-BUFFER, if non-nil, means to enable
|
|
|
|
`jupyter-repl-interaction-mode' in the `current-buffer' and
|
2018-01-30 09:58:56 -06:00
|
|
|
associate it with the REPL created. When called interactively,
|
2018-01-22 19:26:18 -06:00
|
|
|
ASSOCIATE-BUFFER is set to t. If the `current-buffer's
|
|
|
|
`major-mode' does not correspond to the language of the kernel
|
|
|
|
started, ASSOCIATE-BUFFER has no effect.
|
|
|
|
|
2018-06-01 23:14:38 -05:00
|
|
|
Optional argument CLIENT-CLASS is the class that will be passed
|
|
|
|
to `jupyter-start-new-kernel' and should be a subclass of
|
|
|
|
`jupyter-repl-client', which is the default.
|
|
|
|
|
2018-01-30 09:58:56 -06:00
|
|
|
When called interactively, display the new REPL buffer.
|
|
|
|
Otherwise, in a non-interactive call, return the
|
|
|
|
`jupyter-repl-client' connect to the kernel."
|
|
|
|
(interactive (list (car (jupyter-completing-read-kernelspec
|
|
|
|
nil current-prefix-arg))
|
2018-06-01 23:14:38 -05:00
|
|
|
t
|
|
|
|
nil))
|
|
|
|
(or client-class (setq client-class 'jupyter-repl-client))
|
2018-01-30 09:58:56 -06:00
|
|
|
(unless (called-interactively-p 'interactive)
|
|
|
|
(setq kernel-name (caar (jupyter-find-kernelspecs kernel-name))))
|
|
|
|
(unless kernel-name
|
|
|
|
(error "No kernel found for prefix (%s)" kernel-name))
|
2018-06-01 23:14:38 -05:00
|
|
|
(unless (child-of-class-p client-class 'jupyter-repl-client)
|
|
|
|
(error "Class should be a subclass of `jupyter-repl-client' (`%s')" client-class))
|
2018-09-16 21:17:10 -05:00
|
|
|
(cl-destructuring-bind (_manager client)
|
2018-06-01 23:14:38 -05:00
|
|
|
(jupyter-start-new-kernel kernel-name client-class)
|
2018-01-30 09:58:56 -06:00
|
|
|
(jupyter-repl--new-repl client)
|
|
|
|
(when (and associate-buffer
|
2018-02-09 17:21:10 -06:00
|
|
|
(eq major-mode (jupyter-repl-language-mode client)))
|
2018-01-30 09:58:56 -06:00
|
|
|
(jupyter-repl-associate-buffer client))
|
2018-06-10 23:12:50 -05:00
|
|
|
(when (called-interactively-p 'interactive)
|
|
|
|
(pop-to-buffer (oref client buffer)))
|
|
|
|
client))
|
2017-12-23 15:34:28 -06:00
|
|
|
|
2018-01-22 19:25:41 -06:00
|
|
|
;;;###autoload
|
2018-06-01 23:14:38 -05:00
|
|
|
(defun connect-jupyter-repl (file-or-plist &optional associate-buffer client-class)
|
2018-01-30 09:59:53 -06:00
|
|
|
"Run a Jupyter REPL using a kernel's connection FILE-OR-PLIST.
|
2018-01-22 19:25:41 -06:00
|
|
|
FILE-OR-PLIST can be either a file holding the connection
|
2018-01-30 09:59:53 -06:00
|
|
|
information or a property list of connection information.
|
2018-01-22 19:25:41 -06:00
|
|
|
ASSOCIATE-BUFFER has the same meaning as in `run-jupyter-repl'.
|
|
|
|
|
2018-06-01 23:14:38 -05:00
|
|
|
Optional argument CLIENT-CLASS is the class of the client that
|
|
|
|
will be used to initialize the REPL and should be a subclass of
|
|
|
|
`jupyter-repl-client', which is the default.
|
|
|
|
|
2018-05-13 09:06:55 -05:00
|
|
|
Return the `jupyter-repl-client' connected to the kernel. When
|
|
|
|
called interactively, display the new REPL buffer as well."
|
2018-06-01 23:14:38 -05:00
|
|
|
(interactive (list (read-file-name "Connection file: ") t nil))
|
|
|
|
(or client-class (setq client-class 'jupyter-repl-client))
|
|
|
|
(unless (child-of-class-p client-class 'jupyter-repl-client)
|
|
|
|
(error "Class should be a subclass of `jupyter-repl-client' (`%s')" client-class))
|
|
|
|
(let ((client (make-instance client-class)))
|
2018-01-22 19:25:41 -06:00
|
|
|
(jupyter-initialize-connection client file-or-plist)
|
|
|
|
(jupyter-start-channels client)
|
2018-09-16 21:17:10 -05:00
|
|
|
(jupyter-repl--new-repl client)
|
|
|
|
(when (and associate-buffer
|
|
|
|
(eq major-mode (jupyter-repl-language-mode client)))
|
|
|
|
(jupyter-repl-associate-buffer client))
|
|
|
|
(when (called-interactively-p 'interactive)
|
|
|
|
(pop-to-buffer (oref client buffer)))
|
|
|
|
client))
|
2018-01-08 21:38:32 -06:00
|
|
|
|
2018-05-16 20:38:23 -05:00
|
|
|
(provide 'jupyter-repl)
|
2018-01-08 21:38:32 -06:00
|
|
|
|
2017-12-23 15:34:28 -06:00
|
|
|
;; Local Variables:
|
|
|
|
;; byte-compile-warnings: (not free-vars)
|
|
|
|
;; End:
|
2018-05-16 20:38:23 -05:00
|
|
|
;;; jupyter-repl.el ends here
|