mirror of
https://github.com/vale981/emacs-ipython-notebook
synced 2025-03-05 17:11:41 -05:00
Merge pull request #525 from dickmao/already-open-bug
Cleanup save-before-quit logic, and polymode kill buffer bugfix
This commit is contained in:
commit
7c7691c26d
17 changed files with 246 additions and 207 deletions
|
@ -6,7 +6,9 @@ Fork the repo on github. Clone the fork to your home directory.
|
|||
|
||||
Install cask. Run `make dist` to ensure correct cask functionality.
|
||||
|
||||
Run `make test` to ensure a correct baseline. This locally replicates the travis ci build. You may need to install other software such as jupyter, R, matplotlib, etc.
|
||||
Run `make test` to ensure a correct baseline.
|
||||
|
||||
Until we institute a virtualenv for the required testing software (jupyter, R, matplotlib, etc.), out-of-the-box `make test` remains problematic.
|
||||
|
||||
Remove the MELPA-installed EIN by deleting the package directory (on my system, it's `~/.emacs.d/elpa/ein-20190122.1341`) or running `M-x package-delete`.
|
||||
|
||||
|
|
1
Makefile
1
Makefile
|
@ -41,6 +41,7 @@ autoloads:
|
|||
clean:
|
||||
cask clean-elc
|
||||
rm -rf test/test-install
|
||||
rm -rf log/*websocket*
|
||||
|
||||
.PHONY: dist-clean
|
||||
dist-clean: clean
|
||||
|
|
|
@ -305,7 +305,6 @@ created via `ein:notebook-create-checkpoint`.
|
|||
.. el:function:: ein:worksheet-execute-all-cell
|
||||
.. el:function:: ein:worksheet-delete-cell
|
||||
.. el:function:: ein:junk-rename
|
||||
.. el:function:: ein:notebook-kill-all-buffers
|
||||
.. el:function:: ein:iexec-mode
|
||||
.. el:function:: ein:notebook-create-checkpoint
|
||||
.. el:function:: ein:notebook-restore-to-checkpoint
|
||||
|
@ -516,7 +515,6 @@ Notebook
|
|||
|
||||
.. el:variable:: ein:worksheet-enable-undo
|
||||
.. el:variable:: ein:notebook-modes
|
||||
.. el:variable:: ein:notebook-kill-buffer-ask
|
||||
.. el:variable:: ein:notebook-querty-timeout-open
|
||||
.. el:variable:: ein:notebook-querty-timeout-save
|
||||
.. el:variable:: ein:cell-traceback-level
|
||||
|
|
|
@ -130,3 +130,27 @@ Scenario: kernel reconnect succeeds
|
|||
And header says "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command]"
|
||||
And I clear log expr "ein:log-all-buffer-name"
|
||||
And my reconnect is questioned
|
||||
|
||||
@exit
|
||||
Scenario: Saving fails upon quit, need to consult user
|
||||
Given new default notebook
|
||||
When I type "import math"
|
||||
And I wait for cell to execute
|
||||
And I cannot save upon quit
|
||||
|
||||
@kill
|
||||
Scenario: Assign variable, save, kill notebook buffer, get it back, check variable
|
||||
Given new default notebook
|
||||
When I type "import math"
|
||||
And I press "RET"
|
||||
And I type "b = math.pi"
|
||||
And I press "RET"
|
||||
And I wait for cell to execute
|
||||
And I press "C-x C-s"
|
||||
And I wait for the smoke to clear
|
||||
And I kill buffer and reopen
|
||||
And I press "C-c C-b"
|
||||
And I type "b"
|
||||
And I wait for cell to execute
|
||||
Then I should see "3.1415"
|
||||
And I press "C-x k"
|
|
@ -49,6 +49,21 @@
|
|||
(ein:kernel-reconnect-session (ein:$notebook-kernel ein:%notebook%)
|
||||
(lambda (kernel session-p)
|
||||
(should-not session-p))))))
|
||||
(When "^I cannot save upon quit$"
|
||||
(lambda ()
|
||||
(let ((always-errback
|
||||
(lambda (args)
|
||||
(cl-destructuring-bind (notebook callback cbargs errback)
|
||||
args
|
||||
(list notebook errback nil errback)))))
|
||||
(add-function :filter-args (symbol-function 'ein:notebook-save-notebook)
|
||||
always-errback)
|
||||
(cl-letf (((symbol-function 'y-or-n-p)
|
||||
(lambda (prompt) (message "%s" prompt) t)))
|
||||
(should (ein:notebook-opened-notebooks))
|
||||
(ein:notebook-close-notebooks)
|
||||
(Then "I should see message \"Some notebooks could not be saved. Exit anyway?\""))
|
||||
(remove-function (symbol-function 'ein:notebook-save-notebook) always-errback))))
|
||||
|
||||
(When "I restart kernel$"
|
||||
(lambda ()
|
||||
|
@ -144,6 +159,11 @@
|
|||
(ein:$notebook-notebook-name notebook))))
|
||||
(switch-to-buffer buf-name)
|
||||
(Then "I should be in buffer \"%s\"" buf-name))))))
|
||||
(When "^I kill buffer and reopen$"
|
||||
(lambda ()
|
||||
(let ((name (ein:$notebook-notebook-name ein:%notebook%)))
|
||||
(When "I press \"C-x k\"")
|
||||
(And (format "old notebook \"%s\"" name)))))
|
||||
|
||||
(When "^I \\(finally \\)?stop the server\\(\\)$"
|
||||
(lambda (final-p _workaround)
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
(if ein:%notebooklist%
|
||||
(loop for notebook in (ein:notebook-opened-notebooks)
|
||||
for path = (ein:$notebook-notebook-path notebook)
|
||||
do (ein:notebook-kill-kernel-then-close-command notebook t)
|
||||
do (ein:notebook-kill-kernel-then-close-command notebook)
|
||||
do (loop repeat 8
|
||||
until (not (ein:notebook-live-p notebook))
|
||||
do (sleep-for 0 500)
|
||||
|
@ -64,7 +64,8 @@
|
|||
(setq ein:notebook-create-checkpoint-on-save nil)
|
||||
(setq ein:testing-dump-file-log (concat default-directory "log/ecukes.log"))
|
||||
(setq ein:testing-dump-file-messages (concat default-directory "log/ecukes.messages"))
|
||||
(setq ein:testing-dump-file-server (concat default-directory "log/ecukes.server"))
|
||||
(setq ein:testing-dump-file-server (concat default-directory "log/ecukes.server"))
|
||||
(setq ein:testing-dump-file-websocket (concat default-directory "log/ecukes.websocket"))
|
||||
(setq ein:testing-dump-file-request (concat default-directory "log/ecukes.request"))
|
||||
(setq org-confirm-babel-evaluate nil)
|
||||
(setq transient-mark-mode t)
|
||||
|
|
|
@ -61,7 +61,6 @@
|
|||
(defun ein:dev-reload ()
|
||||
"Reload ein-*.el modules."
|
||||
(interactive)
|
||||
(ein:notebook-kill-all-buffers)
|
||||
(makunbound 'ein:notebook-mode-map) ; so defvar works.
|
||||
(load "ein-notebook") ; ... but make sure it will be defined first.
|
||||
(ein:load-files "^ein-.*\\.el$"))
|
||||
|
|
|
@ -203,31 +203,11 @@ the log of the running jupyter server."
|
|||
(interactive)
|
||||
(ein:and-let* ((proc (ein:jupyter-server-process))
|
||||
(_ok (or force (y-or-n-p "Stop server and close notebooks?"))))
|
||||
(let ((unsaved (ein:notebook-opened-notebooks #'ein:notebook-modified-p))
|
||||
(check-for-saved (make-hash-table :test #'equal)))
|
||||
(when unsaved
|
||||
(loop for nb in unsaved
|
||||
for nb-name = (ein:$notebook-notebook-name nb)
|
||||
when (y-or-n-p (format "Save %s?" nb-name))
|
||||
do (progn
|
||||
(setf (gethash nb-name check-for-saved) t)
|
||||
(ein:notebook-save-notebook
|
||||
nb
|
||||
(lambda (name check-hash) (remhash name check-hash))
|
||||
(list nb-name check-for-saved)))))
|
||||
(loop for x upfrom 0 by 1
|
||||
until (or (zerop (hash-table-count check-for-saved))
|
||||
(> x 20))
|
||||
do (sleep-for 0 500)))
|
||||
|
||||
(mapc #'ein:notebook-close (ein:notebook-opened-notebooks))
|
||||
|
||||
(ein:notebook-close-notebooks t)
|
||||
(loop repeat 10
|
||||
do (ein:query-gc-running-process-table)
|
||||
when (zerop (hash-table-count ein:query-running-process-table))
|
||||
return t
|
||||
do (ein:query-running-process-table)
|
||||
until (zerop (hash-table-count ein:query-running-process-table))
|
||||
do (sleep-for 0 500))
|
||||
|
||||
;; Both (quit-process) and (delete-process) leaked child kernels, so signal
|
||||
(if (eql system-type 'windows-nt)
|
||||
(delete-process proc)
|
||||
|
|
|
@ -266,7 +266,7 @@ combo must match exactly these url/port you used format
|
|||
`ein:notebooklist-login'."
|
||||
(interactive (list
|
||||
(ein:notebooklist-ask-url-or-port)
|
||||
(ein:get-notebook-or-error)))
|
||||
(ein:notebook--get-nb-or-error)))
|
||||
(message "Updating server info and restarting kernel for notebooklist %s"
|
||||
(ein:$notebook-notebook-name notebook))
|
||||
(setf (ein:$notebook-url-or-port notebook) new-url-or-port)
|
||||
|
@ -277,14 +277,11 @@ combo must match exactly these url/port you used format
|
|||
(ein:$notebook-notebook-name notebook)))))
|
||||
|
||||
(defun ein:notebook-buffer (notebook)
|
||||
"Return the buffer that is associated with NOTEBOOK."
|
||||
;; FIXME: Find a better way to define notebook buffer!
|
||||
;; For example, the last accessed buffer.
|
||||
(let ((first-buffer
|
||||
(lambda (ws-list)
|
||||
(loop for ws in ws-list if (ein:worksheet-buffer ws) return it))))
|
||||
(or (funcall first-buffer (ein:$notebook-worksheets notebook))
|
||||
(funcall first-buffer (ein:$notebook-scratchsheets notebook)))))
|
||||
"Return first buffer in NOTEBOOK's worksheets."
|
||||
(loop for ws in (append (ein:$notebook-worksheets notebook)
|
||||
(ein:$notebook-scratchsheets notebook))
|
||||
if (ein:worksheet-buffer ws)
|
||||
return it))
|
||||
|
||||
(defun ein:notebook-buffer-list (notebook)
|
||||
"Return the buffers associated with NOTEBOOK's kernel.
|
||||
|
@ -823,7 +820,7 @@ This is equivalent to do ``C-c`` in the console program."
|
|||
(ein:$notebook-worksheets notebook))
|
||||
(ein:notebook-save-notebook notebook callback cbargs)))
|
||||
|
||||
(defun ein:notebook-save-notebook (notebook &optional callback cbargs)
|
||||
(defun ein:notebook-save-notebook (notebook &optional callback cbargs errback)
|
||||
(unless (ein:notebook-buffer notebook)
|
||||
(let ((buf (format ein:notebook-buffer-name-template
|
||||
(ein:$notebook-url-or-port notebook)
|
||||
|
@ -845,7 +842,7 @@ This is equivalent to do ``C-c`` in the console program."
|
|||
#'ein:notebook-save-notebook-success
|
||||
(list notebook callback cbargs)
|
||||
#'ein:notebook-save-notebook-error
|
||||
(list notebook))))
|
||||
(list notebook errback))))
|
||||
|
||||
(defun ein:notebook-save-notebook-command ()
|
||||
"Save the notebook."
|
||||
|
@ -923,31 +920,79 @@ NAME is any non-empty string that does not contain '/' or '\\'.
|
|||
(ein:$notebook-scratchsheets notebook)))
|
||||
(ein:log 'info "Notebook renamed to %s." (ein:$content-name content)))
|
||||
|
||||
(defun ein:notebook-close (notebook)
|
||||
"Close NOTEBOOK and kill its buffer."
|
||||
(interactive (prog1 (list (ein:notebook--get-nb-or-error))
|
||||
(or (ein:notebook-ask-before-kill-buffer)
|
||||
(error "Quit"))))
|
||||
(let ((ein:notebook-kill-buffer-ask nil))
|
||||
;; Let `ein:notebook-kill-buffer-callback' do its job.
|
||||
(mapc #'kill-buffer (ein:notebook-buffer-list notebook))))
|
||||
(defmacro ein:notebook-avoid-recursion (&rest body)
|
||||
`(let ((kill-buffer-query-functions
|
||||
(remove-if (lambda (x) (eq 'ein:notebook-kill-buffer-query x))
|
||||
kill-buffer-query-functions)))
|
||||
,@body))
|
||||
|
||||
(defun ein:notebook-kill-kernel-then-close-command (notebook &optional force)
|
||||
(defun ein:notebook-kill-notebook-buffers (notebook)
|
||||
"Kill all of NOTEBOOK's buffers"
|
||||
(mapc #'ein:notebook-kill-current-buffer (ein:notebook-buffer-list notebook)))
|
||||
|
||||
(defun ein:notebook-kill-current-buffer (buf)
|
||||
"Kill BUF and avoid recursion in kill-buffer-query-functions"
|
||||
(ein:notebook-avoid-recursion
|
||||
(let ((notebook (buffer-local-value 'ein:%notebook% buf)))
|
||||
(when (kill-buffer buf)
|
||||
(ein:notebook-tidy-opened-notebooks notebook)))))
|
||||
|
||||
(defsubst ein:notebook-kill-buffer-query ()
|
||||
(ein:aif (ein:get-notebook--notebook)
|
||||
(let ((buf (or (buffer-base-buffer (current-buffer))
|
||||
(current-buffer))))
|
||||
(ein:notebook-ask-save it (apply-partially
|
||||
#'ein:notebook-kill-current-buffer
|
||||
buf))
|
||||
;; don't kill buffer!
|
||||
nil)
|
||||
;; kill buffer!
|
||||
t))
|
||||
|
||||
(defun ein:notebook-ask-save (notebook callback0)
|
||||
(unless callback0
|
||||
(setq callback0 #'ignore))
|
||||
(if (ein:notebook-modified-p notebook)
|
||||
(if (y-or-n-p (format "Save %s?" (ein:$notebook-notebook-name notebook)))
|
||||
(lexical-let ((success-positive 0))
|
||||
(add-function :before callback0 (lambda () (setq success-positive 1)))
|
||||
(ein:notebook-save-notebook notebook callback0 nil
|
||||
(lambda () (setq success-positive -1)))
|
||||
(loop repeat 10
|
||||
until (not (zerop success-positive))
|
||||
do (sleep-for 0 200)
|
||||
finally return (> success-positive 0)))
|
||||
(when (ein:worksheet-p ein:%worksheet%)
|
||||
(ein:worksheet-dont-save-cells ein:%worksheet%)) ;; TODO de-obfuscate
|
||||
(funcall callback0)
|
||||
t)
|
||||
(funcall callback0)
|
||||
t))
|
||||
|
||||
(defun ein:notebook-close (notebook &optional callback &rest cbargs)
|
||||
(interactive (list (ein:notebook--get-nb-or-error)))
|
||||
(lexical-let* ((notebook (or notebook (ein:notebook--get-nb-or-error)))
|
||||
(callback0 (apply-partially #'ein:notebook-kill-notebook-buffers notebook)))
|
||||
(when callback
|
||||
(add-function :after callback0
|
||||
(apply #'apply-partially callback cbargs)))
|
||||
(ein:notebook-ask-save notebook callback0)))
|
||||
|
||||
(defun ein:notebook-kill-kernel-then-close-command (notebook)
|
||||
"Kill kernel and then kill notebook buffer.
|
||||
To close notebook without killing kernel, just close the buffer
|
||||
as usual."
|
||||
(interactive (list ein:%notebook%))
|
||||
(when (or force (ein:notebook-ask-before-kill-buffer))
|
||||
(let ((kernel (ein:$notebook-kernel notebook))
|
||||
(callback (apply-partially
|
||||
(lambda (notebook* kernel)
|
||||
(ein:notebook-close notebook*))
|
||||
notebook)))
|
||||
(if (ein:kernel-live-p kernel)
|
||||
(ein:message-whir "Ending session"
|
||||
(add-function :before callback done-callback)
|
||||
(ein:kernel-delete-session kernel callback))
|
||||
(funcall callback nil)))))
|
||||
(interactive (list (ein:notebook--get-nb-or-error)))
|
||||
(let ((kernel (ein:$notebook-kernel notebook))
|
||||
(callback1 (apply-partially
|
||||
(lambda (notebook* kernel)
|
||||
(ein:notebook-close notebook*))
|
||||
notebook)))
|
||||
(if (ein:kernel-live-p kernel)
|
||||
(ein:message-whir "Ending session"
|
||||
(add-function :before callback1 done-callback)
|
||||
(ein:kernel-delete-session kernel callback1))
|
||||
(funcall callback1 nil))))
|
||||
|
||||
(defun ein:fast-content-from-notebook (notebook)
|
||||
"Quickly generate a basic content structure from notebook. This
|
||||
|
@ -1201,8 +1246,7 @@ When used as a lisp function, delete worksheet WS from NOTEBOOk."
|
|||
(setf (ein:$notebook-worksheets notebook)
|
||||
(delq ws (ein:$notebook-worksheets notebook)))
|
||||
(setf (ein:$notebook-dirty notebook) t)
|
||||
(let ((ein:notebook-kill-buffer-ask nil))
|
||||
(kill-buffer (ein:worksheet-buffer ws))))
|
||||
(kill-buffer (ein:worksheet-buffer ws)))
|
||||
|
||||
(defun ein:notebook-worksheet-move-prev (notebook ws)
|
||||
"Switch the current worksheet with the previous one."
|
||||
|
@ -1243,7 +1287,7 @@ 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:get-notebook-or-error)
|
||||
(interactive (list (ein:notebook--get-nb-or-error)
|
||||
current-prefix-arg
|
||||
t))
|
||||
(let ((ss (or (unless new
|
||||
|
@ -1260,7 +1304,6 @@ worksheet to save result."
|
|||
"A map: (URL-OR-PORT NOTEBOOK-ID) => notebook instance.")
|
||||
|
||||
(defun ein:notebook-get-opened-notebook (url-or-port path)
|
||||
(ein:notebook-opened-notebooks) ;; garbage collects dead notebooks -- TODO refactor
|
||||
(gethash (list url-or-port path) ein:notebook--opened-map))
|
||||
|
||||
(defun ein:notebook-get-opened-buffer (url-or-port path)
|
||||
|
@ -1273,6 +1316,11 @@ worksheet to save result."
|
|||
notebook
|
||||
ein:notebook--opened-map))
|
||||
|
||||
(defun ein:notebook-tidy-opened-notebooks (notebook)
|
||||
"Remove NOTEBOOK from ein:notebook--opened-map if it's not ein:notebook-live-p"
|
||||
(unless (ein:notebook-live-p notebook)
|
||||
(ein:notebook-remove-opened-notebook notebook)))
|
||||
|
||||
(defun ein:notebook-remove-opened-notebook (notebook)
|
||||
(remhash (list (ein:$notebook-url-or-port notebook)
|
||||
(ein:$notebook-notebook-path notebook))
|
||||
|
@ -1283,11 +1331,7 @@ worksheet to save result."
|
|||
If PREDICATE is given, notebooks are filtered by PREDICATE.
|
||||
PREDICATE is called with each notebook and notebook is included
|
||||
in the returned list only when PREDICATE returns non-nil value."
|
||||
(let (notebooks)
|
||||
(maphash (lambda (k n) (if (ein:notebook-live-p n)
|
||||
(push n notebooks)
|
||||
(remhash k ein:notebook--opened-map)))
|
||||
ein:notebook--opened-map)
|
||||
(let ((notebooks (hash-table-values ein:notebook--opened-map)))
|
||||
(if predicate
|
||||
(seq-filter predicate notebooks)
|
||||
notebooks)))
|
||||
|
@ -1321,10 +1365,6 @@ PREDICATE is called with the buffer name for each opened notebook."
|
|||
|
||||
;;; Predicate
|
||||
|
||||
(defun ein:notebook-buffer-p ()
|
||||
"Return non-`nil' if current buffer is notebook buffer."
|
||||
ein:%notebook%)
|
||||
|
||||
(defun ein:notebook-live-p (notebook)
|
||||
"Return non-`nil' if NOTEBOOK has live buffer."
|
||||
(buffer-live-p (ein:notebook-buffer notebook)))
|
||||
|
@ -1482,8 +1522,7 @@ Use simple `python-mode' based notebook mode when MuMaMo is not installed::
|
|||
'(("Save notebook" ein:notebook-save-notebook-command)
|
||||
("Copy and rename notebook" ein:notebook-save-to-command)
|
||||
("Rename notebook" ein:notebook-rename-command)
|
||||
("Close notebook without saving"
|
||||
ein:notebook-close)
|
||||
("Close notebook" ein:notebook-close)
|
||||
("Kill kernel then close notebook"
|
||||
ein:notebook-kill-kernel-then-close-command))))
|
||||
("Edit"
|
||||
|
@ -1691,55 +1730,20 @@ the first argument and CBARGS as the rest of arguments."
|
|||
(apply callback data cbargs)))
|
||||
callback cbargs))))
|
||||
|
||||
|
||||
;;; Buffer and kill hooks
|
||||
(add-hook 'kill-buffer-query-functions 'ein:notebook-kill-buffer-query)
|
||||
|
||||
(defcustom ein:notebook-kill-buffer-ask t
|
||||
"Whether EIN should ask before killing unsaved notebook buffer."
|
||||
:type '(choice (const :tag "Yes" t)
|
||||
(const :tag "No" nil))
|
||||
:group 'ein)
|
||||
(defun ein:notebook-close-notebooks (&optional blithely)
|
||||
"Used in `ein:jupyter-server-stop' and `kill-emacs-query-functions' hook."
|
||||
(ein:aif (ein:notebook-opened-notebooks)
|
||||
(if (and (cl-notevery #'identity (mapcar #'ein:notebook-close it))
|
||||
(not blithely))
|
||||
(y-or-n-p "Some notebooks could not be saved. Exit anyway?")
|
||||
t)
|
||||
t))
|
||||
|
||||
;; -- `kill-buffer-query-functions'
|
||||
(defun ein:notebook-ask-before-kill-buffer ()
|
||||
"Return `nil' to prevent killing the notebook buffer.
|
||||
Called via `kill-buffer-query-functions'."
|
||||
(not (or (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
|
||||
"This notebook has unsaved changes. Discard those changes?")))
|
||||
(when (ein:worksheet-p ein:%worksheet%)
|
||||
;; To make `ein:worksheet-save-cells' no-op.
|
||||
(ein:worksheet-dont-save-cells ein:%worksheet%)
|
||||
nil))))
|
||||
(add-hook 'kill-emacs-query-functions 'ein:notebook-close-notebooks t)
|
||||
|
||||
(add-hook 'kill-buffer-query-functions 'ein:notebook-ask-before-kill-buffer)
|
||||
|
||||
;; -- `kill-emacs-query-functions'
|
||||
(defun ein:notebook-ask-before-kill-emacs ()
|
||||
"Return `nil' to prevent killing Emacs when unsaved notebook exists.
|
||||
Called via `kill-emacs-query-functions'."
|
||||
(condition-case err
|
||||
(let ((unsaved (seq-filter #'ein:notebook-modified-p
|
||||
(ein:notebook-opened-notebooks))))
|
||||
(if (null unsaved)
|
||||
t
|
||||
(let ((answer
|
||||
(y-or-n-p
|
||||
(format "You have %s unsaved notebook(s). Discard changes?"
|
||||
(length unsaved)))))
|
||||
;; kill all unsaved buffers forcefully
|
||||
(when answer
|
||||
(mapc #'ein:notebook-close unsaved))
|
||||
answer)))
|
||||
((debug error)
|
||||
(ein:log 'error "Got error: %S" err)
|
||||
(y-or-n-p "Error while examine notebooks. Kill Emacs anyway? "))))
|
||||
|
||||
(add-hook 'kill-emacs-query-functions 'ein:notebook-ask-before-kill-emacs)
|
||||
|
||||
;; -- `kill-buffer-hook'
|
||||
(defun ein:notebook-kill-buffer-callback ()
|
||||
"Call notebook destructor. This function is called via `kill-buffer-hook'."
|
||||
;; TODO - it remains a bug that neither `ein:notebook-kill-buffer-callback'
|
||||
|
@ -1753,25 +1757,6 @@ Called via `kill-emacs-query-functions'."
|
|||
"Add \"notebook destructor\" to `kill-buffer-hook'."
|
||||
(add-hook 'kill-buffer-hook 'ein:notebook-kill-buffer-callback nil t))
|
||||
|
||||
;; Useful command to close notebooks.
|
||||
(defun ein:notebook-kill-all-buffers ()
|
||||
"Close all opened notebooks."
|
||||
(interactive)
|
||||
(let* ((notebooks (ein:notebook-opened-notebooks))
|
||||
(unsaved (seq-filter #'ein:notebook-modified-p notebooks)))
|
||||
(if notebooks
|
||||
(if (y-or-n-p
|
||||
(format (concat "You have %s opened notebook(s). "
|
||||
(when unsaved
|
||||
(format "%s are UNSAVED. " (length unsaved)))
|
||||
"Really kill all of them?")
|
||||
(length notebooks)))
|
||||
(progn (ein:log 'info "Killing all notebook buffers...")
|
||||
(mapc #'ein:notebook-close notebooks)
|
||||
(ein:log 'info "Killing all notebook buffers... Done!"))
|
||||
(ein:log 'info "Canceled to kill all notebooks."))
|
||||
(ein:log 'info "No opened notebooks."))))
|
||||
|
||||
(if (boundp 'undo-tree-incompatible-major-modes)
|
||||
(nconc undo-tree-incompatible-major-modes (list (ein:notebook-choose-mode))))
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ KEY, then call `request' with URL and SETTINGS. KEY is compared by
|
|||
(with-local-quit
|
||||
(when timeout
|
||||
(setq settings (plist-put settings :timeout (/ timeout 1000.0))))
|
||||
(loop do (ein:query-gc-running-process-table)
|
||||
(loop do (ein:query-running-process-table)
|
||||
for running = (hash-table-count ein:query-running-process-table)
|
||||
until (< running ein:max-simultaneous-queries)
|
||||
do (ein:log 'warn "ein:query-singleton-ajax: %d running processes"
|
||||
|
@ -132,8 +132,8 @@ KEY, then call `request' with URL and SETTINGS. KEY is compared by
|
|||
(puthash key response ein:query-running-process-table)
|
||||
response)))
|
||||
|
||||
(defun ein:query-gc-running-process-table ()
|
||||
"Garbage collect dead processes in `ein:query-running-process-table'."
|
||||
(defun ein:query-running-process-table ()
|
||||
"Keep track of unfinished curl requests."
|
||||
(maphash
|
||||
(lambda (key buffer)
|
||||
(when (request-response-done-p buffer)
|
||||
|
|
|
@ -75,16 +75,28 @@
|
|||
"Websocket gets its cookies using the url-cookie API, so we need to copy over
|
||||
any cookies that are made and stored during the contents API calls via
|
||||
emacs-request."
|
||||
(let* ((parsed-url (url-generic-parse-url url))
|
||||
(host-port (if (url-port-if-non-default parsed-url)
|
||||
(format "%s:%s" (url-host parsed-url) (url-port parsed-url))
|
||||
(url-host parsed-url)))
|
||||
(securep (string-match "^wss://" url))
|
||||
(cookies (request-cookie-alist (url-host parsed-url) "/" securep))
|
||||
(hub-cookies (request-cookie-alist (url-host parsed-url) "/hub/" securep))
|
||||
(user-cookies (request-cookie-alist (url-host parsed-url) (ein:maybe-get-jhconn-user url) securep)))
|
||||
(dolist (c (append cookies (append hub-cookies user-cookies)))
|
||||
(ein:websocket-store-cookie c host-port (car (url-path-and-query parsed-url)) securep))))
|
||||
(lexical-let*
|
||||
((parsed-url (url-generic-parse-url url))
|
||||
(host-port (if (url-port-if-non-default parsed-url)
|
||||
(format "%s:%s" (url-host parsed-url) (url-port parsed-url))
|
||||
(url-host parsed-url)))
|
||||
(securep (string-match "^wss://" url))
|
||||
(read-cookies-func (lambda (path)
|
||||
(request-cookie-alist
|
||||
(url-host parsed-url) path securep)))
|
||||
(cookies (loop repeat 4
|
||||
for cand = (mapcan read-cookies-func
|
||||
`("/" "/hub/"
|
||||
,(ein:maybe-get-jhconn-user url)))
|
||||
until (cl-some (lambda (x) (string= "_xsrf" (car x))) cand)
|
||||
do (ein:log 'info
|
||||
"ein:websocket--prepare-cookies: no _xsrf among %s, retrying."
|
||||
cand)
|
||||
do (sleep-for 0 300)
|
||||
finally return cand)))
|
||||
(dolist (c cookies)
|
||||
(ein:websocket-store-cookie c host-port
|
||||
(car (url-path-and-query parsed-url)) securep))))
|
||||
|
||||
(defun ein:websocket (url kernel on-message on-close on-open)
|
||||
(ein:websocket--prepare-cookies url)
|
||||
|
|
|
@ -212,7 +212,7 @@ TYPE can be 'body, nil."
|
|||
(save-restriction
|
||||
(widen)
|
||||
(let ((range (pm-innermost-range
|
||||
(or (when (car args) (funcall modifier (car args)))
|
||||
(or (when (car args) (max (point-min) (funcall modifier (car args))))
|
||||
(point)))))
|
||||
(narrow-to-region (car range) (cdr range))
|
||||
(apply f args)))))
|
||||
|
|
|
@ -46,11 +46,24 @@
|
|||
(defun ein:testing-save-buffer (buffer-or-name file-name)
|
||||
(when (and buffer-or-name (get-buffer buffer-or-name) file-name)
|
||||
(with-current-buffer buffer-or-name
|
||||
(write-region (point-min) (point-max) file-name))))
|
||||
(let ((coding-system-for-write 'raw-text))
|
||||
(write-region (point-min) (point-max) file-name)))))
|
||||
|
||||
(defun ein:testing-dump-logs ()
|
||||
(ein:testing-save-buffer "*Messages*" ein:testing-dump-file-messages)
|
||||
(ein:testing-save-buffer "*ein:jupyter-server*" ein:testing-dump-file-server)
|
||||
(mapc (lambda (b)
|
||||
(ein:and-let* ((bname (buffer-name b))
|
||||
(prefix "kernels/")
|
||||
(is-websocket (search "*websocket" bname))
|
||||
(kernel-start (search prefix bname))
|
||||
(sofar (subseq bname (+ kernel-start (length prefix))))
|
||||
(kernel-end (search "/" sofar)))
|
||||
(ein:testing-save-buffer
|
||||
bname
|
||||
(concat ein:testing-dump-file-websocket "."
|
||||
(seq-take sofar kernel-end)))))
|
||||
(buffer-list))
|
||||
(ein:testing-save-buffer ein:log-all-buffer-name ein:testing-dump-file-log)
|
||||
(ein:testing-save-buffer request-log-buffer-name ein:testing-dump-file-request))
|
||||
|
||||
|
@ -59,7 +72,7 @@
|
|||
if I call this between links in a deferred chain. Adding a flush-queue."
|
||||
(deferred:flush-queue!)
|
||||
(ein:testing-wait-until (lambda ()
|
||||
(ein:query-gc-running-process-table)
|
||||
(ein:query-running-process-table)
|
||||
(zerop (hash-table-count ein:query-running-process-table)))
|
||||
nil ms interval t))
|
||||
|
||||
|
@ -124,6 +137,16 @@ is not run in batch mode before Emacs 24.1."
|
|||
|
||||
(add-hook 'kill-emacs-hook #'ein:testing-dump-logs)
|
||||
|
||||
(with-eval-after-load "ein-notebook"
|
||||
;; if y-or-n-p isn't specially overridden, make it always "no"
|
||||
(lexical-let ((original-y-or-n-p (symbol-function 'y-or-n-p)))
|
||||
(add-function :around (symbol-function 'ein:notebook-ask-save)
|
||||
(lambda (f &rest args)
|
||||
(if (not (eq (symbol-function 'y-or-n-p) original-y-or-n-p))
|
||||
(apply f args)
|
||||
(cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest args) nil)))
|
||||
(apply f args)))))))
|
||||
|
||||
(provide 'ein-testing)
|
||||
|
||||
;;; ein-testing.el ends here
|
||||
|
|
|
@ -208,8 +208,7 @@ When NUM-OPEN = NUM-CLOSE, notebook should be closed."
|
|||
(setq ss-list
|
||||
(append ss-list (list (make-instance 'ein:scratchsheet)))))
|
||||
;; Close worksheet
|
||||
(let ((ws (car (ein:$notebook-worksheets notebook)))
|
||||
(ein:notebook-kill-buffer-ask nil))
|
||||
(let ((ws (car (ein:$notebook-worksheets notebook))))
|
||||
(ein:notebook-close-worksheet notebook ws)
|
||||
(kill-buffer (ein:worksheet-buffer ws)))
|
||||
;; Make sure adding scratchsheet work.
|
||||
|
@ -233,7 +232,6 @@ When NUM-OPEN = NUM-CLOSE, notebook should be closed."
|
|||
(ert-deftest ein:notebook-close-scratchsheet/open-two-close-one ()
|
||||
(ein:testing-notebook-close-scratchsheet-open-and-close 2 1))
|
||||
|
||||
|
||||
;;; Insertion and deletion of cells
|
||||
|
||||
(ert-deftest ein:notebook-insert-cell-below-command-simple ()
|
||||
|
@ -320,10 +318,7 @@ some text
|
|||
(should (equal (ein:worksheet-ncells ein:%worksheet%) 1))
|
||||
(call-interactively #'ein:worksheet-kill-cell)
|
||||
(should (equal (ein:worksheet-ncells ein:%worksheet%) 0))
|
||||
(flet ((y-or-n-p (&rest ignore) t)
|
||||
(ein:notebook-del (&rest ignore)))
|
||||
;; FIXME: are there anyway to skip confirmation?
|
||||
(kill-buffer)))
|
||||
(kill-buffer))
|
||||
(with-current-buffer (ein:testing-notebook-make-empty "NB2")
|
||||
(call-interactively #'ein:worksheet-yank-cell)
|
||||
(should (equal (ein:worksheet-ncells ein:%worksheet%) 1)))))
|
||||
|
@ -900,7 +895,7 @@ defined."
|
|||
|
||||
(defun ein:testing-notebook-data-assert-nb4-worksheet-contents (notebook &optional text)
|
||||
(let* ((data (ein:notebook-to-json notebook))
|
||||
(cells (assoc-default 'cells data #'eq)))
|
||||
(cells (assoc-default 'cells data)))
|
||||
(if text
|
||||
(progn
|
||||
(should (= (length cells) 1))
|
||||
|
@ -945,9 +940,9 @@ defined."
|
|||
;; Open scratch sheet.
|
||||
(ein:notebook-scratchsheet-open notebook)
|
||||
;; Discard a worksheet buffer
|
||||
(should (ein:notebook-modified-p notebook))
|
||||
(let (ein:notebook-kill-buffer-ask)
|
||||
(kill-buffer buffer))
|
||||
(should-not (kill-buffer buffer))
|
||||
(ein:testing-wait-until (lambda ()
|
||||
(not (buffer-live-p buffer))))
|
||||
(should (ein:notebook-live-p notebook))
|
||||
;; to-json should still work
|
||||
(if (< (ein:$notebook-nbformat notebook) 4)
|
||||
|
@ -961,8 +956,7 @@ defined."
|
|||
(ert-deftest ein:notebook-kill-kernel-then-close-when-its-alive ()
|
||||
(with-current-buffer (ein:testing-notebook-make-new)
|
||||
(let ((buffer (current-buffer))
|
||||
(notebook ein:%notebook%)
|
||||
(ein:notebook-kill-buffer-ask nil))
|
||||
(notebook ein:%notebook%))
|
||||
(cl-letf (((symbol-function 'ein:kernel-live-p) (lambda (&rest args) t))
|
||||
((symbol-function 'ein:kernel-delete-session) (lambda (kernel callback) (funcall callback kernel))))
|
||||
(call-interactively #'ein:notebook-kill-kernel-then-close-command))
|
||||
|
@ -972,8 +966,7 @@ defined."
|
|||
(with-current-buffer (ein:testing-notebook-make-new)
|
||||
(let ((buffer (current-buffer))
|
||||
(notebook ein:%notebook%)
|
||||
(kernel (ein:$notebook-kernel ein:%notebook%))
|
||||
(ein:notebook-kill-buffer-ask nil))
|
||||
(kernel (ein:$notebook-kernel ein:%notebook%)))
|
||||
(mocker-let
|
||||
((ein:kernel-live-p
|
||||
(kernel)
|
||||
|
@ -1210,7 +1203,6 @@ value of `ein:worksheet-enable-undo'."
|
|||
|
||||
(ert-deftest ein:notebook-ask-before-kill-emacs-simple ()
|
||||
(let ((ein:notebook--opened-map (make-hash-table :test 'equal)))
|
||||
(should (ein:notebook-ask-before-kill-emacs))
|
||||
(with-current-buffer
|
||||
(ein:testing-notebook-make-empty "Modified Notebook.ipynb")
|
||||
(call-interactively #'ein:worksheet-insert-cell-below)
|
||||
|
@ -1219,57 +1211,57 @@ value of `ein:worksheet-enable-undo'."
|
|||
(ein:testing-notebook-make-empty "Saved Notebook.ipynb")
|
||||
(ein:notebook-save-notebook-success ein:%notebook%)
|
||||
(should-not (ein:notebook-modified-p)))
|
||||
(flet ((y-or-n-p (&rest ignore) t)
|
||||
(ein:notebook-del (&rest ignore)))
|
||||
(kill-buffer
|
||||
(ein:testing-notebook-make-empty "Killed Notebook.ipynb")))
|
||||
(kill-buffer (ein:testing-notebook-make-empty "Killed Notebook.ipynb"))
|
||||
(should (gethash `(,ein:testing-notebook-dummy-url "Modified Notebook.ipynb") ein:notebook--opened-map))
|
||||
(should (gethash `(,ein:testing-notebook-dummy-url "Saved Notebook.ipynb") ein:notebook--opened-map))
|
||||
(should (gethash `(,ein:testing-notebook-dummy-url "Killed Notebook.ipynb") ein:notebook--opened-map))
|
||||
(should (= (hash-table-count ein:notebook--opened-map) 3))
|
||||
(mocker-let ((y-or-n-p
|
||||
(prompt)
|
||||
((:input '("You have 1 unsaved notebook(s). Discard changes?")
|
||||
:output t))))
|
||||
(should (ein:notebook-ask-before-kill-emacs)))))
|
||||
(should-not (gethash `(,ein:testing-notebook-dummy-url "Killed Notebook.ipynb") ein:notebook--opened-map))
|
||||
(should (= (hash-table-count ein:notebook--opened-map) 2))
|
||||
(cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest args) nil)))
|
||||
(ein:notebook-close-notebooks t)
|
||||
(should-not (ein:notebook-opened-notebooks)))))
|
||||
|
||||
|
||||
;;; 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)))))
|
||||
(should (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)))))
|
||||
(lexical-let ((buf (current-buffer)))
|
||||
(should-not (kill-buffer buf))
|
||||
(ein:testing-wait-until (lambda ()
|
||||
(not (buffer-live-p buf)))))))
|
||||
|
||||
(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 '("This notebook has unsaved changes. Discard those changes?")
|
||||
:output t))))
|
||||
(should (ein:notebook-ask-before-kill-buffer)))))
|
||||
(lexical-let ((buffer (current-buffer)))
|
||||
(call-interactively #'ein:worksheet-insert-cell-below)
|
||||
(should-not (kill-buffer buffer))
|
||||
(ein:testing-wait-until (lambda ()
|
||||
(not (buffer-live-p 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)))))
|
||||
(lexical-let ((buf (current-buffer)))
|
||||
(should (buffer-live-p buf))
|
||||
(with-current-buffer (ein:worksheet-buffer
|
||||
(ein:notebook-scratchsheet-open ein:%notebook%))
|
||||
(lexical-let ((buf2 (current-buffer)))
|
||||
(should-not (eq buf buf2))
|
||||
(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%))
|
||||
(should-not (kill-buffer buf2))
|
||||
(ein:testing-wait-until (lambda ()
|
||||
(not (buffer-live-p buf2))))))
|
||||
(should-not (ein:worksheet-modified-p ein:%worksheet%))
|
||||
(should-not (kill-buffer buf))
|
||||
(ein:testing-wait-until (lambda ()
|
||||
(not (buffer-live-p buf)))))))
|
||||
|
||||
|
||||
;; Misc unit tests
|
||||
|
||||
(ert-deftest ein:notebook-test-notebook-name-simple ()
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
|
||||
(ein:setq-if-not ein:testing-dump-file-log "./log/testein.log")
|
||||
(ein:setq-if-not ein:testing-dump-file-messages "./log/testein.messages")
|
||||
|
||||
(setq message-log-max t)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
(setq ein:testing-dump-file-log (concat default-directory "log/testfunc.log"))
|
||||
(setq ein:testing-dump-file-messages (concat default-directory "log/testfunc.messages"))
|
||||
(setq ein:testing-dump-file-server (concat default-directory "log/testfunc.server"))
|
||||
(setq ein:testing-dump-file-websocket (concat default-directory "log/testfunc.websocket"))
|
||||
(setq ein:testing-dump-file-request (concat default-directory "log/testfunc.request"))
|
||||
(with-eval-after-load "python"
|
||||
(setq python-indent-guess-indent-offset-verbose nil))
|
||||
|
|
|
@ -23,8 +23,8 @@ cask_install_or_reset() {
|
|||
mv ./Cask.tmp ./Cask
|
||||
set +x
|
||||
fi
|
||||
cask install || { rm -rf .cask && false; }
|
||||
cask update
|
||||
cask install </dev/null
|
||||
cask update </dev/null
|
||||
# travis cache
|
||||
rsync -vazSHe ssh .cask $HOME/
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue