From ee3b0f095c7b3b53a512817e3993b4f5036b8e5d Mon Sep 17 00:00:00 2001 From: dickmao Date: Sat, 3 Nov 2018 16:02:04 -0400 Subject: [PATCH] Follow-up to cleaning up websockets Renaming of functions with better understanding of reconnects. Under the original logic, reconnecting blithely created a new session if the original no longer existed. Now it will alert the user when this happens. Also hopefully fixes #381 as there was a bug of missing argument not being caught by lisp's undisciplined typing. --- features/notebook.feature | 20 ++- features/step-definitions/ein-steps.el | 19 ++- lisp/ein-classes.el | 4 +- lisp/ein-core.el | 17 +-- lisp/ein-kernel.el | 166 ++++++++++++++++--------- lisp/ein-notebook.el | 95 +++++++------- lisp/ein-notebooklist.el | 20 +-- test/ein-testing-notebook.el | 5 +- test/test-ein-kernel.el | 17 +-- test/test-ein-notebook.el | 2 +- 10 files changed, 217 insertions(+), 148 deletions(-) diff --git a/features/notebook.feature b/features/notebook.feature index 180db2e..d91529c 100644 --- a/features/notebook.feature +++ b/features/notebook.feature @@ -16,7 +16,7 @@ Scenario: not running server locally Then I should not see "ein:completions--prepare-oinfo" @reconnect -Scenario: kernel restart succeeds +Scenario: kernel reconnect succeeds Given new default notebook When I type "import math" And I wait for cell to execute @@ -25,14 +25,26 @@ Scenario: kernel restart succeeds Then I should see "WS closed unexpectedly" And I switch to buffer like "Untitled" And header says "Kernel requires reconnect C-c C-r" + And I clear log expr "ein:log-all-buffer-name" And I press "C-c C-r" And I wait for the smoke to clear And header does not say "Kernel requires reconnect C-c C-r" - And I clear log expr "ein:log-all-buffer-name" - And I reconnect kernel And I switch to log expr "ein:log-all-buffer-name" Then I should not see "[warn]" And I should not see "[error]" - And I should see "ein:kernel-start--complete" + And I should see "ein:kernel-retrieve-session--complete" + And I switch to buffer like "Untitled" + And I clear log expr "ein:log-all-buffer-name" + And I restart kernel + And I switch to log expr "ein:log-all-buffer-name" + Then I should not see "[warn]" + And I should not see "[error]" + And I should see "ein:kernel-retrieve-session--complete" + And I switch to buffer like "Untitled" + And I kill kernel + And header says "Kernel requires reconnect C-c C-r" + And I clear log expr "ein:log-all-buffer-name" + And my reconnect is questioned + \ No newline at end of file diff --git a/features/step-definitions/ein-steps.el b/features/step-definitions/ein-steps.el index c0beba1..6bcec9c 100644 --- a/features/step-definitions/ein-steps.el +++ b/features/step-definitions/ein-steps.el @@ -1,16 +1,27 @@ -(When "with no opened notebooks call \"\\(.+\\)\"$" +(When "^with no opened notebooks call \"\\(.+\\)\"$" (lambda (func) (cl-letf (((symbol-function 'ein:notebook-opened-buffer-names) #'ignore)) (When (format "I call \"%s\"" func))))) -(When "header \\(does not \\)?says? \"\\(.+\\)\"$" +(When "^header \\(does not \\)?says? \"\\(.+\\)\"$" (lambda (negate says) (let ((equal-p (string= says (slot-value (slot-value ein:%notification% 'kernel) 'message)))) (cl-assert (if negate (not equal-p) equal-p))))) -(When "I reconnect kernel$" +(When "^I kill kernel$" (lambda () - (ein:notebook-reconnect-kernel) + (ein:kernel-delete-session (ein:$notebook-kernel ein:%notebook%)) + (And "I wait for the smoke to clear"))) + +(When "^my reconnect is questioned" + (lambda () + (ein:notebook-reconnect-session-command (lambda (notebook session-p) + (assert (not session-p)))))) + +(When "I restart kernel$" + (lambda () + (cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest ignore) t))) + (ein:notebook-restart-session-command)) (And "I wait for the smoke to clear"))) (When "I call eldoc-documentation-function$" diff --git a/lisp/ein-classes.el b/lisp/ein-classes.el index 446be4a..a3bf530 100644 --- a/lisp/ein-classes.el +++ b/lisp/ein-classes.el @@ -228,10 +228,8 @@ ;; FIXME: Rewrite `ein:$kernel' using `defclass'. It should ease ;; testing since I can mock I/O using method overriding. (defstruct ein:$kernel - "Hold kernel variables. + "Should perhaps be named ein:$session. We glom session and kernel as defined by the server as just ein:$kernel in the client. -`ein:$kernel-url-or-port' - URL or port of IPython server. " url-or-port events diff --git a/lisp/ein-core.el b/lisp/ein-core.el index 2cc87e1..7911e01 100644 --- a/lisp/ein-core.el +++ b/lisp/ein-core.el @@ -161,11 +161,11 @@ the source is in git repository." :timeout ein:content-query-timeout :parser 'ein:json-read :sync ein:force-sync - :complete (apply-partially #'ein:query-kernelspecs--complete url-or-port callback) - :success (apply-partially #'ein:query-kernelspecs--success url-or-port) + :complete (apply-partially #'ein:query-kernelspecs--complete url-or-port) + :success (apply-partially #'ein:query-kernelspecs--success url-or-port callback) :error (apply-partially #'ein:query-kernelspecs--error url-or-port callback iteration))) -(defun* ein:query-kernelspecs--success (url-or-port +(defun* ein:query-kernelspecs--success (url-or-port callback &key data symbol-status response &allow-other-keys) (let ((ks (list :default (plist-get data :default))) @@ -181,7 +181,8 @@ the source is in git repository." :language (plist-get (plist-get info :spec) :language) :spec (plist-get info :spec))) - ks))))))) + ks)))))) + (when callback (funcall callback))) (defun* ein:query-kernelspecs--error (url-or-port callback iteration &key response error-thrown @@ -191,13 +192,13 @@ the source is in git repository." (ein:log 'verbose "Retry kernelspecs #%s in response to %s" iteration (request-response-status-code response)) (ein:query-kernelspecs url-or-port callback (1+ iteration))) (ein:log 'error - "ein:query-kernelspecs--error %s: ERROR %s DATA %s" url-or-port (car error-thrown) (cdr error-thrown)))) + "ein:query-kernelspecs--error %s: ERROR %s DATA %s" url-or-port (car error-thrown) (cdr error-thrown)) + (when callback (funcall callback)))) -(defun* ein:query-kernelspecs--complete (url-or-port callback &key data response +(defun* ein:query-kernelspecs--complete (url-or-port &key data response &allow-other-keys &aux (resp-string (format "STATUS: %s DATA: %s" (request-response-status-code response) data))) - (ein:log 'debug "ein:query-kernelspecs--complete %s" resp-string) - (when callback (funcall callback))) + (ein:log 'debug "ein:query-kernelspecs--complete %s" resp-string)) (defsubst ein:get-ipython-major-version (vstr) (if vstr diff --git a/lisp/ein-kernel.el b/lisp/ein-kernel.el index 5230b14..b884cc5 100644 --- a/lisp/ein-kernel.el +++ b/lisp/ein-kernel.el @@ -20,8 +20,11 @@ ;; along with ein-kernel.el. If not, see . ;;; Commentary: - -;; +;; `ein:kernel' is the proxy class of notebook server state. +;; It agglomerates both the "kernel" and "session" objects of server described here +;; https://github.com/jupyter/jupyter/wiki/Jupyter-Notebook-Server-API +;; It may have been better to keep them separate to allow parallel reasoning with +;; the notebook server, but that time is past. ;;; Code: @@ -95,18 +98,80 @@ :content content :parent_header (make-hash-table))) -(defun ein:kernel-start (kernel notebook &optional iteration callback) - "Start kernel of the notebook whose id is NOTEBOOK-ID. +(defun* ein:kernel-session-p (notebook callback &optional iteration &aux (kernel (ein:$notebook-kernel notebook))) + "Don't make any changes on the server side. CALLBACK with arity 1, a boolean whether session exists on server." + (unless iteration + (setq iteration 0)) + (let ((session-id (ein:$kernel-session-id kernel))) + (ein:query-singleton-ajax + (list 'kernel-session-p session-id) + (ein:url (ein:$kernel-url-or-port kernel) "api/sessions" session-id) + :type "GET" + :sync ein:force-sync + :parser #'ein:json-read + :complete (apply-partially #'ein:kernel-session-p--complete session-id) + :success (apply-partially #'ein:kernel-session-p--success session-id callback) + :error (apply-partially #'ein:kernel-session-p--error notebook callback iteration)))) -CALLBACK of arity 0 (e.g., print a message kernel started)" - (assert (and (ein:$notebook-p notebook) (ein:$kernel-p kernel))) +(defun* ein:kernel-session-p--complete (session-id &key data response + &allow-other-keys + &aux (resp-string (format "STATUS: %s DATA: %s" (request-response-status-code response) data))) + (ein:log 'debug "ein:kernel-session-p--complete %s" resp-string)) + +(defun* ein:kernel-session-p--error (notebook callback iteration &key error-thrown symbol-status data &allow-other-keys) + (if (ein:aand (plist-get data :message) (search "not found" it)) + (when callback (funcall callback nil)) + (let* ((max-tries 3) + (tries-left (1- (- max-tries iteration)))) + (ein:log 'verbose "ein:kernel-session-p--error [%s], %s tries left" + (car error-thrown) tries-left) + (if (> tries-left 0) + (ein:kernel-session-p notebook callback (1+ iteration)))))) + +(defun* ein:kernel-session-p--success (session-id callback &key data &allow-other-keys) + (let ((session-p (equal (plist-get data :id) session-id))) + (ein:log 'verbose "ein:kernel-session-p--success: session-id=%s session-p=%s" + session-id session-p) + (when callback (funcall callback session-p)))) + +(defun* ein:kernel-restart-session (notebook + &aux (kernel (ein:$notebook-kernel notebook))) + "Server side delete of NOTEBOOK session and subsequent restart with all new state" + (ein:kernel-delete-session kernel + (apply-partially + (lambda (notebook*) + (ein:events-trigger (ein:$kernel-events kernel) + 'status_restarting.Kernel) + (ein:kernel-retrieve-session notebook* 0 + (apply-partially + (lambda (nb) + (with-current-buffer (ein:notebook-buffer nb) + (ein:notification-status-set + (slot-value ein:%notification% 'kernel) + 'status_restarted.Kernel))) + notebook*))) + notebook))) + +(defun* ein:kernel-retrieve-session (notebook &optional iteration callback + &aux (kernel (ein:$notebook-kernel notebook))) + "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. + +The server logic is here (could not find other documentation) +https://github.com/jupyter/notebook/blob/04a686dbaf9dfe553324a03cb9e6f778cf1e3da1/notebook/services/sessions/handlers.py#L56-L81 + +CALLBACK of arity 0 (e.g., print a message kernel started) + +This no longer works in iPython-2.0. Protocol is to create a session for a +notebook, which will automatically create and associate a kernel with the notebook. +" (unless iteration (setq iteration 0)) (if (<= (ein:$kernel-api-version kernel) 2) - (error "Api version %s unsupported" (ein:$kernel-api-version kernel)) - (let ((kernelspec (ein:$notebook-kernelspec notebook))) + (error "Api %s unsupported" (ein:$kernel-api-version kernel)) + (let ((kernelspec (ein:$notebook-kernelspec notebook)) + (kernel-id (ein:$kernel-kernel-id kernel))) (ein:query-singleton-ajax - (list 'kernel-start (ein:$kernel-kernel-id kernel)) + (list 'kernel-retrieve-session kernel-id) (ein:url (ein:$kernel-url-or-port kernel) "api/sessions") :type "POST" :data (json-encode @@ -118,55 +183,37 @@ CALLBACK of arity 0 (e.g., print a message kernel started)" (("name" . ,(ein:$kernelspec-name kernelspec)))))))) (t `(("path" . ,(ein:$notebook-notebook-path notebook)) ("type" . "notebook") - ,@(if kernelspec - `(("kernel" . - (("name" . ,(ein:$kernelspec-name kernelspec)))))))))) + ("kernel" . + (,@(if kernelspec + `(("name" . ,(ein:$kernelspec-name kernelspec)))) + ,@(if kernel-id + `(("id" . ,kernel-id))))) + )))) :sync ein:force-sync :parser #'ein:json-read - :complete (apply-partially #'ein:kernel-start--complete kernel callback) - :success (apply-partially #'ein:kernel-start--success kernel callback) - :error (apply-partially #'ein:kernel-start--error kernel notebook iteration callback))))) + :complete (apply-partially #'ein:kernel-retrieve-session--complete kernel callback) + :success (apply-partially #'ein:kernel-retrieve-session--success kernel callback) + :error (apply-partially #'ein:kernel-retrieve-session--error notebook iteration callback))))) -(defun ein:kernel-restart (kernel) - "Will not restart kernel if kernel doesn't have a session-id. - -Kernel can be dead (as in the websocket died unexpectedly) but must be fully-formed for the restart." - (ein:kernel-delete kernel - (apply-partially - (lambda (kernel* notebook) - (if (ein:kernel-live-p kernel*) - (ein:log 'error "Kernel %s still live!" (ein:$kernel-kernel-id kernel*)) - (ein:events-trigger (ein:$kernel-events kernel*) - 'status_restarting.Kernel) - (ein:kernel-start kernel* notebook 0 - (apply-partially - (lambda (nb) - (with-current-buffer (ein:notebook-buffer nb) - (ein:notification-status-set - (slot-value ein:%notification% 'kernel) - 'status_restarted.Kernel))) - notebook)))) - kernel (ein:get-notebook-or-error)))) - -(defun* ein:kernel-start--complete (kernel callback &key data response - &allow-other-keys +(defun* ein:kernel-retrieve-session--complete (kernel callback &key data response + &allow-other-keys &aux (resp-string (format "STATUS: %s DATA: %s" (request-response-status-code response) data))) - (ein:log 'debug "ein:kernel-start--complete %s" resp-string)) + (ein:log 'debug "ein:kernel-retrieve-session--complete %s" resp-string)) -(defun* ein:kernel-start--error (kernel notebook iteration callback &key error-thrown sybmol-status &allow-other-keys) +(defun* ein:kernel-retrieve-session--error (notebook iteration callback &key error-thrown symbol-status &allow-other-keys) (let* ((max-tries 3) (tries-left (1- (- max-tries iteration)))) - (ein:log 'verbose "ein:kernel-start--error [%s], %s tries left" + (ein:log 'verbose "ein:kernel-retrieve-session--error [%s], %s tries left" (car error-thrown) tries-left) (if (> tries-left 0) - (ein:kernel-start kernel notebook (1+ iteration) callback)))) + (ein:kernel-retrieve-session notebook (1+ iteration) callback)))) -(defun* ein:kernel-start--success (kernel callback &key data &allow-other-keys) +(defun* ein:kernel-retrieve-session--success (kernel callback &key data &allow-other-keys) (let ((session-id (plist-get data :id))) (if (plist-get data :kernel) (setq data (plist-get data :kernel))) (destructuring-bind (&key id &allow-other-keys) data - (ein:log 'verbose "ein:kernel-start--success: kernel-id=%s session-id=%s" + (ein:log 'verbose "ein:kernel-retrieve-session--success: kernel-id=%s session-id=%s" id session-id) (setf (ein:$kernel-kernel-id kernel) id) (setf (ein:$kernel-session-id kernel) session-id) @@ -255,8 +302,8 @@ See: https://github.com/ipython/ipython/pull/3307" (ein:$kernel-after-start-hook kernel))) (defun ein:kernel-disconnect (kernel) - "Disconnect websocket connection to running kernel, but do not -kill the kernel." + "Close websocket connection to running kernel, but do not +delete the kernel on the server side" (ein:events-trigger (ein:$kernel-events kernel) 'status_disconnected.Kernel) (ein:aif (ein:$kernel-websocket kernel) (progn (ein:websocket-close it) @@ -558,32 +605,31 @@ Example:: :success (lambda (&rest ignore) (ein:log 'info "Sent interruption command."))))) -(defun ein:kernel-delete (kernel &optional callback) +(defun ein:kernel-delete-session (kernel &optional callback) "Regardless of success or error, we clear all state variables of kernel and funcall CALLBACK of arity 0 (e.g., kernel restart)" - (ein:and-let* ((kernel kernel) - (session-id (ein:$kernel-session-id kernel))) + (ein:and-let* ((session-id (ein:$kernel-session-id kernel))) (ein:query-singleton-ajax - (list 'kernel-delete session-id) + (list 'kernel-delete-session session-id) (ein:url (ein:$kernel-url-or-port kernel) "api/sessions" session-id) :type "DELETE" - :complete (apply-partially #'ein:kernel-delete--complete kernel session-id callback) - :error (apply-partially #'ein:kernel-delete--error session-id callback) - :success (apply-partially #'ein:kernel-delete--success session-id callback)))) + :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)))) -(defun* ein:kernel-delete--error (session-id callback +(defun* ein:kernel-delete-session--error (session-id callback &key response error-thrown &allow-other-keys) - (ein:log 'error "ein:kernel-delete--error %s: ERROR %s DATA %s" + (ein:log 'error "ein:kernel-delete-session--error %s: ERROR %s DATA %s" session-id (car error-thrown) (cdr error-thrown))) -(defun* ein:kernel-delete--success (session-id callback &key data symbol-status response +(defun* ein:kernel-delete-session--success (session-id callback &key data symbol-status response &allow-other-keys) - (ein:log 'verbose "ein:kernel-delete--success: %s deleted" session-id)) + (ein:log 'verbose "ein:kernel-delete-session--success: %s deleted" session-id)) -(defun* ein:kernel-delete--complete (kernel session-id callback &key data response - &allow-other-keys +(defun* ein:kernel-delete-session--complete (kernel session-id callback &key data response + &allow-other-keys &aux (resp-string (format "STATUS: %s DATA: %s" (request-response-status-code response) data))) - (ein:log 'debug "ein:kernel-delete--complete %s" resp-string) + (ein:log 'debug "ein:kernel-delete-session--complete %s" resp-string) (ein:kernel-disconnect kernel) (when callback (funcall callback))) diff --git a/lisp/ein-notebook.el b/lisp/ein-notebook.el index 952ec0d..7489b3c 100644 --- a/lisp/ein-notebook.el +++ b/lisp/ein-notebook.el @@ -253,7 +253,7 @@ jupyter are concerned, 'localhost:8888' and '127.0.0.1:8888' are (ein:$notebook-notebook-name notebook)) (setf (ein:$notebook-url-or-port notebook) new-url-or-port) (with-current-buffer (ein:notebook-buffer notebook) - (ein:notebook-start-kernel notebook) + (ein:notebook-retrieve-session notebook) (rename-buffer (format ein:notebook-buffer-name-template (ein:$notebook-url-or-port notebook) (ein:$notebook-notebook-name notebook))))) @@ -369,7 +369,7 @@ notebook buffer. Let's warn for now to see who is doing this. (ein:$notebook-notebook-name notebook) (ein:$content-name content)) (ein:notebook-bind-events notebook (ein:events-new)) (ein:notebook-maybe-set-kernelspec notebook (plist-get (ein:$content-raw-content content) :metadata)) - (ein:notebook-start-kernel notebook) + (ein:notebook-retrieve-session notebook) (ein:notebook-from-json notebook (ein:$content-raw-content content)) ; notebook buffer is created here (setf (ein:$notebook-kernelinfo notebook) (ein:kernelinfo-new (ein:$notebook-kernel notebook) @@ -485,27 +485,14 @@ notebook buffer." (ein:$notebook-notebook-name notebook)) (ein:$notebook-events notebook)))) -(defun ein:notebook-reconnect-kernel () - "Assuming server will give us back the old kernel id if we send the same noteobook path. TODO - Need to re-verify." - (interactive) - (ein:aif ein:%notebook% - (progn - (ein:kernel-disconnect (ein:$notebook-kernel it)) - (ein:events-trigger (ein:$kernel-events (ein:$notebook-kernel it)) - 'status_reconnecting.Kernel) - (ein:kernel-start (ein:$notebook-kernel it) it - (apply-partially - (lambda (nb) - (with-current-buffer (ein:notebook-buffer nb) - (ein:notification-status-set - (slot-value ein:%notification% 'kernel) - 'status_reconnected.Kernel))) - it))))) - (define-obsolete-function-alias 'ein:notebook-show-in-shared-output 'ein:shared-output-show-code-cell-at-point "0.1.2") +(autoload 'org-toggle-latex-fragment "org") +(defalias 'ein:notebook-toggle-latex-fragment 'org-toggle-latex-fragment + "Borrow from org-mode the rendering of latex overlays") + ;;; Kernel related things @@ -537,21 +524,22 @@ notebook buffer." notebook buffer then the user will be prompted to select an opened notebook." (interactive (let* ((notebook (or (ein:get-notebook) - (completing-read - "Select notebook [URL-OR-PORT/NAME]: " + (ido-completing-read + "Select notebook: " (ein:notebook-opened-buffer-names)))) - (kernel-name (completing-read + (kernel-name (ido-completing-read "Select kernel: " (ein:list-available-kernels (ein:$notebook-url-or-port notebook))))) (list notebook kernel-name))) (setf (ein:$notebook-kernelspec notebook) (ein:get-kernelspec (ein:$notebook-url-or-port notebook) kernel-name)) (ein:log 'info "Restarting notebook %s with new kernel %s." (ein:$notebook-notebook-name notebook) kernel-name) - (ein:notebook-restart-kernel notebook)) + (ein:kernel-restart-session notebook)) -;;; This no longer works in iPython-2.0. Protocol is to create a session for a -;;; notebook, which will automatically create and associate a kernel with the notebook. -(defun ein:notebook-start-kernel (notebook) +(defun ein:notebook-retrieve-session (notebook) + "Formerly ein:notebook-start-kernel. + +If 'picking up from where we last off', that is, we restart emacs and reconnect to same server, jupyter will hand us back the original, still running session." (let* ((base-url (concat ein:base-kernel-url "kernels")) (kernelspec (ein:$notebook-kernelspec notebook)) (kernel (ein:kernel-new (ein:$notebook-url-or-port notebook) @@ -561,23 +549,37 @@ notebook buffer then the user will be prompted to select an opened notebook." (setf (ein:$notebook-kernel notebook) kernel) (when (eq (ein:get-mode-for-kernel (ein:$notebook-kernelspec notebook)) 'python) (ein:pytools-setup-hooks kernel notebook)) - (ein:kernel-start kernel notebook))) + (ein:kernel-retrieve-session notebook))) -(defun ein:notebook-restart-kernel (notebook) - (ein:kernel-restart (ein:$notebook-kernel notebook))) +(defun ein:notebook-reconnect-session-command (&optional callback) + "It seems convenient but undisciplined to blithely create a new session if the original one no longer exists. CALLBACK takes notebook and session-p." + (interactive) + (unless callback + (setq callback + (lambda (notebook session-p) + (if (or session-p (y-or-n-p "Session not found. Restart?")) + (ein:kernel-retrieve-session notebook 0 + (apply-partially (lambda (nb) + (with-current-buffer (ein:notebook-buffer nb) + (ein:notification-status-set + (slot-value ein:%notification% 'kernel) + 'status_reconnected.Kernel))) + notebook)))))) + (ein:aif ein:%notebook% + (progn + (ein:kernel-disconnect (ein:$notebook-kernel it)) + (ein:events-trigger (ein:$kernel-events (ein:$notebook-kernel it)) + 'status_reconnecting.Kernel) + (ein:kernel-session-p + it (apply-partially callback it))))) -(autoload 'org-toggle-latex-fragment "org") -(defalias 'ein:notebook-toggle-latex-fragment 'org-toggle-latex-fragment - "Borrow from org-mode the rendering of latex overlays") - -(defun ein:notebook-restart-kernel-command () - "Send request to the server to restart kernel." +(defun ein:notebook-restart-session-command () + "Delete session on server side. Start new session." (interactive) (ein:aif ein:%notebook% - (when (or (not (ein:kernel-live-p (ein:$notebook-kernel it))) - (y-or-n-p "Restart kernel? ")) - (ein:notebook-restart-kernel it)) - (ein:log 'error "Not in notebook buffer!"))) + (if (y-or-n-p "Are you sure? ") + (ein:kernel-restart-session it)) + (message "Not in notebook buffer!"))) (define-obsolete-function-alias 'ein:notebook-request-tool-tip-or-help-command @@ -598,11 +600,6 @@ This is equivalent to do ``C-c`` in the console program." (interactive) (ein:kernel-interrupt (ein:$notebook-kernel ein:%notebook%))) -(defun ein:notebook-kernel-delete-command () - (interactive) - (when (y-or-n-p "Delete kernel?") - (ein:kernel-delete (ein:$notebook-kernel ein:%notebook%)))) - ;; autoexec (defun ein:notebook-execute-autoexec-cells (notebook) @@ -903,7 +900,7 @@ as usual." (let ((kernel (ein:$notebook-kernel notebook))) ;; If kernel is live, kill it before closing. (if (ein:kernel-live-p kernel) - (ein:kernel-delete kernel (apply-partially #'ein:notebook-close notebook)) + (ein:kernel-delete-session kernel (apply-partially #'ein:notebook-close notebook)) (ein:notebook-close notebook))))) (defun ein:fast-content-from-notebook (notebook) @@ -1388,8 +1385,8 @@ This hook is run regardless the actual major mode used." (define-key map (kbd "C-c C-$") 'ein:tb-show) (define-key map "\C-c\C-x" nil) (define-key map "\C-c\C-x\C-l" 'ein:notebook-toggle-latex-fragment) - (define-key map "\C-c\C-x\C-r" 'ein:notebook-restart-kernel-command) - (define-key map "\C-c\C-r" 'ein:notebook-reconnect-kernel) + (define-key map "\C-c\C-x\C-r" 'ein:notebook-restart-session-command) + (define-key map "\C-c\C-r" 'ein:notebook-reconnect-session-command) (define-key map "\C-c\C-z" 'ein:notebook-kernel-interrupt-command) (define-key map "\C-c\C-q" 'ein:notebook-kill-kernel-then-close-command) (define-key map (kbd "C-c C-#") 'ein:notebook-close) @@ -1494,8 +1491,8 @@ This hook is run regardless the actual major mode used." ein:worksheet-next-input-history)))) ("Kernel" ,@(ein:generate-menu - '(("Restart kernel" ein:notebook-restart-kernel-command) - ("Reconnect kernel" ein:notebook-reconnect-kernel) + '(("Restart session" ein:notebook-restart-session-command) + ("Reconnect session" ein:notebook-reconnect-session-command) ("Switch kernel" ein:notebook-switch-kernel) ("Interrupt kernel" ein:notebook-kernel-interrupt-command)))) ("Worksheets [Experimental]" diff --git a/lisp/ein-notebooklist.el b/lisp/ein-notebooklist.el index b69a955..5769f79 100644 --- a/lisp/ein-notebooklist.el +++ b/lisp/ein-notebooklist.el @@ -372,9 +372,10 @@ This function is called via `ein:notebook-after-rename-hook'." TODO - New and open should be separate, and we should flag an exception if we try to new an existing. " + (interactive (list (ein:notebooklist-ask-url-or-port) - (completing-read - "Select kernel [default]: " + (ido-completing-read + "Select kernel: " (ein:list-available-kernels (ein:$notebooklist-url-or-port ein:%notebooklist%)) nil t nil nil "default" nil))) (let ((path (or path (ein:$notebooklist-path (or ein:%notebooklist% (ein:notebooklist-list-get url-or-port))))) @@ -436,8 +437,8 @@ You may find the new one in the notebook list." error) "Open new notebook and rename the notebook." (interactive (let* ((url-or-port (or (ein:get-url-or-port) (ein:default-url-or-port))) - (kernelspec (completing-read - "Select kernel [default]: " + (kernelspec (ido-completing-read + "Select kernel: " (ein:list-available-kernels url-or-port) nil t nil nil "default" nil)) (name (read-from-minibuffer (format "Notebook name (at %s): " url-or-port)))) @@ -651,6 +652,7 @@ You may find the new one in the notebook list." error) (ein:format-time-string ein:notebooklist-date-format dt)))) (defun render-directory (url-or-port sessions) + ;; SESSIONS is a hashtable of path to (session-id . kernel-id) pairs (with-current-buffer (ein:notebooklist-get-buffer url-or-port) (widget-insert "\n------------------------------------------\n\n") (ein:make-sorting-widget "Sort by" ein:notebooklist-sort-field) @@ -712,12 +714,14 @@ You may find the new one in the notebook list." error) (if (gethash path sessions) (widget-create 'link - :notify (lexical-let ((session (car (gethash path sessions))) - (nblist ein:%notebooklist%)) + :notify (lexical-let ((url-or-port url-or-port) + (path path) + (session (car (gethash path sessions)))) (lambda (&rest ignore) (run-at-time 1 nil #'ein:notebooklist-reload) - (ein:kernel-delete (make-ein:$kernel :url-or-port (ein:$notebooklist-url-or-port nblist) - :session-id session)))) + ;; can only stop opened notebooks + (ein:and-let* ((nb (ein:notebook-get-opened-notebook url-or-port path))) + (ein:kernel-delete-session (ein:$notebook-kernel nb))))) "Stop") (widget-insert "------")) (widget-insert " ") diff --git a/test/ein-testing-notebook.el b/test/ein-testing-notebook.el index 11950ef..097c2a6 100644 --- a/test/ein-testing-notebook.el +++ b/test/ein-testing-notebook.el @@ -41,12 +41,9 @@ (content (make-ein:$content :url-or-port ein:testing-notebook-dummy-url :notebook-version 3 :path path))) - ;; using dynamically scoped flet instead of cl-flet, where - ;; "bindings are lexical... all references to the named functions - ;; must appear physically within the body of the cl-flet" (flet ((pop-to-buffer (buf) buf) (ein:need-notebook-version (url-or-port) 3) - (ein:notebook-start-kernel (notebook)) + (ein:notebook-new-session (notebook)) (ein:notebook-enable-autosaves (notebook))) (let ((notebook (ein:notebook-new ein:testing-notebook-dummy-url path kernelspec))) (setf (ein:$notebook-kernel notebook) diff --git a/test/test-ein-kernel.el b/test/test-ein-kernel.el index 3623c27..391a125 100644 --- a/test/test-ein-kernel.el +++ b/test/test-ein-kernel.el @@ -3,17 +3,20 @@ (require 'ein-kernel) (require 'ein-testing-kernel) +(require 'ein-testing-notebook) (defun eintest:kernel-new (port) (ein:kernel-new port "/api/kernels" (get-buffer-create "*eintest: dummy for kernel test*"))) (ert-deftest ein:kernel-restart-check-url () - (lexical-let* ((kernel (eintest:kernel-new 8888)) + (lexical-let* ((notebook (ein:notebook-new ein:testing-notebook-dummy-url "" nil)) + (kernel (eintest:kernel-new 8888)) (kernel-id "KERNEL-ID") (desired-url "http://127.0.0.1:8888/api/sessions/KERNEL-ID") (dummy-response (make-request-response)) got-url) + (setq (ein:$notebook-kernel notebook) kernel) (cl-letf (((symbol-function 'request) (lambda (url &rest ignore) (setq got-url url) dummy-response)) ((symbol-function 'set-process-query-on-exit-flag) #'ignore) @@ -21,9 +24,9 @@ ((symbol-function 'ein:websocket) (lambda (&rest ignore) (make-ein:$websocket :ws nil :kernel kernel :closed-by-client nil))) ((symbol-function 'ein:events-trigger) #'ignore) ((symbol-function 'ein:get-notebook-or-error) (lambda () (ein:get-notebook)))) - (ein:kernel-start--success + (ein:kernel-retrieve-session--success kernel nil :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id)) - (ein:kernel-restart kernel) + (ein:kernel-restart-session notebook) (should (equal got-url desired-url))))) (ert-deftest ein:kernel-interrupt-check-url () @@ -37,7 +40,7 @@ (ein:kernel-stop-channels (&rest ignore)) (ein:websocket (url kernel on-message on-close on-open) (make-ein:$websocket :ws nil :kernel kernel :closed-by-client nil)) (ein:websocket-open-p (websocket) t)) - (ein:kernel-start--success + (ein:kernel-retrieve-session--success kernel nil :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id)) (ein:kernel-interrupt kernel) (should (equal got-url desired-url))))) @@ -52,10 +55,10 @@ (set-process-query-on-exit-flag (process flag)) (ein:kernel-stop-channels (&rest ignore)) (ein:websocket (url kernel on-message on-close on-open) (make-ein:$websocket :ws nil :kernel kernel :closed-by-client nil))) - (ein:kernel-start--success + (ein:kernel-retrieve-session--success kernel nil :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id)) - (ein:kernel-delete kernel) - (should (equal got-url desired-url))))) + (ein:kernel-delete-session kernel)) + (should (equal got-url desired-url)))) ;;; Test `ein:kernel-construct-help-string' diff --git a/test/test-ein-notebook.el b/test/test-ein-notebook.el index bf8be32..836dc64 100644 --- a/test/test-ein-notebook.el +++ b/test/test-ein-notebook.el @@ -968,7 +968,7 @@ defined." ((ein:kernel-live-p (kernel) ((:input (list kernel) :output t))) - (ein:kernel-delete + (ein:kernel-delete-session (kernel &optional callback) ((:input (list kernel (apply-partially #'ein:notebook-close notebook)))))) (call-interactively #'ein:notebook-kill-kernel-then-close-command))