notebooklist delete

This commit is contained in:
dickmao 2020-01-12 23:01:26 -05:00
parent b749cf7792
commit 6ea920dd0f
12 changed files with 180 additions and 126 deletions

View file

@ -37,15 +37,30 @@ Scenario: Stop after closing notebook
And I keep clicking "Resync" until "Stop"
And I click on "Stop"
And I switch to log expr "ein:log-all-buffer-name"
Then I should see "Deleted session"
Then I should see "kernel-delete-session--success"
And I am in notebooklist buffer
And I go to word "Untitled"
And I go to beginning of line
And I dump buffer
And I click without going top on "Open"
And no notebooks pending
And I switch to buffer like "Untitled"
@delete
Scenario: Delete closes buffers and sessions
Given I am in notebooklist buffer
And I click on "New Notebook"
And no notebooks pending
And I switch to buffer like "Untitled"
And I am in notebooklist buffer
And I clear log expr "ein:log-all-buffer-name"
And I click on "Delete"
And I wait for buffer to not say "Untitled"
Then eval "(should-not (ein:notebook-opened-notebooks)))"
Then eval "(should-not (seq-some (lambda (b) (cl-search "Untitled" (buffer-name b))) (buffer-list)))"
And I switch to log expr "ein:log-all-buffer-name"
Then I should see "kernel-delete-session--success"
Then I should see "notebooklist-delete-notebook--complete"
@content
Scenario: Read a massive directory
Given I create a directory "/var/tmp/fg7Cv8" with depth 4 and width 8

View file

