Merge branch 'scratchsheet'

This commit is contained in:
Takafumi Arakaki 2012-08-28 08:02:13 +02:00
commit 9ba0010cc9
9 changed files with 314 additions and 40 deletions

View file

@ -498,6 +498,10 @@ Change Log
v0.2
----
* Add "scratch sheet". This acts almost as same as worksheet, but you
don't need to save it. You can use try any code without saving
junks in your notebook. Use the command
:el:symbol:`ein:notebook-scratchsheet-open` to open scratch sheet.
* Menu support in notebook mode.
* Auto-connection support.
The new function :el:symbol:`ein:connect-to-default-notebook` can be

View file

@ -908,7 +908,7 @@ Called from ewoc pretty printer via `ein:cell-insert-output'."
(ein:cell-set-input-prompt cell (plist-get content :execution_count))
(ein:cell-running-set cell nil)
(let ((events (oref cell :events)))
(ein:events-trigger events 'set_dirty.Notebook '(:value t))
(ein:events-trigger events 'set_dirty.Worksheet (list :value t :cell cell))
(ein:events-trigger events 'maybe_reset_undo.Notebook cell)))
(defmethod ein:cell--handle-set-next-input ((cell ein:codecell) text)

View file

@ -44,6 +44,7 @@
(require 'ein-kernelinfo)
(require 'ein-cell)
(require 'ein-worksheet)
(require 'ein-scratchsheet)
(require 'ein-completer)
(require 'ein-pager)
(require 'ein-events)
@ -193,6 +194,9 @@ Current buffer for these functions is set to the notebook buffer.")
`ein:$notebook-worksheets' : list of `ein:worksheet'
List of worksheets.
`ein:$notebook-scratchsheets' : list of `ein:worksheet'
List of scratch worksheets.
"
url-or-port
notebook-id
@ -206,6 +210,7 @@ Current buffer for these functions is set to the notebook buffer.")
nbformat-minor
events
worksheets
scratchsheets
)
;; FIXME: Remove `ein:%notebook%' when worksheet is fully implemented.
@ -213,6 +218,9 @@ Current buffer for these functions is set to the notebook buffer.")
"Buffer local variable to store an instance of `ein:$notebook'.")
(define-obsolete-variable-alias 'ein:notebook 'ein:%notebook% "0.1.2")
;;; Constructor
(defun ein:notebook-new (url-or-port notebook-id &rest args)
(let ((notebook (apply #'make-ein:$notebook
:url-or-port url-or-port
@ -220,6 +228,9 @@ Current buffer for these functions is set to the notebook buffer.")
args)))
notebook))
;;; Destructor
(defun ein:notebook-del (notebook)
"Destructor for `ein:$notebook'."
(ein:log-ignore-errors
@ -228,11 +239,16 @@ Current buffer for these functions is set to the notebook buffer.")
(defun ein:notebook-close-worksheet (notebook ws)
"Close worksheet WS in NOTEBOOK. If WS is the last worksheet,
call notebook destructor `ein:notebook-del'."
(symbol-macrolet ((worksheets (ein:$notebook-worksheets notebook)))
(symbol-macrolet ((worksheets (ein:$notebook-worksheets notebook))
(scratchsheets (ein:$notebook-scratchsheets notebook)))
(setq worksheets (delq ws worksheets))
(unless worksheets
(setq scratchsheets (delq ws scratchsheets))
(unless (or worksheets scratchsheets)
(ein:notebook-del notebook))))
;;; Notebook utility functions
(defun ein:notebook-buffer (notebook)
"Return the buffer that is associated with NOTEBOOK."
;; FIXME: Find a better way to define notebook buffer! (or remove this func)
@ -243,11 +259,19 @@ call notebook destructor `ein:notebook-del'."
"Return the buffers associated with NOTEBOOK's kernel.
The buffer local variable `default-directory' of these buffers
will be updated with kernel's cwd."
(ein:filter #'identity (mapcar #'ein:worksheet-buffer
(ein:$notebook-worksheets notebook))))
(ein:filter #'identity
(mapcar #'ein:worksheet-buffer
(append (ein:$notebook-worksheets notebook)
(ein:$notebook-scratchsheets notebook)))))
(defun ein:notebook--get-nb-or-error ()
(or ein:%notebook% (error "Not in notebook buffer.")))
(defalias 'ein:notebook-name 'ein:$notebook-notebook-name)
;;; Open notebook
(defun ein:notebook-url (notebook)
(ein:notebook-url-from-url-and-id (ein:$notebook-url-or-port notebook)
(ein:$notebook-notebook-id notebook)))
@ -362,15 +386,7 @@ of minor mode."
(defun ein:notebook-bind-events (notebook events)
"Bind events related to PAGER to the event handler EVENTS."
(setf (ein:$notebook-events notebook) events)
;; As IPython support only supports whole-notebook saving, there is
;; no need for finer-level `set_dirty.Notebook'. Keep this until
;; IPython supports finer-level saving.
(ein:events-on events
'set_dirty.Notebook
(lambda (notebook data)
(setf (ein:$notebook-dirty notebook)
(plist-get data :value)))
notebook)
(ein:worksheet-class-bind-events events)
;; As calling multiple callbacks for this event does not make sense,
;; I amadding this in notebook instead of worksheet.
(ein:events-on events
@ -449,7 +465,7 @@ This is equivalent to do ``C-c`` in the console program."
'ein:shared-output-eval-string "0.1.2")
;;; Persistance and loading
;;; Persistence and loading
(defun ein:notebook-set-notebook-name (notebook name)
"Check NAME and change the name of NOTEBOOK to it."
@ -613,6 +629,38 @@ as usual."
(ein:kernel-kill kernel #'ein:notebook-close (list ein:%notebook%))
(ein:notebook-close ein:%notebook%)))))
;;; Scratch sheet
(defun ein:notebook-scratchsheet-new (notebook)
"Create new scratchsheet in NOTEBOOK."
(let ((ss (ein:scratchsheet-new
notebook
(ein:$notebook-kernel notebook)
(ein:$notebook-events notebook))))
(push ss (ein:$notebook-scratchsheets notebook))
(ein:worksheet-render ss)
(with-current-buffer (ein:worksheet-buffer ss)
(setq ein:%notebook% notebook))
ss))
(defun ein:notebook-scratchsheet-open (notebook &optional new popup)
"Open \"scratch sheet\".
Open a new one when prefix argument is given.
Scratch sheet is almost identical to worksheet. However, EIN
will not save the buffer. Use this buffer like of normal IPython
console. Note that you can always copy cells into the normal
worksheet to save result."
(interactive (list (ein:notebook--get-nb-or-error)
current-prefix-arg
t))
(let ((ss (or (unless new
(car (ein:$notebook-scratchsheets notebook)))
(ein:notebook-scratchsheet-new notebook))))
(when popup
(pop-to-buffer (ein:worksheet-buffer ss)))
ss))
;;; Opened notebooks
@ -767,6 +815,7 @@ Do not use `python-mode'. Use plain mode when MuMaMo is not installed::
(define-key map (kbd "C-c C-.") 'ein:pytools-jump-to-source-command)
(define-key map "\M-," 'ein:pytools-jump-back-command)
(define-key map (kbd "C-c C-,") 'ein:pytools-jump-back-command)
(define-key map (kbd "C-c C-/") 'ein:notebook-scratchsheet-open)
(easy-menu-define ein:notebook-menu map "EIN Notebook Mode Menu"
`("EIN Notebook"
,@(ein:generate-menu
@ -813,7 +862,8 @@ Do not use `python-mode'. Use plain mode when MuMaMo is not installed::
("Rename notebook" ein:notebook-rename-command)
("Jump to definition" ein:pytools-jump-to-source-command)
("Go back to the previous jump point"
ein:pytools-jump-back-command)))
ein:pytools-jump-back-command)
("Open scratch sheet" ein:notebook-scratchsheet-open)))
["Popup traceback viewer" ein:tb-show
:help "Show full traceback in different buffer"]
["Evaluate code in minibuffer" ein:shared-output-eval-string
@ -867,6 +917,7 @@ Note that print page is not supported in IPython 0.12.1."
"Return `nil' to prevent killing the notebook buffer.
Called via `kill-buffer-query-functions'."
(not (and ein:notebook-kill-buffer-ask
(ein:worksheet-p ein:%worksheet%) ; it's not `ein:scratchsheet'
(ein:notebook-modified-p)
(not (y-or-n-p "You have unsaved changes. Discard changes?")))))

52
lisp/ein-scratchsheet.el Normal file
View file

@ -0,0 +1,52 @@
;;; ein-scratchsheet.el --- Worksheet without needs for saving
;; Copyright (C) 2012 Takafumi Arakaki
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
;; This file is NOT part of GNU Emacs.
;; ein-scratchsheet.el 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 3 of the License, or
;; (at your option) any later version.
;; ein-scratchsheet.el 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 ein-scratchsheet.el.
;; If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'ein-worksheet)
(defvar ein:scratchsheet-buffer-name-template "*ein:scratch %s/%s*")
(defclass ein:scratchsheet (ein:worksheet)
;; Note that `data' slot is accessed when rendering worksheet.
;; So, set valid empty data (`nil') here.
((data :initarg :data :initform nil))
:documentation
"Worksheet without needs for saving.")
(defun ein:scratchsheet-new (notebook kernel events &rest args)
(apply #'make-instance 'ein:scratchsheet
:notebook notebook :kernel kernel :events events
args))
(defmethod ein:worksheet--buffer-name ((ws ein:scratchsheet))
(format ein:scratchsheet-buffer-name-template
(ein:worksheet-url-or-port ws)
(ein:worksheet-full-name ws)))
(provide 'ein-scratchsheet)
;;; ein-scratchsheet.el ends here

View file

@ -115,7 +115,7 @@
(defun ein:shared-output-bind-events (events)
"Add dummy event handlers."
(ein:events-on events 'set_dirty.Notebook #'ignore)
(ein:events-on events 'set_dirty.Worksheet #'ignore)
(ein:events-on events 'maybe_reset_undo.Notebook #'ignore))
(defun ein:shared-output-get-cell ()

View file

@ -73,23 +73,31 @@
(defmethod ein:worksheet-bind-events ((ws ein:worksheet))
(with-slots (events) ws
(ein:events-on events
'set_next_input.Worksheet
#'ein:worksheet--set-next-input
ws)
;; Bind events for sub components:
(ein:notification-bind-events (oref ws :notification) events)
(mapc (lambda (cell) (oset cell :events events))
(ein:worksheet-get-cells ws))))
(defmethod ein:worksheet--set-next-input ((ws ein:worksheet) data)
(defun ein:worksheet-class-bind-events (events)
"Binds event handlers which are not needed to be bound per instance."
(ein:events-on events 'set_next_input.Worksheet
#'ein:worksheet--set-next-input)
(ein:events-on events 'set_dirty.Worksheet #'ein:worksheet--set-dirty))
(defun ein:worksheet--set-next-input (-ignore- data)
(destructuring-bind (&key cell text) data
(if (eq (oref cell :ewoc) (oref ws :ewoc)) ; CELL is in this WS
(let ((new-cell (ein:worksheet-insert-cell-below ws 'code cell)))
(ein:cell-set-text new-cell text)
(oset ws :dirty t))
(ein:log 'debug
"WORKSHEET--SET-NEXT-INPUT: CELL is not in this buffer."))))
(ein:with-live-buffer (ein:cell-buffer cell)
(ein:and-let* ((ws ein:%worksheet%)
(new-cell
(ein:worksheet-insert-cell-below ws 'code cell)))
(ein:cell-set-text new-cell text)
(oset ws :dirty t)))))
(defun ein:worksheet--set-dirty (-ignore- data)
"Set dirty flag of worksheet in which CELL in DATA locates."
(destructuring-bind (&key value cell) data
(ein:with-live-buffer (ein:cell-buffer cell)
(ein:worksheet-set-modified-p ein:%worksheet% value))))
(defmethod ein:worksheet-notebook-name ((ws ein:worksheet))
(ein:notebook-name (oref ws :notebook)))

View file

@ -27,6 +27,7 @@
;;; Code:
(require 'ein-notebook)
(require 'ein-testing-cell)
(defun ein:testing-notebook-from-json (json-string &optional notebook-id)
(unless notebook-id (setq notebook-id "NOTEBOOK-ID"))
@ -81,6 +82,18 @@ The new cell is bound to a variable `cell'."
,cell-type nil t)))
,@body)))
(defun ein:testing-make-notebook-with-outputs (list-outputs)
"Make a new notebook with cells with output.
LIST-OUTPUTS is a list of list of strings (pyout text). Number
of LIST-OUTPUTS equals to the number cells to be contained in the
notebook."
(ein:testing-notebook-make-new
nil nil
(mapcar (lambda (outputs)
(ein:testing-codecell-data
nil nil (mapcar #'ein:testing-codecell-pyout-data outputs)))
list-outputs)))
(provide 'ein-testing-notebook)
;;; ein-testing-notebook.el ends here

View file

@ -140,6 +140,55 @@ is not found."
(format "Heading %s" level)))
do (should (= (oref cell :level) level))))))
;;; Destructor
(defvar ein:testing-notebook-del-args-log 'nolog)
(defadvice ein:notebook-del (before ein:testing-notebook-del activate)
"Log argument passed to"
(when (listp ein:testing-notebook-del-args-log)
(push (ad-get-args 0) ein:testing-notebook-del-args-log)))
(defun ein:testing-assert-notebook-del-not-called ()
(should-not ein:testing-notebook-del-args-log))
(defun ein:testing-assert-notebook-del-called-once ()
(should (= (length ein:testing-notebook-del-args-log) 1))
(mapc (lambda (arg) (should (= (length arg) 1)))
ein:testing-notebook-del-args-log)
(should (eq (caar ein:testing-notebook-del-args-log) notebook)))
(defun ein:testing-notebook-close-worksheet-open-and-close (num-open num-close)
(should (> num-open 0))
(let ((notebook (buffer-local-value 'ein:%notebook%
(ein:testing-notebook-make-empty)))
ein:testing-notebook-del-args-log)
(symbol-macrolet ((ws-list (ein:$notebook-worksheets notebook)))
;; Add worksheets. They can be just empty instance for this test.
(dotimes (_ (1- num-open))
(setq ws-list (append ws-list (list (make-instance 'ein:worksheet)))))
;; Make sure adding worksheet work.
(should (= (length ws-list) num-open))
(mapc (lambda (ws) (should (ein:worksheet-p ws))) ws-list)
;; Close worksheets
(dotimes (_ num-close)
(ein:notebook-close-worksheet notebook (car ws-list)))
;; Actual tests:
(should (= (length ws-list) (- num-open num-close)))
(if (= num-open num-close)
(ein:testing-assert-notebook-del-called-once)
(ein:testing-assert-notebook-del-not-called)))))
(ert-deftest ein:notebook-close-worksheet/open-one-close-one ()
(ein:testing-notebook-close-worksheet-open-and-close 1 1))
(ert-deftest ein:notebook-close-worksheet/open-two-close-two ()
(ein:testing-notebook-close-worksheet-open-and-close 2 2))
(ert-deftest ein:notebook-close-worksheet/open-two-close-one ()
(ein:testing-notebook-close-worksheet-open-and-close 2 1))
;; Notebook commands
@ -477,18 +526,6 @@ NO-TRIM is passed to `ein:notebook-split-cell-at-point'."
;; Check it worked
(ein:testing-test-output-visibility-all t)))
(defun ein:testing-make-notebook-with-outputs (list-outputs)
"Make a new notebook with cells with output.
LIST-OUTPUTS is a list of list of strings (pyout text). Number
of LIST-OUTPUTS equals to the number cells to be contained in the
notebook."
(ein:testing-notebook-make-new
nil nil
(mapcar (lambda (outputs)
(ein:testing-codecell-data
nil nil (mapcar #'ein:testing-codecell-pyout-data outputs)))
list-outputs)))
(defun ein:testing-assert-cell-output-num (cell num-outputs)
(should (ein:codecell-p cell))
(should (= (length (oref cell :outputs)) num-outputs)))
@ -626,6 +663,32 @@ defined."
(eintest:worksheet-execute-cell-and-*-deftest insert-below "markdown" nil t )
(eintest:worksheet-execute-cell-and-*-deftest insert-below "markdown" t t )
;;; Persistence and loading
(defun ein:testin-notebook-close (num-ws num-ss)
(should (= num-ws 1)) ; currently EIN only supports 1 WS
(should (>= num-ss 0))
(let ((notebook (buffer-local-value 'ein:%notebook%
(ein:testing-notebook-make-empty)))
ein:testing-notebook-del-args-log)
(dotimes (_ num-ss)
(ein:notebook-scratchsheet-new notebook))
(let ((buffers (ein:notebook-buffer-list notebook)))
(should (= (length buffers) (+ num-ws num-ss)))
(ein:notebook-close notebook)
(mapc (lambda (b) (should-not (buffer-live-p b))) buffers)
(ein:testing-assert-notebook-del-called-once))))
(ert-deftest ein:notebook-close/one-ws-no-ss ()
(ein:testin-notebook-close 1 0))
(ert-deftest ein:notebook-close/one-ws-one-ss ()
(ein:testin-notebook-close 1 1))
(ert-deftest ein:notebook-close/one-ws-five-ss ()
(ein:testin-notebook-close 1 5))
;; Notebook undo
@ -906,6 +969,42 @@ value of `ein:notebook-enable-undo'."
:output t))))
(should (ein:notebook-ask-before-kill-emacs)))))
;;; Buffer and kill hooks
(ert-deftest ein:notebook-ask-before-kill-buffer/no-ein-buffer ()
(with-temp-buffer
(mocker-let ((y-or-n-p (prompt) ()))
(should (ein:notebook-ask-before-kill-buffer)))))
(ert-deftest ein:notebook-ask-before-kill-buffer/new-notebook ()
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(mocker-let ((y-or-n-p (prompt) ()))
(should (ein:notebook-ask-before-kill-buffer)))))
(ert-deftest ein:notebook-ask-before-kill-buffer/modified-notebook ()
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(call-interactively #'ein:worksheet-insert-cell-below)
(mocker-let ((y-or-n-p
(prompt)
((:input '("You have unsaved changes. Discard changes?")
:output t))))
(should (ein:notebook-ask-before-kill-buffer)))))
(ert-deftest ein:notebook-ask-before-kill-buffer/modified-scratchsheet ()
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(with-current-buffer (ein:worksheet-buffer
(ein:notebook-scratchsheet-open ein:%notebook%))
(should (= (ein:worksheet-ncells ein:%worksheet%) 1))
(call-interactively #'ein:worksheet-insert-cell-below)
(should (= (ein:worksheet-ncells ein:%worksheet%) 2))
(should (ein:worksheet-modified-p ein:%worksheet%))
(mocker-let ((y-or-n-p (prompt) ()))
(should (ein:notebook-ask-before-kill-buffer))))
(should-not (ein:worksheet-modified-p ein:%worksheet%))
(mocker-let ((y-or-n-p (prompt) ()))
(should (ein:notebook-ask-before-kill-buffer)))))
;; Misc unit tests

View file

@ -0,0 +1,47 @@
(eval-when-compile (require 'cl))
(require 'ert)
(require 'ein-notebook)
(require 'ein-testing-notebook)
;;; Event handler
(defun ein:testing-worksheet-set-dirty
(pre-dirty value post-dirty fired-in)
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(when pre-dirty
(ein:cell-goto (ein:worksheet-get-current-cell))
(insert "something"))
(should (equal (ein:worksheet-modified-p ein:%worksheet%) pre-dirty))
(with-current-buffer (funcall fired-in)
(let ((events (oref ein:%worksheet% :events))
(cell (ein:worksheet-get-current-cell)))
(ein:events-trigger events 'set_dirty.Worksheet
(list :cell cell :value value))))
(should (equal (ein:worksheet-modified-p ein:%worksheet%) post-dirty))))
(defun ein:testing-scratchsheet-buffer ()
(ein:worksheet-buffer (ein:notebook-scratchsheet-open ein:%notebook%)))
(defmacro ein:testing-worksheet-set-dirty-deftest
(pre-dirty value post-dirty &optional fired-in)
(let ((name (intern (format "ein:worksheet-set-dirty/%s-to-%s-fired-in-%s"
pre-dirty value
(or fired-in "current-buffer"))))
(fired-in-defun
(case fired-in
(scratchsheet 'ein:testing-scratchsheet-buffer)
(t 'current-buffer))))
`(ert-deftest ,name ()
(ein:testing-worksheet-set-dirty ,pre-dirty ,value ,post-dirty
#',fired-in-defun))))
(ein:testing-worksheet-set-dirty-deftest t nil nil)
(ein:testing-worksheet-set-dirty-deftest t t t )
(ein:testing-worksheet-set-dirty-deftest nil nil nil)
(ein:testing-worksheet-set-dirty-deftest nil t t )
(ein:testing-worksheet-set-dirty-deftest t nil t scratchsheet)
(ein:testing-worksheet-set-dirty-deftest t t t scratchsheet)
(ein:testing-worksheet-set-dirty-deftest nil nil nil scratchsheet)
(ein:testing-worksheet-set-dirty-deftest nil t nil scratchsheet)