emacs-ipython-notebook/lisp/ein-jupyter.el
John Miller 191d27c3ac Make stopping a jupyter server more user-friendly.
When calling `ein:jupyter-server-stop` ask to user if they are sure and also
give them the option to save any unsaved notebooks before killing the server.

Before closing the server also close any open notebook buffers.
2017-02-15 15:02:07 -06:00

123 lines
5.1 KiB
EmacsLisp

;;; ein-jupyter.el --- Manage the jupyter notebook server
;; Copyright (C) 2017- John M. Miller
;; Authors: John M. Miller <millejoh at mac.com>
;; This file is NOT part of GNU Emacs.
;; ein-jupyter.el is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; ein-jupyter.el is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with ein-jupyter.el. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(defcustom ein:jupyter-server-buffer-name "*ein:jupyter-server*"
"The name of the buffer to run a jupyter notebook server
session in."
:group 'ein
:type 'string)
(defvar *ein:jupyter-server-accept-timeout* 60)
(defvar %ein:jupyter-server-session% nil)
(defvar *ein:last-jupyter-command* nil)
(defvar *ein:last-jupyter-directory* nil)
(defun ein:jupyter-server--cmd (path dir)
(list path
"notebook"
(format "--notebook-dir=%s" dir)))
(defun ein:jupyter-server-start (server-path server-directory)
"Start the jupyter notebook server at the given path.
This command opens an asynchronous process running the jupyter
notebook server and then tries to detect the url and token to
generate automatic calls to `ein:notebooklist-login' and
`ein:notebooklist-open'.
On executing the command will prompt the user for the path to the
jupyter executable and the path for the root directory containing
the notebooks the user wants to access.
The buffer named by `ein:jupyter-server-buffer-name' will contain
the log of the running jupyter server."
(interactive (list
(read-file-name "Server Command: " default-directory nil nil *ein:last-jupyter-command*)
(read-directory-name "Notebook Directory: " *ein:last-jupyter-directory*)))
(assert (and (file-exists-p server-path)
(file-executable-p server-path))
t "Command %s is not valid!" server-path)
(setf *ein:last-jupyter-command* server-path
*ein:last-jupyter-directory* server-directory)
(if (buffer-live-p (get-buffer ein:jupyter-server-buffer-name))
(message "Notebook session is already running, check the contents of %s"
ein:jupyter-server-buffer-name))
(message "Starting notebook server in directory: %s" server-directory)
(let* ((proc (make-process :name "EIN: Jupyter notebook server"
:buffer ein:jupyter-server-buffer-name
:command (ein:jupyter-server--cmd server-path server-directory))))
(setq %ein:jupyter-server-session% proc)
(if (accept-process-output proc *ein:jupyter-server-accept-timeout*)
(progn
(sit-for 1.0)
(ein:jupyter-server-login-and-open)))))
(defun ein:jupyter-server-login-and-open ()
(interactive)
(when (buffer-live-p (get-buffer ein:jupyter-server-buffer-name))
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)
(ein:notebooklist-login url-or-port token)
(sit-for 1.0)
(ein:notebooklist-open url-or-port))))
(defun ein:jupyter-server-stop ()
"Stop a running jupyter notebook server.
Use this command to stop a running jupyter notebook server. If
there is no running server then no action will be taken.
"
(interactive)
(when (and %ein:jupyter-server-session%
(y-or-n-p "Kill jupyter server and close all open 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
when (y-or-n-p (format "Save notebook %s before stopping the server?" (ein:$notebook-notebook-name nb)))
do (progn
(setf (gethash (ein:$notebook-notebook-name nb) check-for-saved) t)
(ein:notebook-save-notebook nb 0
#'(lambda (name check-hash)
(remhash name check-hash))
(list (ein:$notebook-notebook-name nb) check-for-saved)))))
(loop for x upfrom 0 by 1
until (or (= (hash-table-count check-for-saved) 0)
(> x 1000000))
do (sit-for 0.1)))
(mapc #'ein:notebook-close (ein:notebook-opened-notebooks))
(delete-process %ein:jupyter-server-session%)
(kill-buffer ein:jupyter-server-buffer-name)
(setq %ein:jupyter-server-session% nil)
(message "Stopped Jupyter notebook server.")))
(defun ein:jupyter-server-conn-info ()
(with-current-buffer ein:jupyter-server-buffer-name
(goto-char (point-min))
(re-search-forward "\\(https?://.*:[0-9]+\\)/\\?token=\\(.*\\)" nil t)
(let ((url-or-port (match-string 1))
(token (match-string 2)))
(list url-or-port token))))