diff --git a/Makefile b/Makefile index e7537a6..0cb3976 100644 --- a/Makefile +++ b/Makefile @@ -29,11 +29,12 @@ README.rst: README.in.rst lisp/ein-notebook.el (describe-minor-mode \"ein:notebook-mode\") \ (with-current-buffer \"*Help*\" (princ (buffer-string))))" 2>/dev/null \ | tools/readme-sed.sh "KEYS NOTEBOOK" README.in.rst "key.*binding" > README.rst0 - perl -ne "s/^(\s*)\S+.*CI VERSION.*$$/\$${1}$(TEST_VERSION)/; print" README.rst0 > README.rst1 + perl -ne "s/^((\s*)C-c C-c(\s+).*)$$/\$${1}\n\$${2}C-u C-c C-c \$${3}ein:worksheet-execute-all-cells/; print" README.rst0 > README.rst1 + perl -ne "s/^(\s*)\S+.*CI VERSION.*$$/\$${1}$(TEST_VERSION)/; print" README.rst1 > README.rst0 grep ';;' lisp/ein.el \ | awk '/;;;\s*Commentary/{within=1;next}/;;;\s*/{within=0}within' \ | sed -e 's/^\s*;;*\s*//g' \ - | tools/readme-sed.sh "COMMENTARY" README.rst1 > README.rst + | tools/readme-sed.sh "COMMENTARY" README.rst0 > README.rst rm README.rst0 README.rst1 .PHONY: autoloads diff --git a/README.rst b/README.rst index f02995c..4e4a770 100644 --- a/README.rst +++ b/README.rst @@ -142,8 +142,8 @@ Keymap (C-h m) ein:worksheet-goto-next-input-km ein:worksheet-goto-prev-input-km ein:worksheet-execute-cell-and-insert-below-km - ein:worksheet-move-cell-down-km - ein:worksheet-move-cell-up-km + ein:worksheet-not-move-cell-down-km + ein:worksheet-not-move-cell-up-km C-x C-s ein:notebook-save-notebook-command-km C-x C-w ein:notebook-rename-command-km @@ -155,6 +155,7 @@ Keymap (C-h m) C-c C-a ein:worksheet-insert-cell-above-km C-c C-b ein:worksheet-insert-cell-below-km C-c C-c ein:worksheet-execute-cell-km + C-u C-c C-c ein:worksheet-execute-all-cells C-c C-e ein:worksheet-toggle-output-km C-c C-f ein:file-open-km C-c C-h ein:pytools-request-help-km @@ -192,7 +193,6 @@ Keymap (C-h m) C-c C-S-l ein:worksheet-clear-all-output-km C-c C-# ein:notebook-close-km C-c C-$ ein:tb-show-km - C-c C-' ein:worksheet-turn-on-autoexec-km C-c C-/ ein:notebook-scratchsheet-open-km C-c C-; ein:shared-output-show-code-cell-at-point-km C-c ein:worksheet-move-cell-down-km diff --git a/features/undo.feature b/features/undo.feature index 62769f1..cb93810 100644 --- a/features/undo.feature +++ b/features/undo.feature @@ -223,6 +223,36 @@ Scenario: Undo needs to at least work for reopened notebooks And I undo again Then the cursor should be at point "124" +@undo +Scenario: Execute all cells, mod some cells, get outputs, undo mods + Given I enable "ein:worksheet-enable-undo" + Given new python notebook + When I type "from time import sleep" + And I press "RET" + And I type "sleep(3)" + And I press "C-c C-b" + And I type "sleep(1)" + And I press "RET" + And I type "import math" + And I press "RET" + And I type "2*math.asin(1)" + And I press "C-c C-b" + And I press "C-c C-t" + And I type "mark" + And I call "ein:worksheet-execute-all-cells-above" + And I press "C-c C-b" + And I type "undo" + And I press "C-" + And I press "C-" + And I type "surprise" + And I wait for buffer to say "3.14159" + And I press "C-/" + Then the cursor should be at point "51" + And I undo again + Then the cursor should be at point "139" + And I undo again + Then the cursor should be at point "125" + @undo Scenario: Toggling between markdown and codecell does not break undo Given I enable "ein:worksheet-enable-undo" @@ -244,112 +274,3 @@ Scenario: Toggling between markdown and codecell does not break undo And I wait for cell to execute And I press "C-/" Then the cursor should be at point "62" - -@timestamp -Scenario: Undo (kind of) needs to work when someone explicitly requires ein-timestamp - Given I start the server configured "\n" - Given I enable "ein:worksheet-enable-undo" - Given old notebook "undo.ipynb" - And I type "howdy" - And I press "RET" - And I press "C-" - And I press "C-" - And I type "rowdy" - And I press "RET" - And I press "C-" - And I press "C-" - And I press "C-c C-k" - And I type "bowdy" - And I press "RET" - And I press "C-c C-y" - And I press "C-/" - Then the cursor should be at point "15" - And I press "C-/" - And I press "C-n" - And I press "C-n" - And I press "C-c C-s" - And I press "C-/" - And I undo again - And I undo again - And I undo again - And I undo again - Then the cursor should be at point "20" - And I undo again - Then the cursor should be at point "88" - And I press "C-" - And I press "C-k" - And I press "C-k" - And I press "C-k" - And I type "1.618" - And I wait for cell to execute - And I press "C-" - And I press "C-c C-m" - And I press "C-n" - And I press "C-n" - And I press "C-c C-s" - And I press "C-/" - And I undo again - And I undo again - And I undo again - Then the cursor should be at point "124" - -@timestamp -Scenario: Kill yank doesn't break undo - Given I enable "ein:worksheet-enable-undo" - Given new python notebook - When I type "import math" - And I press "M-RET" - And I type "[i for i in [1,2]]" - And I press "M-RET" - And I type "math.log(math.exp(1.0))" - And I wait for cell to execute - And I press "C-" - And I press "C-" - And I press "C-c C-k" - And I press "C-" - And I press "C-c C-y" - And I press "C-/" - Then the cursor should be at point "117" - -@timestamp -Scenario: Split and merge don't break undo - Given I enable "ein:worksheet-enable-undo" - Given new python notebook - When I type "print("hello")" - And I press "C-c C-b" - And I type "1111" - And I press "RET" - And I press "RET" - And I press "RET" - And I type "2222" - And I press "RET" - And I type "3333" - And I press "C-c C-b" - And I type "4444" - And I press "C-" - And I press "C-n" - And I press "C-c C-s" - And I wait for cell to execute - And I press "C-" - And I wait for cell to execute - And I press "C-" - And I wait for cell to execute - And I press "C-/" - And I press "C-" - And I type "5555" - And I press "RET" - And I type "6666" - And I wait for cell to execute - And I press "C-/" - And I undo again - And I undo again - And I undo again - And I undo again - Then the cursor should be at point "156" - And I press "C-c C-m" - And I press "C-c C-m" - And I press "C-/" - And I undo again - And I undo again - And I undo again - Then the cursor should be at point "93" diff --git a/lisp/ein-cell.el b/lisp/ein-cell.el index 540da3d..aa0f265 100644 --- a/lisp/ein-cell.el +++ b/lisp/ein-cell.el @@ -151,12 +151,6 @@ To view full output, use `ein:notebook-show-in-shared-output'." (const :tag "Show all traceback" nil)) :group 'ein) -(defcustom ein:cell-autoexec-prompt "⚡" - "String shown in the cell prompt when the auto-execution flag -is on." - :type 'string - :group 'ein) - (defcustom ein:truncate-long-cell-output nil "When nil do not truncate cells with long outputs. When set to a number will limit the number of lines in a cell output." @@ -471,9 +465,7 @@ Return language name as a string or `nil' when not defined. Called from ewoc pretty printer via `ein:cell-pp'." ;; Newline is inserted in `ein:cell-insert-input'. (ein:insert-read-only - (concat - (format "In [%s]:" (or (ein:oref-safe cell 'input-prompt-number) " ")) - (when (slot-value cell 'autoexec) " %s" ein:cell-autoexec-prompt)) + (format "In [%s]:" (or (ein:oref-safe cell 'input-prompt-number) " ")) 'font-lock-face 'ein:cell-input-prompt)) (cl-defmethod ein:cell-insert-prompt ((cell ein:textcell)) @@ -661,25 +653,6 @@ Return language name as a string or `nil' when not defined. (setf (slot-value cell 'input-prompt-number) number) (ein:cell-invalidate-prompt cell)) -(cl-defmethod ein:cell-set-autoexec ((cell ein:codecell) bool) - "Set auto-execution flag of CELL to BOOL and invalidate the -prompt EWOC node." - (setf (slot-value cell 'autoexec) bool) - (ein:cell-invalidate-prompt cell)) - -(cl-defmethod ein:cell-autoexec-p ((cell ein:basecell)) - "Auto-execution flag set to CELL. -Return `nil' always for non-code cells." - nil) - -(cl-defmethod ein:cell-autoexec-p ((cell ein:codecell)) - (slot-value cell 'autoexec)) - -(cl-defmethod ein:cell-toggle-autoexec ((cell ein:codecell)) - "Toggle auto-execution flag of CELL to BOOL and invalidate the -prompt EWOC node." - (ein:cell-set-autoexec cell (not (ein:cell-autoexec-p cell)))) - (cl-defmethod ein:cell-goto ((cell ein:basecell) &optional relpos prop) "Go to the input area of the given CELL. RELPOS is the position relative to the input area. Default is 0. diff --git a/lisp/ein-classes.el b/lisp/ein-classes.el index 199cc72..3de6412 100644 --- a/lisp/ein-classes.el +++ b/lisp/ein-classes.el @@ -260,13 +260,7 @@ Implementation note: Typed `:input-prompt-number' becomes a problem when reading a notebook that saved "*". So don't add `:type'!") (collapsed :initarg :collapsed :initform nil :type boolean) - (running :initarg :running :initform nil :type boolean) - (autoexec :initarg :autoexec :initform nil :type boolean - :documentation "Auto-execution flag. - -This cell is executed when the connected buffer is saved, -provided that (1) this flag is `t' and (2) corresponding -auto-execution mode flag in the connected buffer is `t'."))) + (running :initarg :running :initform nil :type boolean))) (defclass ein:textcell (ein:basecell) ((cell-type :initarg :cell-type :initform "text") diff --git a/lisp/ein-notebook.el b/lisp/ein-notebook.el index f42b03d..6d608f9 100644 --- a/lisp/ein-notebook.el +++ b/lisp/ein-notebook.el @@ -461,14 +461,6 @@ This is equivalent to do ``C-c`` in the console program." (interactive) (ein:kernel-interrupt (ein:$notebook-kernel ein:%notebook%))) -;; autoexec - -(defun ein:notebook-execute-autoexec-cells (notebook) - "Execute cells of which auto-execution flag is on." - (interactive (list (or ein:%notebook% (error "Not in notebook buffer!")))) - (mapc #'ein:worksheet-execute-autoexec-cells - (ein:$notebook-worksheets notebook))) - (define-obsolete-function-alias 'ein:notebook-eval-string 'ein:shared-output-eval-string "0.1.2") @@ -1169,7 +1161,6 @@ Tried add-function: the &rest from :around is an emacs-25 compilation issue." (ein:notebook--define-key map (kbd "M-RET") ein:worksheet-execute-cell-and-goto-next) (ein:notebook--define-key map (kbd "") ein:worksheet-execute-cell-and-insert-below) - (ein:notebook--define-key map (kbd "C-c C-'") ein:worksheet-turn-on-autoexec) (ein:notebook--define-key map "\C-c\C-e" ein:worksheet-toggle-output) (ein:notebook--define-key map "\C-c\C-v" ein:worksheet-set-output-visibility-all) (ein:notebook--define-key map "\C-c\C-l" ein:worksheet-clear-output) @@ -1191,8 +1182,8 @@ Tried add-function: the &rest from :around is an emacs-25 compilation issue." (ein:notebook--define-key map (kbd "C-") ein:worksheet-goto-next-input) (ein:notebook--define-key map (kbd "C-c ") ein:worksheet-move-cell-up) (ein:notebook--define-key map (kbd "C-c ") ein:worksheet-move-cell-down) - (ein:notebook--define-key map (kbd "M-") ein:worksheet-move-cell-up) - (ein:notebook--define-key map (kbd "M-") ein:worksheet-move-cell-down) + (ein:notebook--define-key map (kbd "M-") ein:worksheet-not-move-cell-up) + (ein:notebook--define-key map (kbd "M-") ein:worksheet-not-move-cell-down) (ein:notebook--define-key map "\C-c\C-h" ein:pytools-request-help) (ein:notebook--define-key map (kbd "C-c C-$") ein:tb-show) (ein:notebook--define-key map "\C-c\C-x" nil) diff --git a/lisp/ein-shared-output.el b/lisp/ein-shared-output.el index b6d18b5..962472f 100644 --- a/lisp/ein-shared-output.el +++ b/lisp/ein-shared-output.el @@ -56,9 +56,7 @@ Called from ewoc pretty printer via `ein:cell-pp'." ;; Newline is inserted in `ein:cell-insert-input'. (ein:insert-read-only - (concat - (format "In [%s]" (or (ein:oref-safe cell 'input-prompt-number) " ")) - (when (slot-value cell 'autoexec) " %s" ein:cell-autoexec-prompt)) + (format "In [%s]" (or (ein:oref-safe cell 'input-prompt-number) " ")) 'font-lock-face 'ein:cell-input-prompt)) (cl-defmethod ein:cell-execute ((cell ein:shared-output-cell) kernel code diff --git a/lisp/ein-worksheet.el b/lisp/ein-worksheet.el index c07269f..04217d5 100644 --- a/lisp/ein-worksheet.el +++ b/lisp/ein-worksheet.el @@ -33,6 +33,7 @@ (require 'ein-utils) (require 'ein-cell) (require 'ein-kill-ring) +(require 'warnings) (require 'poly-ein) ;;; Configuration @@ -40,6 +41,11 @@ ;; (define-obsolete-variable-alias ;; 'ein:notebook-enable-undo 'ein:worksheet-enable-undo "0.2.0") +(defcustom ein:worksheet-warn-obsolesced-keybinding t + "Warn of keybindings we arbitrarily obsolesce." + :type 'boolean + :group 'ein) + (defcustom ein:worksheet-enable-undo t "When non-`nil', allow undo of cell inputs only (as opposed to whole-cell operations such as killing, moving, executing cells). @@ -948,6 +954,26 @@ It is set in `ein:notebook-multilang-mode'." (poly-ein-fontify-buffer (ein:worksheet--get-buffer ein:%worksheet%)))) (message "No %s cell" (if up "previous" "next")))) +(defun ein:worksheet-not-move-cell (which) + (when ein:worksheet-warn-obsolesced-keybinding + (ein:display-warning-once + (mapconcat #'identity + '("M- and M- no longer move cells." + "Use C-c and C-c ." + "Custom set variable `ein:worksheet-warn-obsolesced-keybinding' to disable this warning.") "\n") + warning-minimum-level)) + (call-interactively (cl-some #'identity + (mapcar (lambda (pair) (lookup-key (cdr pair) which)) + (cdr minor-mode-map-alist))))) + +(defun ein:worksheet-not-move-cell-up (&rest _args) + (interactive) + (ein:worksheet-not-move-cell (kbd "M-"))) + +(defun ein:worksheet-not-move-cell-down (&rest _args) + (interactive) + (ein:worksheet-not-move-cell (kbd "M-"))) + (defun ein:worksheet-move-cell-up (ws cell) (interactive (list (ein:worksheet--get-ws-or-error) (ein:worksheet-get-current-cell))) @@ -1021,18 +1047,24 @@ Do not clear input prompts when the prefix argument is given." (mapc (lambda (cell) (setf (slot-value cell 'kernel) (slot-value ws 'kernel))) (seq-filter #'ein:codecell-p (ein:worksheet-get-cells ws)))) -(defun ein:worksheet-execute-cell (ws cell) +(defun ein:worksheet-execute-cell (ws cell &optional batch) "Execute code type CELL." - (interactive (list (ein:worksheet--get-ws-or-error) - (ein:worksheet-get-current-cell - :cell-p #'ein:codecell-p))) + (interactive `(,(ein:worksheet--get-ws-or-error) + ,(ein:worksheet-get-current-cell) + ,(when current-prefix-arg + (prog1 (read-char-choice "[RET]all [a]bove [b]elow: " (list ?\r ?a ?b)) + (message ""))))) (ein:kernel-when-ready (slot-value ws 'kernel) (apply-partially - (lambda (ws* cell* kernel) - (ein:cell-execute cell*) - (oset ws* :dirty t) - (ein:worksheet--unshift-undo-list cell*)) - ws cell)) + (lambda (ws* cell* batch* _kernel) + (cl-case batch* + (?\r (ein:worksheet-execute-all-cells ws*)) + (?a (ein:worksheet-execute-all-cells ws* :above cell*)) + (?b (ein:worksheet-execute-all-cells ws* :below cell*)) + (t (ein:cell-execute cell*) + (oset ws* :dirty t) + (ein:worksheet--unshift-undo-list cell*)))) + ws cell batch)) cell) (defun ein:worksheet-execute-cell-and-goto-next (ws cell &optional insert) @@ -1060,34 +1092,37 @@ cell bellow." "Execute all cells in the current worksheet buffer. If :above or :below specified, execute above/below the current cell." (interactive (list (ein:worksheet--get-ws-or-error))) - (let* ((all (seq-filter #'ein:codecell-p (ein:worksheet-get-cells ws))) - (current-id (aif (ein:worksheet-get-current-cell) (slot-value it 'cell-id))) - (not-matching (apply-partially (lambda (my other) - (not (string= (slot-value other 'cell-id) my))) - current-id))) - (mapc #'ein:cell-execute - (if (or above below) - (append (when (and current-id above) - (aif (seq-take-while not-matching all) - it - (prog1 nil - (ein:log 'info - "ein:worksheet-execute-all-cells: no cells above current")))) - (when (and current-id below) - (seq-drop-while not-matching all))) - all)))) + (let ((all (ein:worksheet-get-cells ws))) + (mapc (apply-partially #'ein:worksheet-execute-cell ws) + (seq-filter + #'ein:codecell-p + (aif (or above below) + (-when-let* ((current-id (slot-value it 'cell-id)) + (not-matching (apply-partially + (lambda (my other) + (not (string= (slot-value other 'cell-id) my))) + current-id))) + (append (when (and current-id above) + (aif (seq-take-while not-matching all) + it + (prog1 nil + (ein:log 'info + "ein:worksheet-execute-all-cells: no cells above current")))) + (when (and current-id below) + (seq-drop-while not-matching all)))) + all))))) (defun ein:worksheet-execute-all-cells-above (ws) "Execute all cells above the current cell (exclusively) in the current worksheet buffer." (interactive (list (ein:worksheet--get-ws-or-error))) - (ein:worksheet-execute-all-cells ws :above t)) + (ein:worksheet-execute-all-cells ws :above (ein:worksheet-get-current-cell))) (defun ein:worksheet-execute-all-cells-below (ws) "Execute all cells below the current cell (inclusively) in the current worksheet buffer." (interactive (list (ein:worksheet--get-ws-or-error))) - (ein:worksheet-execute-all-cells ws :below t)) + (ein:worksheet-execute-all-cells ws :below (ein:worksheet-get-current-cell))) ;;; Metadata @@ -1144,43 +1179,6 @@ current worksheet buffer." (indent-rigidly beg end (- (ein:find-leftmost-column beg end))))) -;;; Auto-execution - -(defun ein:worksheet-toggle-autoexec (cell) - "Toggle auto-execution flag of the cell at point." - (interactive (list (ein:worksheet-get-current-cell #'ein:codecell-p))) - (ein:cell-toggle-autoexec cell)) - -(defun ein:worksheet-turn-on-autoexec (cells &optional off) - "Turn on auto-execution flag of the cells in region or cell at point. -When the prefix argument is given, turn off the flag instead. Questionable." - (interactive - (list (ein:worksheet-get-cells-in-region-or-at-point - :cell-p #'ein:codecell-p) - current-prefix-arg)) - (mapc (lambda (c) (ein:cell-set-autoexec c (not off))) cells) - (ein:log 'info "Turn %s auto-execution flag of %s cells." - (if off "off" "on") - (length cells))) - -(defun ein:worksheet-execute-autoexec-cells (ws) - "Execute cells of which auto-execution flag is on. -This function internally sets current buffer to the worksheet -buffer, so you don't need to set current buffer to call this -function." - (interactive (list (ein:worksheet--get-ws-or-error))) - (ein:with-live-buffer (ein:worksheet-buffer ws) - (ein:kernel-when-ready - (slot-value ws 'kernel) - (apply-partially - (lambda (ws buffer kernel) - (with-current-buffer buffer - (let ((buffer-undo-list t)) - (mapc #'ein:cell-execute - (seq-filter #'ein:cell-autoexec-p - (ein:worksheet-get-cells ws)))))) - ws (current-buffer))))) - ;;; Workarounds (defadvice fill-paragraph (around ein:worksheet-fill-paragraph activate)