@ -40,7 +40,7 @@
(When "^I kill kernel$"
(lambda ()
(ein:kernel-delete-session (ein:$notebook-kernel ein:%notebook%))
(ein:kernel-delete-session nil :kernel (ein:$notebook-kernel ein:%notebook%))
(And "I wait for the smoke to clear")))
(When "^my reconnect is questioned"
@ -122,6 +122,10 @@
do (sleep-for 0 500)
finally do (should (zerop (hash-table-count *ein:notebook--pending-query*))))))
(When "^no notebooks open$"
(lambda ()
(should ())))
(When "^I switch to buffer like \"\\(.+\\)\"$"
(lambda (substr)
(cl-loop repeat 10
@ -284,6 +288,10 @@
(When "I call \"ein:notebooklist-login\"")
(And "I wait for the smoke to clear")))))))
(When "^eval \"\\(.*\\)\"$"
(lambda (command)
(eval (car (read-from-string command)))))
(When "^I wait for completions \"\\(.+\\)\"$"
(lambda (key)
(cl-loop repeat 10

View file

@ -46,6 +46,7 @@
(ein:testing-flush-queries)
(with-current-buffer (ein:notebooklist-get-buffer (car (ein:jupyter-server-conn-info)))
(cl-loop for notebook in (ein:notebook-opened-notebooks)
for url-or-port = (ein:$notebook-url-or-port notebook)
for path = (ein:$notebook-notebook-path notebook)
for done-p = nil
do (ein:notebook-kill-kernel-then-close-command
@ -58,14 +59,14 @@
do (when (or (ob-ein-anonymous-p path)
(search "Untitled" path)
(search "Renamed" path))
(ein:notebooklist-delete-notebook path)
(ein:notebooklist-delete-notebook ein:%notebooklist% url-or-port path)
(cl-loop with fullpath = (concat (file-name-as-directory ein:testing-jupyter-server-root) path)
repeat 10
for extant = (file-exists-p fullpath)
until (not extant)
do (sleep-for 0 1000)
finally do (when extant
(ein:display-warning (format "cannot del %s" path)))))))
(ein:display-warning (format "cannot delete %s" path)))))))
(aif (ein:notebook-opened-notebooks)
(cl-loop for nb in it
for path = (ein:$notebook-notebook-path nb)

View file

@ -519,7 +519,7 @@ Return language name as a string or `nil' when not defined.
(when (and (not (slot-value cell 'collapsed))
(= index ein:cell-max-num-outputs)
(> (point) (point-at-bol)))
;; The first output which exceeds `ein:cell-max-num-outputs'.
;; The first output which exceeds `ein:cell-max-num-outputs'.
(ein:insert-read-only "\n"))
(ein:insert-read-only "."))
(let ((out (nth index (slot-value cell 'outputs))))
@ -819,7 +819,6 @@ If END is non-`nil', return the location of next element."
(cl-defmethod ein:cell-actually-append-output ((cell ein:codecell) json)
(ein:cell-expand cell)
;; (ein:flush-clear-timeout)
(setf (slot-value cell 'outputs)
(append (slot-value cell 'outputs) (list json)))
(let* ((inhibit-read-only t)
@ -840,7 +839,9 @@ If END is non-`nil', return the location of next element."
"Insert pyout type output in the buffer.
Called from ewoc pretty printer via `ein:cell-insert-output'."
(ein:insert-read-only (format "Out [%s]:"
(or (plist-get json :prompt_number) " "))
(or (plist-get json :prompt_number)
(plist-get json :execution_count)
" "))
'font-lock-face 'ein:cell-output-prompt)
(ein:insert-read-only "\n")
(ein:cell-append-mime-type json)
@ -951,8 +952,7 @@ Called from ewoc pretty printer via `ein:cell-insert-output'."
(metadata (slot-value cell 'metadata)))
`((source . ,(ein:cell-get-text cell))
(cell_type . "code")
,@(when execute-count
`((execution_count . ,execute-count)))
(execution_count . ,execute-count)
(outputs . ,(apply #'vector (slot-value cell 'outputs)))
(metadata . ,(plist-put metadata :collapsed (if (slot-value cell 'collapsed) t
json-false))))))
@ -1010,8 +1010,7 @@ Called from ewoc pretty printer via `ein:cell-insert-output'."
:clear_output (cons #'ein:cell--handle-clear-output cell)
:set_next_input (cons #'ein:cell--handle-set-next-input cell)))
(cl-defmethod ein:cell--handle-execute-reply ((cell ein:codecell) content
metadata)
(cl-defmethod ein:cell--handle-execute-reply ((cell ein:codecell) content metadata)
(run-hook-with-args 'ein:on-execute-reply-functions cell content metadata)
(ein:cell-set-input-prompt cell (plist-get content :execution_count))
(ein:cell-running-set cell nil)
@ -1027,14 +1026,31 @@ Called from ewoc pretty printer via `ein:cell-insert-output'."
(list :cell cell :text text))
(ein:events-trigger events 'maybe_reset_undo.Worksheet cell)))
(cl-defmethod ein:cell--handle-output ((cell ein:codecell) msg-type content meta)
;; (ein:output-area-convert-mime-types content (plist-get content :data))
(ein:cell-append-output cell
(plist-put
(plist-put content :output_type msg-type)
:metadata meta))
;; (setf (slot-value cell 'dirty) t)
(ein:events-trigger (slot-value cell 'events) 'maybe_reset_undo.Worksheet cell))
(cl-defmethod ein:cell--handle-output ((cell ein:codecell) msg-type content _metadata)
(let ((json `(:output_type ,msg-type)))
(cl-macrolet ((copy-props
(src tgt props)
`(mapc (lambda (kw)
(let ((val (plist-get ,src kw)))
(when (and (null val) (plist-member ,src kw))
(setq val (make-hash-table)))
(setq ,tgt (plist-put ,tgt kw val))))
,props)))
(ein:case-equal msg-type
(("stream")
(copy-props content json '(:name :text)))
(("display_data")
(copy-props content json '(:data :metadata)))
(("execute_result" "pyout")
(copy-props content json '(:execution_count :data :metadata)))
(("error" "pyerr")
(copy-props content json '(:ename :evalue :traceback)))
(t
(ein:log 'error "ein:cell--handle-output: unhandled msg_type '%s'" msg-type)
(setq json nil))))
(when json
(ein:cell-append-output cell json)
(ein:events-trigger (slot-value cell 'events) 'maybe_reset_undo.Worksheet cell))))
(cl-defmethod ein:cell--handle-clear-output ((cell ein:codecell) content
_metadata)

View file

@ -179,7 +179,6 @@
(metadata :initarg :metadata :initform nil :accessor ein:worksheet--metadata)
(events :initarg :events :accessor ein:worksheet--events)))
;;; Kernel
(defstruct ein:$kernelspec
"Kernel specification as return by the Jupyter notebook server.

View file

@ -141,13 +141,13 @@
(cl-defun ein:kernel-restart-session (kernel)
"Server side delete of KERNEL session and subsequent restart with all new state"
(ein:kernel-delete-session
kernel
(lambda (kernel)
(ein:events-trigger (ein:$kernel-events kernel) 'status_restarting.Kernel)
(ein:kernel-retrieve-session kernel 0
(lambda (kernel)
(ein:events-trigger (ein:$kernel-events kernel)
'status_restarted.Kernel))))))
'status_restarted.Kernel))))
:kernel kernel))
(cl-defun ein:kernel-retrieve-session (kernel &optional iteration callback)
"Formerly ein:kernel-start, but that was misnomer because 1. the server really starts a session (and an accompanying kernel), and 2. it may not even start a session if one exists for the same path.
@ -533,33 +533,36 @@ Example::
:success (lambda (&rest ignore)
(ein:log 'info "Sent interruption command.")))))
(defun ein:kernel-delete--from-session-id (url session-id &optional callback)
"Stop/delete a running kernel from a session id. May also specify a callback function of 0 args to be called once oepration is complete.
We need this to have proper behavior for the 'Stop' command in the ein:notebooklist buffer."
(ein:query-singleton-ajax
(ein:url url "api/sessions" session-id)
:success (apply-partially #'ein:kernel-delete--from-session-complete session-id callback)
:error (apply-partially #'ein:kernel-delete--from-session-error session-id)
:type "DELETE"))
(defun ein:kernel-delete--from-session-complete (session-id callback &rest _)
(ein:log 'info "Deleted session %s and its associated kernel process." session-id)
(when callback
(funcall callback)))
(defun ein:kernel-delete--from-session-error (session-id &rest _)
(ein:log 'info "Error, could not delete session %s." session-id))
(defun ein:kernel-delete-session (kernel &optional callback)
(cl-defun ein:kernel-delete-session (&optional callback
&key url-or-port path kernel
&aux (session-id))
"Regardless of success or error, we clear all state variables of kernel and funcall CALLBACK (kernel)"
(ein:and-let* ((session-id (ein:$kernel-session-id kernel)))
(ein:query-singleton-ajax
(ein:url (ein:$kernel-url-or-port kernel) "api/sessions" session-id)
:type "DELETE"
:complete (apply-partially #'ein:kernel-delete-session--complete kernel session-id callback)
:error (apply-partially #'ein:kernel-delete-session--error session-id callback)
:success (apply-partially #'ein:kernel-delete-session--success session-id callback))))
(cond (kernel
(setq url-or-port (ein:$kernel-url-or-port kernel))
(setq path (ein:$kernel-path kernel))
(setq session-id (ein:$kernel-session-id kernel)))
((and url-or-port path)
(aif (ein:notebook-get-opened-notebook url-or-port path)
(progn
(setq kernel (ein:$notebook-kernel it))
(setq session-id (ein:$kernel-session-id kernel)))
(let ((ein:force-sync t))
(ein:content-query-sessions
url-or-port
(lambda (session-hash)
(setq session-id (car (gethash path session-hash))))
nil))))
(t (error "ein:kernel-delete-session: need kernel, or url-or-port and path")))
(if session-id
(ein:query-singleton-ajax
(ein:url url-or-port "api/sessions" session-id)
:type "DELETE"
:complete (apply-partially #'ein:kernel-delete-session--complete kernel session-id callback)
:error (apply-partially #'ein:kernel-delete-session--error session-id callback)
:success (apply-partially #'ein:kernel-delete-session--success session-id callback))
(ein:log 'verbose "ein:kernel-delete-session: no sessions found for %s" path)
(when callback
(funcall callback kernel))))
(cl-defun ein:kernel-delete-session--error (session-id callback
&key response error-thrown
@ -577,7 +580,8 @@ We need this to have proper behavior for the 'Stop' command in the ein:notebookl
&allow-other-keys
&aux (resp-string (format "STATUS: %s DATA: %s" (request-response-status-code response) data)))
(ein:log 'debug "ein:kernel-delete-session--complete %s" resp-string)
(ein:kernel-disconnect kernel)
(when kernel
(ein:kernel-disconnect kernel))
(when callback (funcall callback kernel)))
;; Reply handlers.
@ -669,7 +673,7 @@ We need this to have proper behavior for the 'Stop' command in the ein:notebookl
msg-type msg-id)
(ein:case-equal msg-type
(("stream" "display_data" "pyout" "pyerr" "error" "execute_result")
(aif (plist-get callbacks :output)
(aif (plist-get callbacks :output) ;; ein:cell--handle-output
(ein:funcall-packed it msg-type content metadata)
(ein:log 'warn (concat "ein:kernel--handle-iopub-reply: "
"No :output callback for msg_id=%s")

View file

@ -830,18 +830,16 @@ as usual."
(declare (indent defun))
(interactive (list (ein:notebook--get-nb-or-error) nil))
(unless callback1 (setq callback1 #'ignore))
(lexical-let ((callback1 callback1))
(let ((kernel (ein:$notebook-kernel notebook))
(callback (apply-partially
(lambda (notebook* kernel*)
(ein:notebook-close notebook*)
(funcall callback1 kernel*))
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)))))
(let* ((kernel (ein:$notebook-kernel notebook))
(callback (apply-partially
(lambda (notebook* cb* kernel*)
(ein:notebook-close notebook*)
(funcall cb* kernel*))
notebook callback1)))
(if (ein:kernel-live-p kernel)
(ein:message-whir "Ending session" callback
(ein:kernel-delete-session callback :kernel kernel))
(funcall callback nil))))
(defun ein:fast-content-from-notebook (notebook)
"Quickly generate a basic content structure from notebook. This

View file

@ -406,34 +406,36 @@ This function is called via `ein:notebook-after-rename-hook'."
name))
(ein:notebooklist-new-notebook url-or-port kernelspec callback no-pop))
(defun ein:notebooklist-delete-notebook-ask (path)
(when (y-or-n-p (format "Delete notebook %s?" path))
(ein:notebooklist-delete-notebook path)))
(defun ein:notebooklist-delete-notebook (path &optional callback)
(defun ein:notebooklist-delete-notebook (notebooklist url-or-port path &optional callback)
"CALLBACK with no arguments, e.g., semaphore"
(lexical-let* ((path path)
(notebooklist ein:%notebooklist%)
(callback callback)
(url-or-port (ein:$notebooklist-url-or-port notebooklist)))
(unless callback
(setq callback (apply-partially #'ein:notebooklist-reload notebooklist)))
(dolist (buf (seq-filter (lambda (b)
(with-current-buffer b
(aif (ein:get-notebook)
(string= path (ein:$notebook-notebook-path it)))))
(buffer-list)))
(kill-buffer buf))
(if (ein:notebook-opened-notebooks
(lambda (nb)
(string= path (ein:$notebook-notebook-path nb))))
(ein:log 'error "ein:notebooklist-delete-notebook: cannot close %s" path)
(ein:query-singleton-ajax
(ein:notebook-url-from-url-and-id
url-or-port (ein:$notebooklist-api-version notebooklist) path)
:type "DELETE"
:complete (apply-partially #'ein:notebooklist-delete-notebook--complete
(ein:url url-or-port path) callback)))))
(declare (indent defun))
(unless callback (setq callback #'ignore))
(dolist (buf (seq-filter (lambda (b)
(with-current-buffer b
(aif (ein:get-notebook)
(string= path (ein:$notebook-notebook-path it)))))
(buffer-list)))
(cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest _args) nil)))
(kill-buffer buf)))
(if (ein:notebook-opened-notebooks (lambda (nb)
(string= path
(ein:$notebook-notebook-path nb))))
(ein:log 'error "ein:notebooklist-delete-notebook: cannot close %s" path)
(let ((delete-nb
(apply-partially
(lambda (url* settings* _kernel)
(apply #'ein:query-singleton-ajax url* settings*))
(ein:notebook-url-from-url-and-id
url-or-port (ein:$notebooklist-api-version notebooklist) path)
(list :type "DELETE"
:complete (apply-partially
#'ein:notebooklist-delete-notebook--complete
(ein:url url-or-port path) callback)))))
(ein:message-whir
"Ending session" delete-nb
(ein:kernel-delete-session delete-nb
:url-or-port url-or-port
:path path)))))
(cl-defun ein:notebooklist-delete-notebook--complete
(url callback
@ -556,7 +558,10 @@ This function is called via `ein:notebook-after-rename-hook'."
(ein:make-sorting-widget "Sort by" ein:notebooklist-sort-field)
(ein:make-sorting-widget "In Order" ein:notebooklist-sort-order)
(widget-insert "\n")
(cl-loop for note in (ein:notebooklist--order-data
(cl-loop with reloader = (apply-partially (lambda (nblist _kernel)
(ein:notebooklist-reload nblist))
ein:%notebooklist%)
for note in (ein:notebooklist--order-data
(ein:$notebooklist-data ein:%notebooklist%)
ein:notebooklist-sort-field
ein:notebooklist-sort-order)
@ -602,28 +607,41 @@ This function is called via `ein:notebook-after-rename-hook'."
if (string= type "notebook")
do (progn (widget-create
'link
:notify (lexical-let ((url-or-port url-or-port)
(path path))
(lambda (&rest _ignore)
(run-at-time 3 nil #'ein:notebooklist-reload)
(ein:notebook-open url-or-port path)))
:notify (apply-partially
(lambda (url-or-port* path* reloader* &rest _args)
(ein:notebook-open url-or-port* path* nil reloader*))
url-or-port path
(apply-partially (lambda (reloader* &rest _args)
(funcall reloader* nil))
reloader))
"Open")
(widget-insert " ")
(if (gethash path sessions)
(widget-create
'link
:notify (lexical-let ((url url-or-port)
(session (car (gethash path sessions))))
(lambda (&rest _ignore)
(ein:kernel-delete--from-session-id url session #'ein:notebooklist-reload)))
:notify
(apply-partially
(lambda (callback* url-or-port* path* &rest _ignore)
(ein:message-whir
"Ending session" callback*
(ein:kernel-delete-session callback*
:url-or-port url-or-port*
:path path*)))
reloader url-or-port path)
"Stop")
(widget-insert "------"))
(widget-insert " ")
(widget-create
'link
:notify (lexical-let ((path path))
(lambda (&rest _ignore)
(ein:notebooklist-delete-notebook-ask path)))
:notify (apply-partially
(lambda (notebooklist* url-or-port* path* callback*
&rest _args)
(when (or noninteractive
(y-or-n-p (format "Delete notebook %s?" path*)))
(ein:notebooklist-delete-notebook
notebooklist* url-or-port* path*
(apply-partially callback* nil))))
ein:%notebooklist% url-or-port path reloader)
"Delete")
(widget-insert " : " (ein:format-nbitem-data name last-modified))
(widget-insert "\n"))

View file

@ -226,9 +226,6 @@ at point, i.e. any word before then \"(\", if it is present."
See: http://api.jquery.com/jQuery.ajax/"
(concat url (format-time-string "?_=%s")))
;;; HTML utils
(defun ein:html-get-data-in-body-tag (key)
"Very ad-hoc parser to get data in body tag."
(ignore-errors
@ -238,9 +235,6 @@ See: http://api.jquery.com/jQuery.ajax/"
(search-forward-regexp (format "%s=\\([^[:space:]\n]+\\)" key))
(match-string 1))))
;;; JSON utils
(defmacro ein:with-json-setting (&rest body)
`(let ((json-object-type 'plist)
(json-array-type 'list))
@ -288,9 +282,6 @@ See: http://api.jquery.com/jQuery.ajax/"
;; (setq ad-return-value "{}")
;; ad-do-it))
;;; EWOC
(defun ein:ewoc-create (pretty-printer &optional header footer nosep)
"Do nothing wrapper of `ewoc-create' to provide better error message."
(condition-case nil
@ -392,7 +383,6 @@ Adapted from twittering-mode.el's `case-string'."
,@body)))
clauses)))
;;; Text manipulation on buffer
(defun ein:find-leftmost-column (beg end)
@ -604,14 +594,16 @@ otherwise it should be a function, which is called on `time'."
(string (format-time-string format time))
(function (funcall format time))))
;;; Emacs utilities
(defmacro ein:message-whir (mesg &rest body)
(defmacro ein:message-whir (mesg callback &rest body)
"Display MESG with a modest animation until ASYNC-CALL completes."
`(lexical-let* (done-p
(done-callback (lambda (&rest ignore) (setf done-p t)))
(errback (lambda (&rest ignore) (setf done-p 'error))))
(ein:message-whir-subr ,mesg (lambda () done-p))
(done-callback (lambda (&rest _args) (setf done-p t)))
(errback (lambda (&rest _args) (setf done-p 'error))))
;; again, how can done-callback remove itself after running?
(add-function :before ,callback done-callback)
(unless noninteractive
(ein:message-whir-subr ,mesg (lambda () done-p)))
,@body))
(defun ein:message-whir-subr (mesg doneback)
@ -626,7 +618,7 @@ DONEBACK returns t or 'error when calling process is done, and nil if not done."
;; "complicated timings of macro expansion lexical-let, deferred:lambda"
;; using deferred:loop instead
(deferred:$
(deferred:loop (cl-loop for i from 1 below 30 by 1 collect i)
(deferred:loop (cl-loop for i from 1 below 60 by 1 collect i)
(lambda ()
(deferred:$
(deferred:next

View file

@ -343,7 +343,8 @@ if necessary. Install CALLBACK (i.e., cell execution) upon notebook retrieval."
do (sleep-for 0 500)
finally do (if extant
(ein:display-warning
(format "cannot del path=%s nbpath=%s" fullpath nbpath))
(format "cannot delete path=%s nbpath=%s"
fullpath nbpath))
(ob-ein--initiate-session session kernelspec callback))))
(notebook (funcall callback notebook))
((string= (url-host parsed-url) ein:url-localhost)

View file

@ -54,17 +54,18 @@
(dummy-response (make-request-response))
got-url)
(cl-letf (((symbol-function 'request)
(lambda (url &rest ignore) (setq got-url url) dummy-response))
(lambda (url &rest _ignore) (setq got-url url) dummy-response))
((symbol-function 'set-process-query-on-exit-flag) #'ignore)
((symbol-function 'ein:kernel-stop-channels) #'ignore)
((symbol-function 'ein:websocket) (lambda (&rest ignore) (make-ein:$websocket :ws nil :kernel kernel :closed-by-client nil)))
((symbol-function 'ein:websocket-open-p) (lambda (&rest ignore) t)))
((symbol-function 'ein:websocket)
(lambda (&rest ignore) (make-ein:$websocket :ws nil :kernel kernel
:closed-by-client nil)))
((symbol-function 'ein:websocket-open-p) (lambda (&rest _ignore) t)))
(ein:kernel-retrieve-session--success
kernel nil :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id))
(ein:kernel-delete-session kernel))
(ein:kernel-delete-session nil :kernel kernel))
(should (equal got-url desired-url))))
;;; Test `ein:kernel-construct-help-string'
(ert-deftest ein:kernel-construct-help-string-when-found ()

View file

@ -924,7 +924,8 @@ defined."
(let ((buffer (current-buffer))
(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))))
((symbol-function 'ein:kernel-delete-session)
(cl-function (lambda (callback &key kernel) (funcall callback kernel)))))
(call-interactively #'ein:notebook-kill-kernel-then-close-command))
(ein:testing-notebook-should-be-closed notebook buffer))))