Merge pull request #342 from dickmao/async-staging

Asynchronize all server communication
This commit is contained in:
John Miller 2018-10-07 21:35:47 -05:00 committed by GitHub
commit 98ce6cc83e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 744 additions and 979 deletions

View file

@ -38,4 +38,4 @@ before_script:
- cask install
script:
- make test || cat log/*.server
- make test || ( for file in log/{testfunc,ecukes}.* ; do echo $file ; cat $file ; done )

2
Cask
View file

@ -3,7 +3,7 @@
(package "ein" "0.14.2" "Emacs IPython Notebook.")
(package-file "lisp/ein.el")
(files "lisp/*.el" :exclude ("lisp/zeroein.el"))
(files "lisp/*.el" (:exclude "lisp/zeroein.el"))
(development
(depends-on "websocket")

View file

@ -4,11 +4,13 @@ IPY_VERSION = 5.8.0
SRC=$(shell cask files)
ELCFILES = $(SRC:.el=.elc)
.PHONY: loaddefs
loaddefs:
sh tools/update-autoloads.sh
.PHONY: clean
clean:
cask clean-elc
-rm -f log/testein*
-rm -f log/testfunc*
env-ipy.%:
tools/makeenv.sh env/ipy.$* tools/requirement-ipy.$*.txt

View file

@ -0,0 +1,35 @@
Scenario: No warnings
Given I switch to log expr "ein:log-all-buffer-name"
Then I should see "[info]"
And I should not see "[warn]"
And I should not see "[error]"
Scenario: Breadcrumbs
Given I am in notebooklist buffer
When I click on dir "step-definitions"
Then I should see "ein-steps"
And I click on "Home"
Then I should see "support"
Scenario: New Notebook
Given I am in notebooklist buffer
When I clear log expr "ein:log-all-buffer-name"
And I click on "New Notebook"
And I switch to log expr "ein:log-all-buffer-name"
Then I should see "Opened notebook Untitled"
Scenario: Resync
Given I am in notebooklist buffer
When I clear log expr "ein:log-all-buffer-name"
And I click on "Resync"
And I switch to log expr "ein:log-all-buffer-name"
Then I should see "kernelspecs--complete"
@foo
Scenario: Global notebooks
Given I am in notebooklist buffer
When I clear log expr "ein:log-all-buffer-name"
And I call "ein:notebooklist-open-notebook-global"
And I wait 0.9 seconds
And I switch to log expr "ein:log-all-buffer-name"
Then I should see "Opened notebook"

View file

@ -1,3 +1,28 @@
(When "^I clear log expr \"\\(.+\\)\"$"
(lambda (log-expr)
(with-current-buffer (symbol-value (intern log-expr))
(let ((inhibit-read-only t))
(erase-buffer)))))
(When "^I switch to log expr \"\\(.+\\)\"$"
(lambda (log-expr)
(switch-to-buffer (symbol-value (intern log-expr)))))
(When "^I am in notebooklist buffer$"
(lambda ()
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)
(switch-to-buffer (ein:notebooklist-get-buffer url-or-port))
(sit-for 0.8)
)))
(When "^I wait \\([.0-9]+\\) seconds$"
(lambda (seconds)
(sit-for (string-to-number seconds))))
(When "^I am in log buffer$"
(lambda ()
(switch-to-buffer ein:log-all-buffer-name)))
(When "^new \\(.+\\) notebook$"
(lambda (kernel)
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)
@ -14,6 +39,25 @@
(switch-to-buffer buf-name)
(Then "I should be in buffer \"%s\"" buf-name))))))
(When "^I click on \"\\(.+\\)\"$"
(lambda (word)
;; from espuds "go to word" without the '\\b's
(goto-char (point-min))
(let ((search (re-search-forward (format "\\[%s\\]" word) nil t))
(message "Cannot go to link '%s' in buffer: %s"))
(cl-assert search nil message word (buffer-string))
(backward-char)
(When "I press \"RET\"")
(sit-for 0.8))))
(When "^I click on dir \"\\(.+\\)\"$"
(lambda (dir)
(When (format "I go to word \"%s\"" dir))
(re-search-backward "Dir" nil t)
(When "I press \"RET\"")
(sit-for 0.8)
))
(When "^old notebook \"\\(.+\\)\"$"
(lambda (path)
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)

View file

@ -17,44 +17,13 @@
(defvar ein:testing-jupyter-server-root (f-parent (f-dirname load-file-name)))
(ein:deflocal ein:%testing-port% nil)
(defun ein:testing-wait-until (predicate &optional predargs ms)
"Wait until PREDICATE function returns non-`nil'.
PREDARGS is argument list for the PREDICATE function.
MS is milliseconds to wait."
(let* ((subms 300)
(count (max 1 (if ms (truncate (/ ms subms)) 25))))
(unless (loop repeat count
when (apply predicate predargs)
return t
do (sleep-for 0 subms))
(error "Timeout: %s" predicate))))
(Setup
(setq ein:force-sync t)
(ein:dev-start-debug)
(setq ein:notebook-autosave-frequency 10000)
(setq ein:testing-dump-file-log "./log/ecukes.log")
(setq ein:testing-dump-file-messages "./log/ecukes.messages")
(setq ein:testing-dump-server-log "./log/ecukes.server")
(setq ein:jupyter-server-args '("--no-browser" "--debug"))
(deferred:sync! (ein:jupyter-server-start (executable-find "jupyter") ein:testing-jupyter-server-root))
(assert (processp %ein:jupyter-server-session%) t "notebook server defunct")
(setq ein:%testing-url% (car (ein:jupyter-server-conn-info))
))
(After
(defun ein:testing-after-scenario ()
(with-current-buffer (ein:notebooklist-get-buffer ein:%testing-url%)
(loop for buffer in (ein:notebook-opened-buffers)
do (let ((kill-buffer-query-functions nil))
(with-current-buffer buffer (not-modified))
(kill-buffer buffer)))
(let ((sessions #s(hash-table test equal data (:pending t)))
(urlport (ein:$notebooklist-url-or-port ein:%notebooklist%)))
(ein:content-query-sessions sessions urlport)
(loop repeat 4
until (null (gethash :pending sessions))
do (sleep-for 0 50))
(let ((urlport (ein:$notebooklist-url-or-port ein:%notebooklist%)))
(loop for note in (ein:$notebooklist-data ein:%notebooklist%)
for path = (plist-get note :path)
for notebook = (ein:notebook-get-opened-notebook urlport path)
@ -62,14 +31,31 @@
do (ein:notebook-kill-kernel-then-close-command notebook t)
(if (search "Untitled" path)
(ein:notebooklist-delete-notebook path))
end))))
end)))
)
(Setup
(ein:dev-start-debug)
(setq ein:notebook-autosave-frequency 10000)
(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-request (concat default-directory "log/ecukes.request"))
(setq ein:jupyter-server-args '("--no-browser" "--debug"))
(setq ein:%testing-url% nil)
(deferred:sync! (ein:jupyter-server-start (executable-find "jupyter") ein:testing-jupyter-server-root))
(assert (processp %ein:jupyter-server-session%) t "notebook server defunct")
(setq ein:%testing-url% (car (ein:jupyter-server-conn-info))))
(After
(ein:testing-after-scenario))
(Teardown
(cl-letf (((symbol-function 'y-or-n-p) (lambda (prompt) t)))
(ein:jupyter-server-stop t))
(ein:testing-dump-logs)
; (ein:testing-dump-logs) ; taken care of by ein-testing.el kill-emacs-hook?
(assert (not (processp %ein:jupyter-server-session%)) t "notebook server orphaned"))
(Fail
(if (not noninteractive)
(keyboard-quit))) ;; useful to prevent emacs from quitting
(if noninteractive
(ein:testing-after-scenario)
(keyboard-quit))) ;; useful to prevent emacs from quitting

View file

@ -1,3 +1,4 @@
@default
Scenario: Undo by default turned off
Given new default notebook
When I type "import math"
@ -5,6 +6,7 @@ Scenario: Undo by default turned off
And I undo demoting errors
Then I should see message "demoted: (user-error No undo information in this buffer)"
@yank
Scenario: Kill yank doesn't break undo
Given I enable undo
Given new default notebook

View file

@ -258,9 +258,7 @@ a number will limit the number of lines in a cell output."
(ein:oset-if-empty cell 'metadata (plist-get data :metadata))
(ein:aif (plist-get (slot-value cell 'metadata) :slideshow)
(let ((slide-type (nth 0 (cdr it))))
(setf (slot-value cell 'slidetype) slide-type)
(message "read slidetype %s" (slot-value cell 'slidetype))
(message "reconstructed slideshow %s" (ein:get-slide-show cell)))))
(setf (slot-value cell 'slidetype) slide-type))))
cell))
(defmethod ein:cell-init ((cell ein:codecell) data)

View file

@ -180,7 +180,7 @@ notebooks."
(ein:notebooklist-list-notebooks))))
(ein:notebooklist-open-notebook-global
nbpath
(lambda (notebook -ignore- buffer no-reconnection)
(lambda (notebook created buffer no-reconnection)
(ein:connect-buffer-to-notebook notebook buffer no-reconnection))
(list (or buffer (current-buffer)) no-reconnection)))
@ -189,9 +189,10 @@ notebooks."
"Connect any buffer to opened notebook and its kernel."
(interactive (list (completing-read "Notebook buffer to connect: "
(ein:notebook-opened-buffer-names))))
(let ((notebook
(buffer-local-value 'ein:%notebook% (get-buffer buffer-or-name))))
(ein:connect-buffer-to-notebook notebook)))
(ein:aif (get-buffer-buffer-or-name)
(let ((notebook (buffer-local-value 'ein:%notebook% it)))
(ein:connect-buffer-to-notebook notebook))
(error "No buffer %s" buffer-or-name)))
;;;###autoload
(defun ein:connect-buffer-to-notebook (notebook &optional buffer

View file

@ -40,7 +40,7 @@
(provide 'ein-contents-api) ; must provide before requiring ein-notebook:
(require 'ein-notebook) ; circular: depends on this file!
(defcustom ein:content-query-timeout (* 60 1000) ; 1 min
(defcustom ein:content-query-timeout (* 60 1000) ;1 min
"Query timeout for getting content from Jupyter/IPython notebook.
If you cannot open large notebooks because of a timeout error try
increasing this value. Setting this value to `nil' means to use
@ -56,71 +56,61 @@ global setting. For global setting and more information, see
:group 'ein)
(defun ein:content-url (content &rest params)
(let ((url-or-port (ein:$content-url-or-port content))
(path (ein:$content-path content)))
(if params
(url-encode-url (apply #'ein:url
url-or-port
"api/contents"
path
params))
(url-encode-url (ein:url url-or-port "api/contents" path)))))
(apply #'ein:content-url* (ein:$content-url-or-port content) (ein:$content-path content) params))
(defun ein:content-url-legacy (content &rest params)
"Generate content url's for IPython Notebook version 2.x"
(let ((url-or-port (ein:$content-url-or-port content))
(path (ein:$content-path content)))
(if params
(url-encode-url (apply #'ein:url
url-or-port
"api/notebooks"
path
params))
(url-encode-url (ein:url url-or-port "api/notebooks" path)))))
(defun ein:content-url* (url-or-port path &rest params)
(let* ((which (if (<= (ein:need-ipython-version url-or-port) 2)
"notebooks" "contents"))
(api-path (concat "api/" which)))
(url-encode-url (apply #'ein:url
url-or-port
api-path
path
params))))
(defun ein:content-query-contents (path &optional url-or-port force-sync callback retry-p)
"Return the contents of the object at the specified path from the Jupyter server."
(condition-case err
(let* ((url-or-port (or url-or-port (ein:default-url-or-port)))
(new-content (make-ein:$content
:url-or-port url-or-port
:ipython-version (ein:query-ipython-version url-or-port)
:path path))
(url (ein:content-url new-content)))
(if (= 2 (ein:$content-ipython-version new-content))
(setq new-content (ein:content-query-contents-legacy path url-or-port ein:force-sync callback))
(ein:query-singleton-ajax
(list 'content-query-contents url-or-port path)
url
:type "GET"
:timeout ein:content-query-timeout
:parser #'ein:json-read
:sync (or force-sync ein:force-sync)
:success (apply-partially #'ein:new-content new-content callback)
:error (apply-partially #'ein:content-query-contents-error url retry-p
(list path url-or-port force-sync callback t))))
new-content)
(error (progn (message "Error %s on query contents, try calling `ein:notebooklist-login` first..." err)
(if (>= ein:log-level (ein:log-level-name-to-int 'debug))
(throw 'error err))))))
(defun ein:content-query-contents (url-or-port path callback)
"Register CALLBACK of arity 1 for the contents at PATH from the Jupyter URL-OR-PORT."
(ein:query-singleton-ajax
(list 'content-query-contents url-or-port path)
(ein:content-url* url-or-port path)
:type "GET"
:timeout ein:content-query-timeout
:parser #'ein:json-read
:sync ein:force-sync
:complete (apply-partially #'ein:content-query-contents--complete url-or-port path)
:success (apply-partially #'ein:content-query-contents--success url-or-port path callback)
:error (apply-partially #'ein:content-query-contents--error url-or-port path)
))
(defun ein:content-query-contents-legacy (path &optional url-or-port force-sync callback)
"Return contents of object at specified path for IPython Notebook versions 2.x"
(let* ((url-or-port (or url-or-port (ein:default-url-or-port)))
(new-content (make-ein:$content :url-or-port url-or-port
:ipython-version (ein:query-ipython-version url-or-port)
:path path))
(url (ein:content-url-legacy new-content)))
(ein:query-singleton-ajax
(list 'content-query-contents-legacy url-or-port path)
url
:type "GET"
:timeout ein:content-query-timeout
:parser #'ein:json-read
:sync ein:force-sync
:success (apply-partially #'ein:query-contents-legacy-success path new-content callback)
:error (apply-partially #'ein:content-query-contents-error url))
new-content))
(defun* ein:content-query-contents--complete (url-or-port path
&key data symbol-status response
&allow-other-keys
&aux (resp-string (format "STATUS: %s DATA: %s" (request-response-status-code response) data)))
(ein:log 'debug "ein:query-contents--complete %s" resp-string))
(defun* ein:content-query-contents--error (url-or-port path &key symbol-status response error-thrown &allow-other-keys)
(ein:log 'error "ein:content-query-contents--error %s REQUEST-STATUS %s DATA %s" (concat (file-name-as-directory url-or-port) path) symbol-status (cdr error-thrown)))
;; TODO: This is one place to check for redirects - update the url slot if so.
;; Will need to pass the response object and check either request-response-history
;; or request-response-url.
(defun* ein:content-query-contents--success (url-or-port path callback
&key data symbol-status response
&allow-other-keys)
(let (content)
(if (<= (ein:need-ipython-version url-or-port) 2)
(setq content (ein:new-content-legacy url-or-port path data))
(setq content (ein:new-content url-or-port path data)))
(ein:aif response
(setf (ein:$content-url-or-port content) (ein:get-response-redirect it)))
;; (if (length (request-response-history response))
;; (let ((url (url-generic-parse-url (format "%s" (request-response-url response)))))
;; (setf (ein:$content-url-or-port content) (format "%s://%s:%s"
;; (url-type url)
;; (url-host url)
;; (url-port url)))))
(when callback (funcall callback content))))
(defun ein:fix-legacy-content-data (data)
(if (listp (car data))
@ -131,49 +121,6 @@ global setting. For global setting and more information, see
(plist-put data :path (plist-get data :name))
(plist-put data :path (format "%s/%s" (plist-get data :path) (plist-get data :name))))))
(defun* ein:query-contents-legacy-success (path content callback &key data &allow-other-keys)
(if (not (plist-get data :type))
;; Content API in 2.x a bit inconsistent.
(progn
(setf (ein:$content-name content) (substring path (or (cl-position ?/ path) 0))
(ein:$content-path content) path
(ein:$content-type content) "directory"
;;(ein:$content-created content) (plist-get data :created)
;;(ein:$content-last-modified content) (plist-get data :last_modified)
(ein:$content-format content) nil
(ein:$content-writable content) nil
(ein:$content-mimetype content) nil
(ein:$content-raw-content content) (ein:fix-legacy-content-data data))
(when callback
(funcall callback content))
content)
(ein:new-content content callback :data data)))
;; TODO: This is one place to check for redirects - update the url slot if so.
;; Will need to pass the response object and check either request-response-history
;; or request-response-url.
(defun* ein:new-content (content callback &key data response &allow-other-keys)
(setf (ein:$content-name content) (plist-get data :name)
(ein:$content-path content) (plist-get data :path)
(ein:$content-type content) (plist-get data :type)
(ein:$content-created content) (plist-get data :created)
(ein:$content-last-modified content) (plist-get data :last_modified)
(ein:$content-format content) (plist-get data :format)
(ein:$content-writable content) (plist-get data :writable)
(ein:$content-mimetype content) (plist-get data :mimetype)
(ein:$content-raw-content content) (plist-get data :content))
(ein:aif response
(setf (ein:$content-url-or-port content) (ein:get-response-redirect it)))
;; (if (length (request-response-history response))
;; (let ((url (url-generic-parse-url (format "%s" (request-response-url response)))))
;; (setf (ein:$content-url-or-port content) (format "%s://%s:%s"
;; (url-type url)
;; (url-host url)
;; (url-port url)))))
(when callback
(funcall callback content))
content)
(defun ein:content-to-json (content)
(let ((path (if (>= (ein:$content-ipython-version content) 3)
(ein:$content-path content)
@ -196,63 +143,111 @@ global setting. For global setting and more information, see
:ipython-version (ein:$notebook-api-version nb)
:raw-content nb-content)))
(defun* ein:content-query-contents-error (url retry-p packed &key symbol-status response &allow-other-keys)
(ein:gc-complete-operation)
(if (and (eql symbol-status 'parse-error)
(not retry-p))
(progn
(message "Content list call failed, maybe because curl hasn't updated it's cookie jar yet? Let's try one more time....")
(apply #'ein:content-query-contents packed))
(progn
(ein:log 'verbose
"Error thrown: %S" (request-response-error-thrown response))
(ein:log 'error
"Content list call %s failed with status %s." url symbol-status))))
;;; Managing/listing the content hierarchy
(defvar *ein:content-hierarchy* (make-hash-table :test #'equal))
(defvar *ein:content-hierarchy* (make-hash-table :test #'equal)
"Content tree keyed by URL-OR-PORT.")
(defun ein:get-content-hierarchy (url-or-port)
(or (gethash url-or-port *ein:content-hierarchy*)
(ein:refresh-content-hierarchy url-or-port)))
(defun ein:content-need-hierarchy (url-or-port)
"Callers assume ein:content-query-hierarchy succeeded. If not, nil."
(ein:aif (gethash url-or-port *ein:content-hierarchy*) it
(ein:log 'warn "No recorded content hierarchy for %s" url-or-port)
nil))
(defun ein:make-content-hierarchy (path url-or-port)
(let* ((node (ein:content-query-contents path url-or-port t))
(active-sessions (make-hash-table :test 'equal))
(items (ein:$content-raw-content node)))
(ein:content-query-sessions active-sessions url-or-port t)
(ein:flatten (loop for item in items
for c = (make-ein:$content :url-or-port url-or-port)
do (ein:new-content c nil :data item)
(defun ein:new-content-legacy (url-or-port path data)
"Content API in 2.x a bit inconsistent."
(if (plist-get data :type)
(ein:new-content url-or-port path data)
(let ((content (make-ein:$content
:url-or-port url-or-port
:ipython-version (ein:need-ipython-version url-or-port)
:path path)))
(setf (ein:$content-name content) (substring path (or (cl-position ?/ path) 0))
(ein:$content-path content) path
(ein:$content-type content) "directory"
;;(ein:$content-created content) (plist-get data :created)
;;(ein:$content-last-modified content) (plist-get data :last_modified)
(ein:$content-format content) nil
(ein:$content-writable content) nil
(ein:$content-mimetype content) nil
(ein:$content-raw-content content) (ein:fix-legacy-content-data data))
content)))
(defun ein:new-content (url-or-port path data)
;; data is like (:size 72 :content nil :writable t :path Untitled7.ipynb :name Untitled7.ipynb :type notebook)
(let ((content (make-ein:$content
:url-or-port url-or-port
:ipython-version (ein:need-ipython-version url-or-port)
:path path)))
(setf (ein:$content-name content) (plist-get data :name)
(ein:$content-path content) (plist-get data :path)
(ein:$content-type content) (plist-get data :type)
(ein:$content-created content) (plist-get data :created)
(ein:$content-last-modified content) (plist-get data :last_modified)
(ein:$content-format content) (plist-get data :format)
(ein:$content-writable content) (plist-get data :writable)
(ein:$content-mimetype content) (plist-get data :mimetype)
(ein:$content-raw-content content) (plist-get data :content))
content))
(defun ein:content-query-hierarchy* (url-or-port path callback sessions content)
"Returns list (tree) of content objects"
(lexical-let* ((url-or-port url-or-port)
(path path)
(callback callback)
(items (ein:$content-raw-content content))
(directories (loop for item in items
if (string= "directory" (plist-get item :type))
collect (ein:new-content url-or-port path item)
end))
(others (loop for item in items
with c0
if (not (string= "directory" (plist-get item :type)))
do (setf c0 (ein:new-content url-or-port path item))
(setf (ein:$content-session-p c0)
(gethash (ein:$content-path c0) sessions))
and collect c0
end)))
(deferred:$
(apply #'deferred:parallel
(loop for c0 in directories
collect
(cond ((string= (ein:$content-type c) "directory")
(cons c
(ein:make-content-hierarchy (ein:$content-path c) url-or-port)))
(t (progn
(setf (ein:$content-session-p c)
(gethash (ein:$content-path c) active-sessions))
c)))))))
(lexical-let ((c0 c0) (d0 (deferred:new #'identity)))
(ein:content-query-contents
url-or-port
(ein:$content-path c0)
(apply-partially #'ein:content-query-hierarchy* url-or-port (ein:$content-path c0) (lambda (tree) (deferred:callback-post d0 (cons c0 tree))) sessions))
d0)))
(deferred:nextc it
(lambda (tree)
(let ((result (append others tree)))
(if (string= path "")
(setf (gethash url-or-port *ein:content-hierarchy*) (-flatten result)))
(funcall callback result)))))))
(defun ein:refresh-content-hierarchy (&optional url-or-port)
(let ((url-or-port (or url-or-port (ein:default-url-or-port))))
(setf (gethash url-or-port *ein:content-hierarchy*)
(ein:make-content-hierarchy "" url-or-port))))
(defun ein:content-query-hierarchy (url-or-port callback)
"Send for content hierarchy of URL-OR-PORT with CALLBACK arity 1 for content hierarchy"
(lexical-let ((url-or-port url-or-port)
(callback callback))
(ein:content-query-sessions
url-or-port
(lambda (sessions)
(ein:content-query-contents url-or-port "" (apply-partially #'ein:content-query-hierarchy* url-or-port "" callback sessions))))))
;;; Save Content
(defun ein:content-save-legacy (content &optional callback cbargs errcb errcbargs)
(ein:query-singleton-ajax
(list 'content-save (ein:$content-url-or-port content) (ein:$content-path content))
(ein:content-url-legacy content)
:type "PUT"
:headers '(("Content-Type" . "application/json"))
:timeout ein:content-query-timeout
:data (ein:content-to-json content)
:success (apply-partially #'ein:content-save-success callback cbargs)
:error (apply-partially #'ein:content-save-error (ein:content-url-legacy content) errcb errcbargs)))
(list 'content-save (ein:$content-url-or-port content) (ein:$content-path content))
(ein:content-url content)
:type "PUT"
:headers '(("Content-Type" . "application/json"))
:timeout ein:content-query-timeout
:data (ein:content-to-json content)
:success (apply-partially #'ein:content-save-success callback cbargs)
:error (apply-partially #'ein:content-save-error (ein:content-url content) errcb errcbargs)))
(defun ein:content-save (content &optional callback cbargs errcb errcbargs)
(if (>= (ein:$content-ipython-version content) 3)
@ -283,12 +278,13 @@ global setting. For global setting and more information, see
;;; Rename Content
(defun ein:content-legacy-rename (content new-path callback cbargs)
(let ((path (substring new-path 0 (or (position ?/ new-path :from-end t) 0)))
(name (substring new-path (or (position ?/ new-path :from-end t) 0))))
(ein:query-singleton-ajax
(list 'content-rename (ein:$content-url-or-port content) (ein:$content-path content))
(ein:content-url-legacy content)
(ein:content-url content)
:type "PATCH"
:data (json-encode `((name . ,name)
(path . ,path)))
@ -333,38 +329,44 @@ global setting. For global setting and more information, see
;;; Sessions
(defun ein:content-query-sessions (session-hash url-or-port &optional force-sync)
(unless force-sync
(setq force-sync ein:force-sync))
(ein:query-singleton-ajax
(list 'content-query-sessions)
(ein:url url-or-port "api/sessions")
:type "GET"
:parser #'ein:json-read
:success (apply-partially #'ein:content-query-sessions-success session-hash url-or-port)
:error (apply-partially #'ein:content-query-sessions-error session-hash)
:sync force-sync))
(defun* ein:content-query-sessions-success (session-hash url-or-port &key data &allow-other-keys)
(defun ein:content-query-sessions (url-or-port callback)
"Register CALLBACK of arity 1 to retrieve the sessions"
(ein:query-singleton-ajax
(list 'content-query-sessions url-or-port)
(ein:url url-or-port "api/sessions")
:type "GET"
:parser #'ein:json-read
:complete (apply-partially #'ein:content-query-sessions--complete url-or-port callback)
:success (apply-partially #'ein:content-query-sessions--success url-or-port callback)
:error (apply-partially #'ein:content-query-sessions--error url-or-port)
:sync ein:force-sync))
(defun* ein:content-query-sessions--success (url-or-port callback &key data &allow-other-keys)
(cl-flet ((read-name (nb-json)
(if (= (ein:query-ipython-version url-or-port) 2)
(if (= (ein:need-ipython-version url-or-port) 2)
(if (string= (plist-get nb-json :path) "")
(plist-get nb-json :name)
(format "%s/%s" (plist-get nb-json :path) (plist-get nb-json :name)))
(plist-get nb-json :path))))
(clrhash session-hash)
(dolist (s data)
(setf (gethash (read-name (plist-get s :notebook)) session-hash)
(cons (plist-get s :id) (plist-get s :kernel))))
session-hash))
(let ((session-hash (make-hash-table :test 'equal)))
(dolist (s data (funcall callback session-hash))
(setf (gethash (read-name (plist-get s :notebook)) session-hash)
(cons (plist-get s :id) (plist-get s :kernel)))))))
(defun* ein:content-query-sessions-error (session-hash &key symbol-status response &allow-other-keys)
(clrhash session-hash)
(ein:log 'error "Session query failed with status %s (%s)." symbol-status response))
(defun* ein:content-query-sessions--error (url-or-port &key error-thrown &allow-other-keys)
(ein:log 'error "ein:content-query-sessions--error %s: ERROR %s DATA %s" url-or-port (car error-thrown) (cdr error-thrown)))
(defun* ein:content-query-sessions--complete (url-or-port 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:query-sessions--complete %s" resp-string))
;;; Checkpoints
(defun ein:content-query-checkpoints (content &optional callback cbargs)
(let* ((url (ein:content-url content "checkpoints")))
(ein:query-singleton-ajax
@ -428,6 +430,7 @@ global setting. For global setting and more information, see
;;; Uploads
(defun ein:get-local-file (path)
"If path exists, get contents and try to guess type of file (one of file, notebook, or directory)
and content format (one of json, text, or base64)."

View file

@ -127,50 +127,105 @@ the source is in git repository."
(concat ein:version "." it)
ein:version))
(defvar *running-ipython-version* (make-hash-table :test #'equal))
;;; Server attribute getters. Not sure if these should be here.
(defun ein:get-ipython-major-version (vstr)
(defvar *ein:ipython-version* (make-hash-table :test #'equal)
"url-or-port to major ipython version")
(defvar *ein:kernelspecs* (make-hash-table :test #'equal)
"url-or-port to kernelspecs")
(defun ein:need-kernelspecs (url-or-port)
"Callers assume ein:query-kernelspecs succeeded. If not, nil."
(ein:aif (gethash url-or-port *ein:kernelspecs*) it
(ein:log 'warn "No recorded kernelspecs for %s" url-or-port)
nil))
(defun ein:query-kernelspecs (url-or-port callback)
"Send for kernelspecs of URL-OR-PORT with CALLBACK arity 0 (just a semaphore)"
(ein:query-singleton-ajax
(list 'ein:query-kernelspecs url-or-port)
(ein:url url-or-port "api/kernelspecs")
:type "GET"
: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)
:error (apply-partially #'ein:query-kernelspecs--error url-or-port)))
(defun* ein:query-kernelspecs--success (url-or-port
&key data symbol-status response
&allow-other-keys)
(let ((ks (list :default (plist-get data :default)))
(specs (ein:plist-iter (plist-get data :kernelspecs))))
(setf (gethash url-or-port *ein:kernelspecs*)
(ein:flatten (dolist (spec specs ks)
(let ((name (car spec))
(info (cdr spec)))
(push (list name (make-ein:$kernelspec :name (plist-get info :name)
:display-name (plist-get (plist-get info :spec)
:display_name)
:resources (plist-get info :resources)
:language (plist-get (plist-get info :spec)
:language)
:spec (plist-get info :spec)))
ks)))))))
(defun* ein:query-kernelspecs--error (url-or-port &key error-thrown &allow-other-keys)
(ein:log 'error
"ein:query-kernelspecs-error %s: ERROR %s DATA %s" url-or-port (car error-thrown) (cdr error-thrown)))
(defun* ein:query-kernelspecs--complete (url-or-port 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:query-kernelspecs--complete %s" resp-string)
(when callback (funcall callback)))
(defsubst ein:get-ipython-major-version (vstr)
(if vstr
(string-to-number (car (split-string vstr "\\.")))
(if (>= ein:log-level (ein:log-level-name-to-int 'debug))
(throw 'error "Null value passed to ein:get-ipython-major-version.")
(ein:log 'warn "Null value passed to ein:get-ipython-major-version."))))
(defun ein:need-ipython-version (url-or-port)
"Callers assume ein:query-ipython-version succeeded. If not, we hardcode a guess."
(ein:aif (gethash url-or-port *ein:ipython-version*) it
(ein:log 'warn "No recorded ipython version for %s" url-or-port)
5))
;; TODO: Use symbols instead of numbers for ipython version ('jupyter and 'legacy)?
(defun ein:query-ipython-version (&optional url-or-port force)
(ein:aif (and (not force) (gethash (or url-or-port (ein:default-url-or-port)) *running-ipython-version*))
it
(let ((resp (request
(ein:jupyterhub-correct-query-url-maybe (ein:url (or url-or-port
(ein:default-url-or-port))
"api"))
:parser #'(lambda ()
(ignore-errors
(ein:json-read)))
:timeout 5.0
:sync t)))
(if (eql 408 (request-response-status-code resp))
(progn
(ein:log 'blather "Version request timed out, could be the server is still warming up. Assuming we are working Jupyter 4.x, and will recheck later.")
4)
(if (eql 404 (request-response-status-code resp))
(progn
(ein:log 'blather "Version api not implemented, assuming we are working with IPython 2.x")
(setf (gethash url-or-port *running-ipython-version*) 2))
(condition-case nil
(if (plist-get (request-response-data resp) :version)
(setf (gethash url-or-port *running-ipython-version*)
(ein:get-ipython-major-version (plist-get (request-response-data resp) :version)))
(progn
(sit-for 0.1)
(ein:query-ipython-version url-or-port t)))
(error (ein:force-ipython-version-check))))))))
(defun ein:query-ipython-version (url-or-port callback)
"Send for ipython version of URL-OR-PORT with CALLBACK arity 0 (just a semaphore)"
(ein:query-singleton-ajax
(list 'query-ipython-version url-or-port)
(ein:jupyterhub-correct-query-url-maybe
(ein:url url-or-port "api"))
:parser #'ein:json-read
:sync ein:force-sync
:complete (apply-partially #'ein:query-ipython-version--complete url-or-port callback)))
(defun* ein:query-ipython-version--complete (url-or-port 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:query-ipython-version--complete %s" resp-string)
(ein:aif (plist-get data :version)
(setf (gethash url-or-port *ein:ipython-version*)
(ein:get-ipython-major-version it))
(case (request-response-status-code response)
(404 (ein:log 'warn "ipython version api not implemented")
(setf (gethash url-or-port *ein:ipython-version*) 2))
(t (ein:log 'warn "ipython version currently unknowable"))))
(when callback (funcall callback)))
(defun ein:force-ipython-version-check ()
(interactive)
(maphash #'(lambda (url-or-port --ignore--)
(ein:query-ipython-version url-or-port t))
*running-ipython-version*))
(ein:query-ipython-version url-or-port nil))
*ein:ipython-version*))
;;; File name translation (tramp support)

View file

@ -119,6 +119,11 @@ When the prefix argument is given, debugging support for websocket
callback (`websocket-callback-debug-on-error') is enabled."
(interactive "P")
(setq debug-on-error t)
;; only use these with deferred:sync! they cause strange failures otherwise!
;; (setq deferred:debug-on-signal t)
;; (setq deferred:debug t)
(setq request-log-level (quote debug))
(setq request-message-level (quote verbose))
(setq websocket-debug t)
(when ws-callback
(setq websocket-callback-debug-on-error t))
@ -130,10 +135,14 @@ callback (`websocket-callback-debug-on-error') is enabled."
;;;###autoload
(defun ein:dev-stop-debug ()
"Disable debugging support enabled by `ein:dev-start-debug'."
"Inverse of `ein:dev-start-debug'. Hard to maintain because it needs to match start"
(interactive)
(setq debug-on-error nil)
(setq websocket-debug nil)
(setq deferred:debug-on-signal nil)
(setq deferred:debug nil)
(setq request-log-level -1)
(setq request-message-level 'warn)
(setq websocket-callback-debug-on-error nil)
(setq ein:debug nil)
(ein:log-set-level 'verbose)

View file

@ -35,8 +35,7 @@
path))
(defun ein:file-open (url-or-port path)
(ein:content-query-contents path url-or-port nil
#'ein:file-open-finish))
(ein:content-query-contents url-or-port path #'ein:file-open-finish))
(defun ein:file-open-finish (content)
(with-current-buffer (get-buffer-create (ein:file-buffer-name (ein:$content-url-or-port content)

View file

@ -174,11 +174,11 @@ the log of the running jupyter server."
(setf *ein:last-jupyter-command* server-cmd-path
*ein:last-jupyter-directory* notebook-directory)
(if (buffer-live-p (get-buffer ein:jupyter-server-buffer-name))
(message "Notebook session is already running, check the contents of %s"
(ein:log 'info "Notebook session is already running, check the contents of %s"
ein:jupyter-server-buffer-name))
(add-hook 'kill-emacs-hook #'(lambda ()
(ein:jupyter-server-stop t)))
(message "Starting notebook server in directory: %s" notebook-directory)
(ein:log 'info "Starting notebook server in directory: %s" notebook-directory)
(lexical-let ((no-login-after-start-p no-login-after-start-p)
(no-popup no-popup)
(proc (ein:jupyter-server--run ein:jupyter-server-buffer-name
@ -204,7 +204,6 @@ the log of the running jupyter server."
(progn
(warn "[EIN] Jupyter server failed to start, cancelling operation.")
(ein:jupyter-server-stop t))
(ein:force-ipython-version-check)
(unless no-login-p
(ein:jupyter-server-login-and-open no-popup))))))))
@ -245,8 +244,8 @@ there is no running server then no action will be taken.
(let ((process (get-buffer-process (current-buffer))))
(when process
(let ((pid (process-id process)))
(ein:log 'info "Signaled %s with pid %s" process pid)
(message "Stopped Jupyter notebook server.")
(ein:log 'verbose "Signaled %s with pid %s" process pid)
(ein:log 'info "Stopped Jupyter notebook server.")
(signal-process (process-id process) 15)))))
(when log
(with-current-buffer ein:jupyter-server-buffer-name

View file

@ -3,62 +3,19 @@
;;; Code:
;;;### (autoloads nil "ein-ac" "ein-ac.el" (0 0 0 0))
;;; Generated autoloads from ein-ac.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-ac" '("ein:")))
;;;***
;;;### (autoloads nil "ein-cell" "ein-cell.el" (0 0 0 0))
;;; Generated autoloads from ein-cell.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-cell" '("ein:")))
;;;***
;;;### (autoloads nil "ein-cell-edit" "ein-cell-edit.el" (0 0 0 0))
;;; Generated autoloads from ein-cell-edit.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-cell-edit" '("ein:")))
;;;***
;;;### (autoloads nil "ein-cell-output" "ein-cell-output.el" (0 0
;;;;;; 0 0))
;;; Generated autoloads from ein-cell-output.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-cell-output" '("ein:")))
;;;***
;;;### (autoloads nil "ein-classes" "ein-classes.el" (0 0 0 0))
;;; Generated autoloads from ein-classes.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-classes" '("ein:")))
;;;***
;;;### (autoloads nil "ein-company" "ein-company.el" (0 0 0 0))
;;;### (autoloads nil "ein-company" "ein-company.el" (23475 34241
;;;;;; 887134 78000))
;;; Generated autoloads from ein-company.el
(autoload 'ein:company-backend "ein-company" "\
\(fn COMMAND &optional ARG &rest IGNORE)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-company" '("ein:company-handle-doc-buffer")))
\(fn COMMAND &optional ARG &rest _)" t nil)
;;;***
;;;### (autoloads nil "ein-completer" "ein-completer.el" (0 0 0 0))
;;; Generated autoloads from ein-completer.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-completer" '("ein:complete")))
;;;***
;;;### (autoloads nil "ein-connect" "ein-connect.el" (0 0 0 0))
;;;### (autoloads nil "ein-connect" "ein-connect.el" (23468 61365
;;;;;; 135112 242000))
;;; Generated autoloads from ein-connect.el
(autoload 'ein:connect-to-notebook-command "ein-connect" "\
@ -92,11 +49,10 @@ notebook.
\(fn)" nil nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-connect" '("ein:")))
;;;***
;;;### (autoloads nil "ein-console" "ein-console.el" (0 0 0 0))
;;;### (autoloads nil "ein-console" "ein-console.el" (23468 61365
;;;;;; 135112 242000))
;;; Generated autoloads from ein-console.el
(autoload 'ein:console-open "ein-console" "\
@ -113,26 +69,10 @@ It should be possible to support python-mode.el. Patches are welcome!
\(fn)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-console" '("ein:console-")))
;;;***
;;;### (autoloads nil "ein-contents-api" "ein-contents-api.el" (0
;;;;;; 0 0 0))
;;; Generated autoloads from ein-contents-api.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-contents-api" '("ein:" "update-content-path" "*ein:content-hierarchy*")))
;;;***
;;;### (autoloads nil "ein-core" "ein-core.el" (0 0 0 0))
;;; Generated autoloads from ein-core.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-core" '("*running-ipython-version*" "ein:")))
;;;***
;;;### (autoloads nil "ein-dev" "ein-dev.el" (0 0 0 0))
;;;### (autoloads nil "ein-dev" "ein-dev.el" (23477 31845 251244
;;;;;; 240000))
;;; Generated autoloads from ein-dev.el
(autoload 'ein:dev-insert-mode-map "ein-dev" "\
@ -148,7 +88,7 @@ callback (`websocket-callback-debug-on-error') is enabled.
\(fn &optional WS-CALLBACK)" t nil)
(autoload 'ein:dev-stop-debug "ein-dev" "\
Disable debugging support enabled by `ein:dev-start-debug'.
Inverse of `ein:dev-start-debug'. Hard to maintain. Not really used.
\(fn)" t nil)
@ -157,25 +97,10 @@ Open a buffer with bug report template.
\(fn)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-dev" '("ein:")))
;;;***
;;;### (autoloads nil "ein-events" "ein-events.el" (0 0 0 0))
;;; Generated autoloads from ein-events.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-events" '("ein:events-")))
;;;***
;;;### (autoloads nil "ein-file" "ein-file.el" (0 0 0 0))
;;; Generated autoloads from ein-file.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-file" '("ein:" "*ein:file-buffername-template*")))
;;;***
;;;### (autoloads nil "ein-helm" "ein-helm.el" (0 0 0 0))
;;;### (autoloads nil "ein-helm" "ein-helm.el" (23064 59027 190301
;;;;;; 971000))
;;; Generated autoloads from ein-helm.el
(autoload 'anything-ein-kernel-history "ein-helm" "\
@ -198,18 +123,10 @@ Choose opened notebook using helm interface.
\(fn)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-helm" '("ein:helm-")))
;;;***
;;;### (autoloads nil "ein-hy" "ein-hy.el" (0 0 0 0))
;;; Generated autoloads from ein-hy.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-hy" '("ein:cell-")))
;;;***
;;;### (autoloads nil "ein-iexec" "ein-iexec.el" (0 0 0 0))
;;;### (autoloads nil "ein-iexec" "ein-iexec.el" (23064 59027 190301
;;;;;; 971000))
;;; Generated autoloads from ein-iexec.el
(autoload 'ein:iexec-mode "ein-iexec" "\
@ -219,11 +136,10 @@ change in its input area.
\(fn &optional ARG)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-iexec" '("ein:iexec-")))
;;;***
;;;### (autoloads nil "ein-inspector" "ein-inspector.el" (0 0 0 0))
;;;### (autoloads nil "ein-inspector" "ein-inspector.el" (23475 34241
;;;;;; 887134 78000))
;;; Generated autoloads from ein-inspector.el
(autoload 'ein:inspect-object "ein-inspector" "\
@ -231,19 +147,10 @@ change in its input area.
\(fn KERNEL OBJECT)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-inspector" '("ein:")))
;;;***
;;;### (autoloads nil "ein-ipdb" "ein-ipdb.el" (0 0 0 0))
;;; Generated autoloads from ein-ipdb.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-ipdb" '("ein:" "*ein:ipdb-")))
;;;***
;;;### (autoloads nil "ein-ipynb-mode" "ein-ipynb-mode.el" (0 0 0
;;;;;; 0))
;;;### (autoloads nil "ein-ipynb-mode" "ein-ipynb-mode.el" (23064
;;;;;; 59027 194301 980000))
;;; Generated autoloads from ein-ipynb-mode.el
(autoload 'ein:ipynb-mode "ein-ipynb-mode" "\
@ -253,11 +160,10 @@ A simple mode for ipynb file.
(add-to-list 'auto-mode-alist '(".*\\.ipynb\\'" . ein:ipynb-mode))
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-ipynb-mode" '("ein:ipynb-parent-mode")))
;;;***
;;;### (autoloads nil "ein-jedi" "ein-jedi.el" (0 0 0 0))
;;;### (autoloads nil "ein-jedi" "ein-jedi.el" (23468 61365 139112
;;;;;; 317000))
;;; Generated autoloads from ein-jedi.el
(autoload 'ein:jedi-complete "ein-jedi" "\
@ -282,33 +188,10 @@ To use EIN and Jedi together, add the following in your Emacs setup before loadi
\(fn)" nil nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-jedi" '("ein:jedi-")))
;;;***
;;;### (autoloads nil "ein-junk" "ein-junk.el" (0 0 0 0))
;;; Generated autoloads from ein-junk.el
(autoload 'ein:junk-new "ein-junk" "\
Open a notebook to try random thing.
Notebook name is determined based on
`ein:junk-notebook-name-template'.
When prefix argument is given, it asks URL or port to use.
\(fn NAME KERNELSPEC URL-OR-PORT)" t nil)
(autoload 'ein:junk-rename "ein-junk" "\
Rename the current notebook based on `ein:junk-notebook-name-template'
and save it immediately.
\(fn NAME)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-junk" '("ein:junk-notebook-name")))
;;;***
;;;### (autoloads nil "ein-jupyter" "ein-jupyter.el" (0 0 0 0))
;;;### (autoloads nil "ein-jupyter" "ein-jupyter.el" (23478 35725
;;;;;; 832413 900000))
;;; Generated autoloads from ein-jupyter.el
(autoload 'ein:jupyter-server-login-and-open "ein-jupyter" "\
@ -349,14 +232,12 @@ 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.
\(fn &optional FORCE)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-jupyter" '("%ein:jupyter-server-session%" "*ein:" "ein:jupyter-")))
\(fn &optional FORCE LOG)" t nil)
;;;***
;;;### (autoloads nil "ein-jupyterhub" "ein-jupyterhub.el" (0 0 0
;;;;;; 0))
;;;### (autoloads nil "ein-jupyterhub" "ein-jupyterhub.el" (23468
;;;;;; 61365 139112 317000))
;;; Generated autoloads from ein-jupyterhub.el
(autoload 'ein:jupyterhub-connect "ein-jupyterhub" "\
@ -364,44 +245,20 @@ Log on to a jupyterhub server using PAM authentication. Requires jupyterhub vers
\(fn URL USER PASSWORD)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-jupyterhub" '("ein:j")))
;;;***
;;;### (autoloads nil "ein-kernel" "ein-kernel.el" (0 0 0 0))
;;;### (autoloads nil "ein-kernel" "ein-kernel.el" (23475 34241 891134
;;;;;; 103000))
;;; Generated autoloads from ein-kernel.el
(defalias 'ein:kernel-url-or-port 'ein:$kernel-url-or-port)
(defalias 'ein:kernel-id 'ein:$kernel-kernel-id)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-kernel" '("ein:" "kernel-restart-try-count" "max-kernel-restart-try-count")))
;;;***
;;;### (autoloads nil "ein-kernelinfo" "ein-kernelinfo.el" (0 0 0
;;;;;; 0))
;;; Generated autoloads from ein-kernelinfo.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-kernelinfo" '("ein:kernelinfo")))
;;;***
;;;### (autoloads nil "ein-kill-ring" "ein-kill-ring.el" (0 0 0 0))
;;; Generated autoloads from ein-kill-ring.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-kill-ring" '("ein:")))
;;;***
;;;### (autoloads nil "ein-log" "ein-log.el" (0 0 0 0))
;;; Generated autoloads from ein-log.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-log" '("ein:")))
;;;***
;;;### (autoloads nil "ein-multilang" "ein-multilang.el" (0 0 0 0))
;;;### (autoloads nil "ein-multilang" "ein-multilang.el" (23468 61365
;;;;;; 139112 317000))
;;; Generated autoloads from ein-multilang.el
(autoload 'ein:notebook-multilang-mode "ein-multilang" "\
@ -409,47 +266,39 @@ Notebook mode with multiple language fontification.
\(fn)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-multilang" '("ein:" "python-imenu-format-parent-item-jump-label")))
;;;***
;;;### (autoloads nil "ein-multilang-fontify" "ein-multilang-fontify.el"
;;;;;; (0 0 0 0))
;;; Generated autoloads from ein-multilang-fontify.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-multilang-fontify" '("ein:mlf-")))
;;;***
;;;### (autoloads nil "ein-node" "ein-node.el" (0 0 0 0))
;;; Generated autoloads from ein-node.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-node" '("ein:")))
;;;***
;;;### (autoloads nil "ein-notebook" "ein-notebook.el" (0 0 0 0))
;;;### (autoloads nil "ein-notebook" "ein-notebook.el" (23478 25882
;;;;;; 960574 643000))
;;; Generated autoloads from ein-notebook.el
(autoload 'ein:junk-new "ein-notebook" "\
Open a notebook to try random thing.
Notebook name is determined based on
`ein:junk-notebook-name-template'.
When prefix argument is given, it asks URL or port to use.
\(fn NAME KERNELSPEC URL-OR-PORT)" t nil)
(autoload 'ein:junk-rename "ein-notebook" "\
Rename the current notebook based on `ein:junk-notebook-name-template'
and save it immediately.
\(fn NAME)" t nil)
(defalias 'ein:notebook-name 'ein:$notebook-notebook-name)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-notebook" '("ein:")))
;;;***
;;;### (autoloads nil "ein-notebooklist" "ein-notebooklist.el" (0
;;;;;; 0 0 0))
;;;### (autoloads nil "ein-notebooklist" "ein-notebooklist.el" (23478
;;;;;; 30232 414267 512000))
;;; Generated autoloads from ein-notebooklist.el
(autoload 'ein:notebooklist-open "ein-notebooklist" "\
Open notebook list buffer.
\(fn &optional URL-OR-PORT PATH NO-POPUP)" t nil)
(autoload 'ein:notebooklist-refresh-kernelspecs "ein-notebooklist" "\
\(fn &optional URL-OR-PORT)" t nil)
\(fn URL-OR-PORT &optional PATH NO-POPUP RESYNC)" t nil)
(autoload 'ein:notebooklist-enable-keepalive "ein-notebooklist" "\
Enable periodic calls to the notebook server to keep long running sessions from expiring.
@ -470,7 +319,7 @@ Disable the notebooklist keepalive calls to the jupyter notebook server.
(autoload 'ein:notebooklist-reload "ein-notebooklist" "\
Reload current Notebook list.
\(fn &optional NOTEBOOKLIST)" t nil)
\(fn NOTEBOOKLIST &optional RESYNC)" t nil)
(autoload 'ein:notebooklist-upload-file "ein-notebooklist" "\
@ -537,19 +386,10 @@ on all the notebooks opened from the current notebooklist.
\(fn NEW-URL-OR-PORT)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-notebooklist" '("ein:" "generate-breadcrumbs" "render-")))
;;;***
;;;### (autoloads nil "ein-notification" "ein-notification.el" (0
;;;;;; 0 0 0))
;;; Generated autoloads from ein-notification.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-notification" '("ein:")))
;;;***
;;;### (autoloads nil "ein-org" "ein-org.el" (0 0 0 0))
;;;### (autoloads nil "ein-org" "ein-org.el" (23468 61365 139112
;;;;;; 317000))
;;; Generated autoloads from ein-org.el
(autoload 'ein:org-open "ein-org" "\
@ -582,27 +422,10 @@ node `(org) External links' and Info node `(org) Search options'
(eval-after-load "org" '(if (fboundp 'org-link-set-parameters) (org-link-set-parameters "ipynb" :follow 'ein:org-open :help-echo "Open ipython notebook." :store 'ein:org-store-link) (org-add-link-type "ipynb" :follow 'ein:org-open) (add-hook 'org-store-link-functions 'ein:org-store-link)))
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-org" '(#("ein:org-goto-link" 0 17 (fontified nil)))))
;;;***
;;;### (autoloads nil "ein-output-area" "ein-output-area.el" (0 0
;;;;;; 0 0))
;;; Generated autoloads from ein-output-area.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-output-area" '("ein:")))
;;;***
;;;### (autoloads nil "ein-pager" "ein-pager.el" (0 0 0 0))
;;; Generated autoloads from ein-pager.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-pager" '("ein:pager-")))
;;;***
;;;### (autoloads nil "ein-pseudo-console" "ein-pseudo-console.el"
;;;;;; (0 0 0 0))
;;;;;; (23064 59027 198301 987000))
;;; Generated autoloads from ein-pseudo-console.el
(autoload 'ein:pseudo-console-mode "ein-pseudo-console" "\
@ -610,41 +433,10 @@ Pseudo console mode. Hit RET to execute code.
\(fn &optional ARG)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-pseudo-console" '("ein:pseudo-console-mode-map")))
;;;***
;;;### (autoloads nil "ein-python" "ein-python.el" (0 0 0 0))
;;; Generated autoloads from ein-python.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-python" '("ein:python-")))
;;;***
;;;### (autoloads nil "ein-pytools" "ein-pytools.el" (0 0 0 0))
;;; Generated autoloads from ein-pytools.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-pytools" '("ein:")))
;;;***
;;;### (autoloads nil "ein-query" "ein-query.el" (0 0 0 0))
;;; Generated autoloads from ein-query.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-query" '(#("ein:" 0 4 (face font-lock-function-name-face fontified nil)) #("*ein:jupyterhub-servers*" 0 24 (fontified nil face font-lock-variable-name-face)))))
;;;***
;;;### (autoloads nil "ein-scratchsheet" "ein-scratchsheet.el" (0
;;;;;; 0 0 0))
;;; Generated autoloads from ein-scratchsheet.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-scratchsheet" '("ein:")))
;;;***
;;;### (autoloads nil "ein-shared-output" "ein-shared-output.el"
;;;;;; (0 0 0 0))
;;;;;; (23468 61365 139112 317000))
;;; Generated autoloads from ein-shared-output.el
(autoload 'ein:shared-output-pop-to-buffer "ein-shared-output" "\
@ -671,40 +463,10 @@ shared output buffer. You can open the buffer by the command
\(fn CODE &optional POPUP VERBOSE KERNEL &rest ARGS)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-shared-output" '("ein:")))
;;;***
;;;### (autoloads nil "ein-skewer" "ein-skewer.el" (0 0 0 0))
;;; Generated autoloads from ein-skewer.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-skewer" '("ein:" "*ein:skewer-running-p*")))
;;;***
;;;### (autoloads nil "ein-smartrep" "ein-smartrep.el" (0 0 0 0))
;;; Generated autoloads from ein-smartrep.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-smartrep" '("ein:smartrep-")))
;;;***
;;;### (autoloads nil "ein-subpackages" "ein-subpackages.el" (0 0
;;;;;; 0 0))
;;; Generated autoloads from ein-subpackages.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-subpackages" '(#("ein:" 0 4 (face font-lock-function-name-face fontified t)))))
;;;***
;;;### (autoloads nil "ein-timestamp" "ein-timestamp.el" (0 0 0 0))
;;; Generated autoloads from ein-timestamp.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-timestamp" '("ein:")))
;;;***
;;;### (autoloads nil "ein-traceback" "ein-traceback.el" (0 0 0 0))
;;;### (autoloads nil "ein-traceback" "ein-traceback.el" (23468 61365
;;;;;; 139112 317000))
;;; Generated autoloads from ein-traceback.el
(autoload 'ein:tb-show "ein-traceback" "\
@ -712,47 +474,19 @@ Show full traceback in traceback viewer.
\(fn)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-traceback" '("ein:t")))
;;;***
;;;### (autoloads nil "ein-utils" "ein-utils.el" (0 0 0 0))
;;; Generated autoloads from ein-utils.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-utils" '("ein:")))
;;;***
;;;### (autoloads nil "ein-websocket" "ein-websocket.el" (0 0 0 0))
;;; Generated autoloads from ein-websocket.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-websocket" '("ein:websocket" "fix-request-netscape-cookie-parse")))
;;;***
;;;### (autoloads nil "ein-worksheet" "ein-worksheet.el" (0 0 0 0))
;;; Generated autoloads from ein-worksheet.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ein-worksheet" '("ein:")))
;;;***
;;;### (autoloads nil "ob-ein" "ob-ein.el" (0 0 0 0))
;;; Generated autoloads from ob-ein.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-ein" '("*ein:org-name-generator*" "ein:" "org-babel-")))
;;;***
;;;### (autoloads nil "zeroein" "zeroein.el" (0 0 0 0))
;;; Generated autoloads from zeroein.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "zeroein" '("zeroein:")))
;;;***
;;;### (autoloads nil nil ("debug-ein.el" "ein-pkg.el" "ein.el")
;;;;;; (0 0 0 0))
;;;### (autoloads nil nil ("debug-ein.el" "ein-ac.el" "ein-cell-edit.el"
;;;;;; "ein-cell-output.el" "ein-cell.el" "ein-classes.el" "ein-completer.el"
;;;;;; "ein-contents-api.el" "ein-core.el" "ein-events.el" "ein-file.el"
;;;;;; "ein-hy.el" "ein-ipdb.el" "ein-junk.el" "ein-kernelinfo.el"
;;;;;; "ein-kill-ring.el" "ein-log.el" "ein-multilang-fontify.el"
;;;;;; "ein-node.el" "ein-notification.el" "ein-output-area.el"
;;;;;; "ein-pager.el" "ein-pkg.el" "ein-python.el" "ein-pytools.el"
;;;;;; "ein-query.el" "ein-scratchsheet.el" "ein-skewer.el" "ein-smartrep.el"
;;;;;; "ein-subpackages.el" "ein-timestamp.el" "ein-utils.el" "ein-websocket.el"
;;;;;; "ein-worksheet.el" "ein.el" "ob-ein.el" "zeroein.el") (23475
;;;;;; 34241 891134 103000))
;;;***

View file

@ -70,7 +70,7 @@
(let* ((levname (ein:log-level-int-to-name level))
(print-level ein:log-print-level)
(print-length ein:log-print-length)
(msg (format "[%s] %s" levname (funcall func)))
(msg (format "%s: [%s] %s" (format-time-string "%H:%M:%S:%3N") levname (funcall func)))
(orig-buffer (current-buffer)))
(if (and ein:log-max-string
(> (length msg) ein:log-max-string))

View file

@ -359,7 +359,6 @@ notebook buffer when CALLBACK is called."
(when callback
(apply callback ein:%notebook% nil cbargs))
ein:%notebook%)
(ein:log 'info "Opening notebook %s..." path)
(ein:notebook-request-open url-or-port path kernelspec callback cbargs))))
(defun ein:notebook-request-open (url-or-port path &optional kernelspec callback cbargs)
@ -372,10 +371,9 @@ argument `t' indicates that the notebook is newly opened.
See `ein:notebook-open' for more information."
(let ((notebook (ein:notebook-new url-or-port path kernelspec)))
(ein:gc-prepare-operation)
(ein:log 'debug "Opening notebook at %s" path)
(ein:content-query-contents path url-or-port nil
(apply-partially #'ein:notebook-request-open-callback-with-callback
notebook callback cbargs))
(ein:content-query-contents url-or-port path
(apply-partially #'ein:notebook-request-open-callback-with-callback
notebook callback cbargs))
;; (ein:query-singleton-ajax
;; (list 'notebook-open url-or-port api-version path)
;; url
@ -391,6 +389,7 @@ See `ein:notebook-open' for more information."
callback
cbargs
content)
(ein:log 'verbose "Opened notebook %s" (ein:$notebook-notebook-path notebook))
(funcall #'ein:notebook-request-open-callback notebook content)
(when callback
(with-current-buffer (ein:notebook-buffer notebook)
@ -465,7 +464,7 @@ of minor mode."
(error "Fix me!")) ;; FIXME
(setf (ein:$notebook-autosave-timer notebook)
(run-at-time 0 ein:notebook-autosave-frequency #'ein:notebook-maybe-save-notebook notebook 0))
(ein:log 'info "Enabling autosaves for %s with frequency %s seconds."
(ein:log 'verbose "Enabling autosaves for %s with frequency %s seconds."
(ein:$notebook-notebook-name notebook)
ein:notebook-autosave-frequency))
@ -477,7 +476,7 @@ of minor mode."
"Select notebook [URL-OR-PORT/NAME]: "
(ein:notebook-opened-buffer-names)))))
(list notebook)))
(ein:log 'info "Disabling auto checkpoints for notebook %s" (ein:$notebook-notebook-name notebook))
(ein:log 'verbose "Disabling auto checkpoints for notebook %s" (ein:$notebook-notebook-name notebook))
(when (ein:$notebook-autosave-timer notebook)
(cancel-timer (ein:$notebook-autosave-timer notebook))))
@ -500,15 +499,13 @@ of minor mode."
;;; Kernel related things
(defvar ein:available-kernelspecs (make-hash-table :test #'equal))
(defun ein:kernelspec-for-nb-metadata (kernelspec)
(let ((display-name (plist-get (ein:$kernelspec-spec kernelspec) :display_name)))
`((:name . ,(ein:$kernelspec-name kernelspec))
(:display_name . ,(format "%s" display-name)))))
(defun ein:get-kernelspec (url-or-port name)
(let* ((kernelspecs (gethash url-or-port ein:available-kernelspecs))
(let* ((kernelspecs (ein:need-kernelspecs url-or-port))
(name (if (stringp name)
(intern (format ":%s" name))
name))
@ -518,50 +515,13 @@ of minor mode."
ks)))
(defun ein:list-available-kernels (url-or-port)
(let ((kernelspecs (gethash url-or-port ein:available-kernelspecs)))
(let ((kernelspecs (ein:need-kernelspecs url-or-port)))
(if kernelspecs
(sort (loop for (key spec) on (ein:plist-exclude kernelspecs '(:default)) by 'cddr
collecting (cons (ein:$kernelspec-name spec)
(ein:$kernelspec-display-name spec)))
(lambda (c1 c2) (string< (cdr c1) (cdr c2)))))))
(defun ein:query-kernelspecs (url-or-port &optional force-refresh)
"Query jupyter server for the list of available
kernels. Results are stored in ein:available-kernelspec, hashed
on server url/port."
(unless (and (not force-refresh) (gethash url-or-port ein:available-kernelspecs))
(ein:query-singleton-ajax
(list 'ein:query-kernelspecs url-or-port)
(ein:url url-or-port "api/kernelspecs")
:type "GET"
:timeout ein:content-query-timeout
:parser 'ein:json-read
:sync t
:success (apply-partially #'ein:query-kernelspecs-success url-or-port)
:error (apply-partially #'ein:query-kernelspecs-error))))
(defun* ein:query-kernelspecs-success (url-or-port &key data &allow-other-keys)
(let ((ks (list :default (plist-get data :default)))
(specs (ein:plist-iter (plist-get data :kernelspecs))))
(setf (gethash url-or-port ein:available-kernelspecs)
(ein:flatten (dolist (spec specs ks)
(let ((name (car spec))
(info (cdr spec)))
(push (list name (make-ein:$kernelspec :name (plist-get info :name)
:display-name (plist-get (plist-get info :spec)
:display_name)
:resources (plist-get info :resources)
:language (plist-get (plist-get info :spec)
:language)
:spec (plist-get info :spec)))
ks)))))))
(defun* ein:query-kernelspecs-error (&key symbol-status response &allow-other-keys)
(ein:log 'verbose
"Error thrown: %S" (request-response-error-thrown response))
(ein:log 'error
"Kernelspc query call failed with status %s." symbol-status))
(defun ein:notebook-switch-kernel (notebook kernel-name)
"Change the kernel for a running notebook. If not called from a
notebook buffer then the user will be prompted to select an opened notebook."
@ -765,7 +725,6 @@ This is equivalent to do ``C-c`` in the console program."
(defun ein:read-nbformat4-worksheets (notebook data)
"Convert a notebook in nbformat4 to a list of worksheet-like
objects suitable for processing in ein:notebook-from-json."
(ein:log 'info "Reading nbformat4 notebook.")
(let* ((cells (plist-get data :cells))
(ws-cells (mapcar (lambda (data) (ein:cell-from-json data)) cells))
(worksheet (ein:notebook--worksheet-new notebook)))

View file

@ -22,9 +22,6 @@
;;; Commentary:
;; The rendering is split into a function for ipython2 and one for
;; ipython3, ein:notebooklist-render-ipy2 and
;; ein:notebooklist-render.
;;; Code:
@ -43,12 +40,12 @@
(require 'dash)
(defcustom ein:notebook-list-render-order
(defcustom ein:notebooklist-render-order
'(render-header
render-opened-notebooks
render-directory-ipy3)
render-directory)
"Order of notebook list sections.
Must contain render-header, render-opened-notebooks, and render-directory-ipy3."
Must contain render-header, render-opened-notebooks, and render-directory."
:group 'ein
:type 'list
)
@ -73,7 +70,7 @@ is opened at first time.::
`ein:$notebooklist-url-or-port'
URL or port of IPython server.
`ein:$notbooklist-path'
`ein:$notebooklist-path'
The path for the notebooklist.
`ein:$notebooklist-data'
@ -175,7 +172,7 @@ To suppress popup, you can pass `ignore' as CALLBACK."
(defun ein:notebooklist-new-url (url-or-port version &optional path)
(let ((base-path (cond ((= version 2) "api/notebooks")
((>= version 3) "api/contents"))))
(ein:log 'info "New notebook. Port: %s, Path: %s" url-or-port path)
(ein:log 'info "New notebook %s" (concat (file-name-as-directory url-or-port) path))
(if (and path (not (string= path "")))
(ein:url url-or-port base-path path)
(ein:url url-or-port base-path))))
@ -204,37 +201,54 @@ To suppress popup, you can pass `ignore' as CALLBACK."
url-or-port)))
;;;###autoload
(defun ein:notebooklist-open (&optional url-or-port path no-popup)
(defun ein:notebooklist-open (url-or-port &optional path no-popup resync)
"Open notebook list buffer."
(interactive (list (ein:notebooklist-ask-url-or-port)))
(unless url-or-port (setq url-or-port (ein:default-url-or-port)))
(unless path (setq path ""))
(if (and (stringp url-or-port) (not (string-match-p "^https?" url-or-port)))
(setq url-or-port (format "http://%s" url-or-port)))
(ein:log 'debug "NOTEBOOKLIST-OPEN: %s/%s" url-or-port path)
(ein:subpackages-load)
(let ((success
(if no-popup
#'ein:notebooklist-url-retrieve-callback
(lambda (content)
(pop-to-buffer
(funcall #'ein:notebooklist-url-retrieve-callback content))))))
(ein:query-kernelspecs url-or-port)
(ein:content-query-contents path url-or-port t success))
;(ein:notebooklist-get-buffer url-or-port)
(lexical-let ((url-or-port url-or-port)
(path path)
(success (if no-popup
#'ein:notebooklist-open--finish
(lambda (content)
(pop-to-buffer
(funcall #'ein:notebooklist-open--finish content))))))
(if (or resync (not (ein:notebooklist-list-get url-or-port)))
(deferred:$
(deferred:parallel
(lexical-let ((d (deferred:new #'identity)))
(ein:query-ipython-version url-or-port (lambda ()
(deferred:callback-post d)))
d)
(lexical-let ((d (deferred:new #'identity)))
(ein:query-kernelspecs url-or-port (lambda ()
(deferred:callback-post d)))
d)
(lexical-let ((d (deferred:new #'identity)))
(ein:content-query-hierarchy url-or-port (lambda (tree)
(deferred:callback-post d)))
d))
(deferred:nextc it
(lambda (&rest ignore)
(ein:content-query-contents url-or-port path success))))
(ein:content-query-contents url-or-port path success)))
)
;;;###autoload
(defun ein:notebooklist-refresh-kernelspecs (&optional url-or-port)
(interactive (list (or (and ein:%notebooklist% (ein:$notebooklist-url-or-port ein:%notebooklist%))
(ein:notebooklist-ask-url-or-port))))
(unless url-or-port
(if ein:%notebooklist%
(setq url-or-port (ein:$notebooklist-url-or-port ein:%notebooklist%))
(setq url-or-port (ein:default-url-or-port))))
(ein:query-kernelspecs url-or-port t)
(when ein:%notebooklist%
(ein:notebooklist-reload ein:%notebooklist%)))
;; point of order (poo): ein:notebooklist-refresh-kernelspecs requeries the kernelspecs and calls ein:notebooklist-reload. ein:notebooklist-reload already requeries the kernelspecs in one of its callbacks, so this function seems redundant.
;; (defun ein:notebooklist-refresh-kernelspecs (&optional url-or-port)
;; (interactive (list (or (and ein:%notebooklist% (ein:$notebooklist-url-or-port ein:%notebooklist%))
;; (ein:notebooklist-ask-url-or-port))))
;; (unless url-or-port
;; (if ein:%notebooklist%
;; (setq url-or-port (ein:$notebooklist-url-or-port ein:%notebooklist%))
;; (setq url-or-port (ein:default-url-or-port))))
;; (ein:query-kernelspecs url-or-port)
;; (when ein:%notebooklist%
;; (ein:notebooklist-reload ein:%notebooklist%))
;; )
(defcustom ein:notebooklist-keepalive-refresh-time 1
"When the notebook keepalive is enabled, the frequency, IN
@ -275,7 +289,7 @@ automatically be called during calls to `ein:notebooklist-open`."
(ein:log 'info "Refreshing notebooklist connection.")))
(refresh-time (* ein:notebooklist-keepalive-refresh-time 60 60)))
(setq ein:notebooklist--keepalive-timer
(run-at-time 0.1 refresh-time #'ein:content-query-contents "" url-or-port nil success)))))
(run-at-time 0.1 refresh-time #'ein:content-query-contents url-or-port "" success)))))
;;;###autoload
(defun ein:notebooklist-disable-keepalive ()
@ -285,14 +299,12 @@ automatically be called during calls to `ein:notebooklist-open`."
(cancel-timer ein:notebooklist--keepalive-timer)
(setq ein:notebooklist--keepalive-timer nil))
(defun* ein:notebooklist-url-retrieve-callback (content)
(defun ein:notebooklist-open--finish (content)
"Called via `ein:notebooklist-open'."
(let ((url-or-port (ein:$content-url-or-port content))
(path (ein:$content-path content))
(ipy-version (ein:$content-ipython-version content))
(data (ein:$content-raw-content content)))
(when (>= ipy-version 3)
(ein:query-kernelspecs url-or-port))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(let ((already-opened-p (ein:notebooklist-list-get url-or-port))
(orig-point (point)))
@ -302,11 +314,9 @@ automatically be called during calls to `ein:notebooklist-open`."
:data data
:api-version ipy-version))
(ein:notebooklist-list-add ein:%notebooklist%)
(if (< ipy-version 3)
(ein:notebooklist-render-ipy2)
(ein:notebooklist-render))
(ein:notebooklist-render ipy-version)
(goto-char orig-point)
(ein:log 'info "Opened notebook list at %s with path %s." url-or-port path)
(ein:log 'verbose "Opened notebooklist at %s" (concat (file-name-as-directory url-or-port) path))
(unless already-opened-p
(run-hooks 'ein:notebooklist-first-open-hook))
(when ein:enable-keepalive
@ -314,22 +324,18 @@ automatically be called during calls to `ein:notebooklist-open`."
(current-buffer)))))
(defun* ein:notebooklist-open-error (url-or-port path
&key symbol-status response
&key error-thrown
&allow-other-keys)
(ein:log 'verbose
"Error thrown: %S" (request-response-error-thrown response))
(ein:log 'error
"Error (%s) while opening notebook list with path %s at the server %s."
symbol-status path url-or-port))
"ein:notebooklist-open-error %s: ERROR %s DATA %s" (concat (file-name-as-directory url-or-port) path) (car error-thrown) (cdr error-thrown)))
;;;###autoload
(defun ein:notebooklist-reload (&optional notebooklist)
(defun ein:notebooklist-reload (notebooklist &optional resync)
"Reload current Notebook list."
(interactive)
(unless notebooklist
(setq notebooklist ein:%notebooklist%))
(ein:notebooklist-open (ein:$notebooklist-url-or-port notebooklist)
(ein:$notebooklist-path notebooklist) t))
(interactive (list ein:%notebooklist%))
(when notebooklist
(ein:notebooklist-open (ein:$notebooklist-url-or-port notebooklist)
(ein:$notebooklist-path notebooklist) t resync)))
(defun ein:notebooklist-refresh-related ()
"Reload notebook list in which current notebook locates.
@ -403,7 +409,7 @@ This function is called via `ein:notebook-after-rename-hook'."
(if data
(let ((name (plist-get data :name))
(path (plist-get data :path)))
(if (= (ein:query-ipython-version url-or-port) 2)
(if (= (ein:need-ipython-version url-or-port) 2)
(if (string= path "")
(setq path name)
(setq path (format "%s/%s" path name))))
@ -428,7 +434,7 @@ This function is called via `ein:notebook-after-rename-hook'."
"Failed to open new notebook (error: %S). \
You may find the new one in the notebook list." error)
(setq no-popup nil)
(ein:notebooklist-open url-or-port no-popup))
(ein:notebooklist-open url-or-port "" no-popup))
;;;###autoload
(defun ein:notebooklist-new-notebook-with-name (name kernelspec url-or-port &optional path)
@ -463,7 +469,6 @@ You may find the new one in the notebook list." error)
(ein:notebooklist-delete-notebook path)))
(defun ein:notebooklist-delete-notebook (path)
(ein:log 'info "Deleting notebook %s..." path)
(ein:query-singleton-ajax
(list 'notebooklist-delete-notebook
(ein:$notebooklist-url-or-port ein:%notebooklist%) path)
@ -472,10 +477,10 @@ You may find the new one in the notebook list." error)
(ein:$notebooklist-api-version ein:%notebooklist%)
path)
:type "DELETE"
:success (apply-partially (lambda (path notebook-list &rest ignore)
:success (apply-partially (lambda (path notebooklist &rest ignore)
(ein:log 'info
"Deleting notebook %s... Done." path)
(ein:notebooklist-reload notebook-list))
"Deleted notebook %s" path)
(ein:notebooklist-reload notebooklist))
path ein:%notebooklist%)))
;; Because MinRK wants me to suffer (not really, I love MinRK)...
@ -493,21 +498,6 @@ You may find the new one in the notebook list." error)
(setf current-path (concat current-path "/" p)
pairs (append pairs (list (cons p current-path)))))))
(defun ein:notebooklist-render-ipy2 ()
"Render notebook list for IPython 2.x sessions.
Notebook list data is passed via the buffer local variable
`ein:notebooklist-data'."
(kill-all-local-variables)
(let ((inhibit-read-only t))
(erase-buffer))
(remove-overlays)
(render-header-ipy2)
(render-directory-ipy2)
(ein:notebooklist-mode)
(widget-setup))
(defun* ein:nblist--sort-group (group by-param order)
(sort group #'(lambda (x y)
(cond ((eql order :ascending)
@ -534,7 +524,7 @@ Notebook list data is passed via the buffer local variable
sort-order)))
(-concat dirs nbs files)))
(defun render-header-ipy2 ()
(defun render-header-ipy2 (&rest args)
"Render the header (for ipython2)."
;; Create notebook list
(widget-insert (format "IPython %s Notebook list\n\n" (ein:$notebooklist-api-version ein:%notebooklist%)))
@ -560,7 +550,7 @@ Notebook list data is passed via the buffer local variable
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-reload))
:notify (lambda (&rest ignore) (ein:notebooklist-reload ein:%notebooklist% t))
"Reload List")
(widget-insert " ")
(widget-create
@ -571,125 +561,102 @@ Notebook list data is passed via the buffer local variable
"Open In Browser")
(widget-insert "\n"))
(defun render-header ()
(defun render-header* (url-or-port &rest args)
"Render the header (for ipython>=3)."
;; Create notebook list
(widget-insert
(if (< (ein:$notebooklist-api-version ein:%notebooklist%) 4)
(format "IPython v%s Notebook list (%s)\n\n" (ein:$notebooklist-api-version ein:%notebooklist%) (ein:$notebooklist-url-or-port ein:%notebooklist%))
(format "Jupyter v%s Notebook list (%s)\n\n" (ein:$notebooklist-api-version ein:%notebooklist%) (ein:$notebooklist-url-or-port ein:%notebooklist%))))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(widget-insert
(if (< (ein:$notebooklist-api-version ein:%notebooklist%) 4)
(format "IPython v%s Notebook list (%s)\n\n" (ein:$notebooklist-api-version ein:%notebooklist%) url-or-port)
(format "Jupyter v%s Notebook list (%s)\n\n" (ein:$notebooklist-api-version ein:%notebooklist%) url-or-port)))
(let ((breadcrumbs (generate-breadcrumbs (ein:$notebooklist-path ein:%notebooklist%))))
(dolist (p breadcrumbs)
(lexical-let ((name (car p))
(path (cdr p)))
(widget-insert " | ")
(widget-create
'link
:notify (lambda (&rest ignore)
(ein:notebooklist-open
(ein:$notebooklist-url-or-port ein:%notebooklist%)
path))
name)))
(widget-insert " |\n\n"))
(let ((breadcrumbs (generate-breadcrumbs (ein:$notebooklist-path ein:%notebooklist%))))
(dolist (p breadcrumbs)
(lexical-let ((url-or-port url-or-port)
(name (car p))
(path (cdr p)))
(widget-insert " | ")
(widget-create
'link
:notify (lambda (&rest ignore)
(ein:notebooklist-open url-or-port path))
name)))
(widget-insert " |\n\n"))
(lexical-let* ((url-or-port (ein:$notebooklist-url-or-port ein:%notebooklist%))
(kernels (ein:list-available-kernels url-or-port)))
(if (null ein:%notebooklist-new-kernel%)
(setq ein:%notebooklist-new-kernel% (ein:get-kernelspec url-or-port (caar kernels))))
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-new-notebook
url-or-port
ein:%notebooklist-new-kernel%))
"New Notebook")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-reload))
"Reload List")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-refresh-kernelspecs))
"Query Kernelspecs")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore)
(browse-url
(ein:url url-or-port)))
"Open In Browser")
(lexical-let* ((url-or-port url-or-port)
(kernels (ein:list-available-kernels url-or-port)))
(if (null ein:%notebooklist-new-kernel%)
(setq ein:%notebooklist-new-kernel% (ein:get-kernelspec url-or-port (caar kernels))))
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-new-notebook
url-or-port
ein:%notebooklist-new-kernel%))
"New Notebook")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-reload ein:%notebooklist% t))
"Resync")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore)
(browse-url (ein:url url-or-port)))
"Open In Browser")
(widget-insert "\n\nCreate New Notebooks Using Kernel: \n")
(let* ((radio-widget (widget-create 'radio-button-choice
:value (and ein:%notebooklist-new-kernel% (ein:$kernelspec-name ein:%notebooklist-new-kernel%))
:notify (lambda (widget &rest ignore)
(setq ein:%notebooklist-new-kernel%
(ein:get-kernelspec url-or-port (widget-value widget)))
(message "New notebooks will be started using the %s kernel."
(ein:$kernelspec-display-name ein:%notebooklist-new-kernel%))))))
(dolist (k kernels)
(widget-radio-add-item radio-widget (list 'item :value (car k)
:format (format "%s\n" (cdr k)))))))
(widget-insert "\n"))
(widget-insert "\n\nCreate New Notebooks Using Kernel:\n")
(let* ((radio-widget (widget-create 'radio-button-choice
:value (and ein:%notebooklist-new-kernel% (ein:$kernelspec-name ein:%notebooklist-new-kernel%))
:notify (lambda (widget &rest ignore)
(setq ein:%notebooklist-new-kernel%
(ein:get-kernelspec url-or-port (widget-value widget)))
(message "New notebooks will be started using the %s kernel."
(ein:$kernelspec-display-name ein:%notebooklist-new-kernel%))))))
(if (null kernels)
(widget-insert "\n No kernels found.")
(dolist (k kernels)
(widget-radio-add-item radio-widget (list 'item :value (car k)
:format (format "%s\n" (cdr k)))))
(widget-insert "\n"))))))
(defun render-opened-notebooks ()
(defun render-opened-notebooks (url-or-port &rest args)
"Render the opened notebooks section (for ipython>=3)."
;; Opened Notebooks Section
(widget-insert "\n---------- All Opened Notebooks ----------\n\n")
(loop for buffer in (ein:notebook-opened-buffers)
do (progn (widget-create
'link
:notify (lexical-let ((buffer buffer))
(lambda (&rest ignore)
(switch-to-buffer buffer)))
"Open")
(widget-create
'link
:notify (lexical-let ((buffer buffer))
(lambda (&rest ignore)
(kill-buffer buffer)
(run-at-time 1 nil
#'ein:notebooklist-reload
ein:%notebooklist%)))
"Close")
(widget-insert " : " (buffer-name buffer))
(widget-insert "\n"))))
(defun render-directory-ipy3 ()
"Call render-direcory with ipy-at-least-3 true."
(render-directory t))
(defun render-directory-ipy2 ()
"Call render-direcory with ipy-at-least-3 false."
(render-directory nil))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(widget-insert "\n---------- All Opened Notebooks ----------\n\n")
(loop for buffer in (ein:notebook-opened-buffers)
do (progn (widget-create
'link
:notify (lexical-let ((buffer buffer))
(lambda (&rest ignore)
(switch-to-buffer buffer)))
"Open")
(widget-create
'link
:notify (lexical-let ((buffer buffer))
(lambda (&rest ignore)
(kill-buffer buffer)
(run-at-time 1 nil
#'ein:notebooklist-reload
ein:%notebooklist%)))
"Close")
(widget-insert " : " (buffer-name buffer))
(widget-insert "\n")))))
(defun ein:format-nbitem-data (name last-modified)
(let ((dt (date-to-time last-modified)))
(format "%-40s%+20s" name
(ein:format-time-string ein:notebooklist-date-format dt))))
(defun render-directory (ipy-at-least-3)
"Render directory.
IPY-AT-LEAST-3 used to keep track of version."
(widget-insert "\n------------------------------------------\n\n")
(unless ipy-at-least-3
(let (api-version (ein:$notebooklist-api-version ein:%notebooklist%))))
(let ((sessions #s(hash-table test equal data (:pending t))))
(ein:content-query-sessions sessions (ein:$notebooklist-url-or-port ein:%notebooklist%) t)
(loop repeat 4
when (null (gethash :pending sessions))
return t
do (sleep-for 0 50))
(defun render-directory (url-or-port sessions)
(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)
(ein:make-sorting-widget "In Order" ein:notebooklist-sort-order)
(widget-insert "\n")
(loop for note in (ein:notebooklist--order-data (ein:$notebooklist-data ein:%notebooklist%)
ein:notebooklist-sort-field
ein:notebooklist-sort-order)
for urlport = (ein:$notebooklist-url-or-port ein:%notebooklist%)
for name = (plist-get note :name)
for path = (plist-get note :path)
for last-modified = (plist-get note :last_modified)
@ -698,27 +665,27 @@ IPY-AT-LEAST-3 used to keep track of version."
;; ((= 3 api-version)
;; (ein:get-actual-path (plist-get note :path))))
for type = (plist-get note :type)
for opened-notebook-maybe = (ein:notebook-get-opened-notebook urlport path)
for opened-notebook-maybe = (ein:notebook-get-opened-notebook url-or-port path)
do (widget-insert " ")
if (string= type "directory")
do (progn (widget-create
'link
:notify (lexical-let ((urlport urlport)
(path name))
:notify (lexical-let ((url-or-port url-or-port)
(name name))
(lambda (&rest ignore)
(ein:notebooklist-open urlport
(ein:url (ein:$notebooklist-path ein:%notebooklist%)
path))))
;; each directory creates a whole new notebooklist
(ein:notebooklist-open url-or-port
(ein:url (ein:$notebooklist-path ein:%notebooklist%) name))))
"Dir")
(widget-insert " : " name)
(widget-insert "\n"))
if (and (string= type "file") ipy-at-least-3)
if (and (string= type "file") (> (ein:need-ipython-version url-or-port) 2))
do (progn (widget-create
'link
:notify (lexical-let ((urlport urlport)
:notify (lexical-let ((url-or-port url-or-port)
(path path))
(lambda (&rest ignore)
(ein:notebooklist-open-file urlport path)))
(ein:notebooklist-open-file url-or-port path)))
"Open")
(widget-insert " ------ ")
(widget-create
@ -765,8 +732,7 @@ IPY-AT-LEAST-3 used to keep track of version."
(widget-insert " : " (ein:format-nbitem-data name last-modified))
(widget-insert "\n")))))
(defun ein:notebooklist-render ()
(defun ein:notebooklist-render (ipy-version)
"Render notebook list widget.
Notebook list data is passed via the buffer local variable
`ein:notebooklist-data'."
@ -775,26 +741,36 @@ Notebook list data is passed via the buffer local variable
(erase-buffer))
(remove-overlays)
(mapc #'funcall ein:notebook-list-render-order)
(let ((url-or-port (ein:$notebooklist-url-or-port ein:%notebooklist%)))
(ein:content-query-sessions url-or-port
(apply-partially #'ein:notebooklist-render--finish ipy-version url-or-port))))
(ein:notebooklist-mode)
(widget-setup))
(defun ein:notebooklist-render--finish (ipy-version url-or-port sessions)
(cl-letf (((symbol-function 'render-header) (if (< ipy-version 3)
#'render-header-ipy2
#'render-header*)))
(mapc (lambda (x) (funcall (symbol-function x) url-or-port sessions))
ein:notebooklist-render-order))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(ein:notebooklist-mode)
(widget-setup)
(goto-char (point-min))))
;;;###autoload
(defun ein:notebooklist-list-notebooks ()
"Return a list of notebook path (NBPATH). Each element NBPATH
is a string of the format \"URL-OR-PORT/NOTEBOOK-NAME\"."
(apply #'append
(loop for nblist in (ein:notebooklist-list)
for url-or-port = (ein:$notebooklist-url-or-port nblist)
for api-version = (ein:$notebooklist-api-version nblist)
collect
(loop for note in (ein:get-content-hierarchy url-or-port)
collect (format "%s/%s" url-or-port
(ein:$content-path note)
))
(loop for content in (ein:content-need-hierarchy url-or-port)
when (string= (ein:$content-type content) "notebook")
collect (format "%s/%s" url-or-port
(ein:$content-path content)))
;; (if (= api-version 3)
;; (loop for note in (ein:make-content-hierarchy "" url-or-port)
;; (loop for note in (ein:content-need-hierarchy url-or-port)
;; collect (format "%s/%s" url-or-port
;; (ein:$content-path note)
;; ))
@ -807,6 +783,7 @@ is a string of the format \"URL-OR-PORT/NOTEBOOK-NAME\"."
;;FIXME: Code below assumes notebook is in root directory - need to do a better
;; job listing notebooks in subdirectories and parsing out the path.
;;;###autoload
(defun ein:notebooklist-open-notebook-global (nbpath &optional callback cbargs)
"Choose notebook from all opened notebook list and open it.
Notebook is specified by a string NBPATH whose format is
@ -814,18 +791,18 @@ Notebook is specified by a string NBPATH whose format is
When used in lisp, CALLBACK and CBARGS are passed to `ein:notebook-open'."
(interactive
(list (completing-read
"Open notebook [URL-OR-PORT/NAME]: "
(ein:notebooklist-list-notebooks))))
(let* ((url-or-port (substring nbpath 0 (cl-position ?/ nbpath)))
(path (substring nbpath (1+ (cl-position ?/ nbpath)))))
(when (and (stringp url-or-port)
(string-match "^[0-9]+$" url-or-port))
(setq url-or-port (string-to-number url-or-port)))
(ein:notebook-open url-or-port path nil callback cbargs)
(ein:log 'info "Notebook '%s' not found" nbpath)))
(list (if noninteractive
(car (ein:notebooklist-list-notebooks))
(completing-read
"Open notebook [URL-OR-PORT/NAME]: "
(ein:notebooklist-list-notebooks)))))
(let* ((parsed (url-generic-parse-url nbpath))
(path (url-filename parsed)))
(ein:notebook-open (substring nbpath 0 (- (length nbpath) (length path)))
(substring path 1) nil callback cbargs)))
;;;###autoload
(defun ein:notebooklist-load (&optional url-or-port)
"Load notebook list but do not pop-up the notebook list buffer.
@ -844,7 +821,7 @@ in order to make this code work.
See also:
`ein:connect-to-default-notebook', `ein:connect-default-notebook'."
(ein:notebooklist-open url-or-port t))
(ein:notebooklist-open url-or-port "" t))
(defun ein:notebooklist-find-server-by-notebook-name (name)
@ -855,7 +832,7 @@ See also:
for ipython-version = (ein:$notebooklist-api-version nblist)
do
(if (>= ipython-version 3)
(loop for note in (ein:make-content-hierarchy "" url-or-port)
(loop for note in (ein:content-need-hierarchy url-or-port)
when (equal (ein:$content-name note) name)
do (return-from outer
(list url-or-port (ein:$content-path note))))
@ -866,7 +843,7 @@ See also:
(format "%s/%s" (plist-get note :path) (plist-get note :name))))))))
(defun ein:notebooklist-open-notebook-by-file-name
(&optional filename noerror buffer-callback)
(&optional filename noerror buffer-callback)
"Find the notebook named as same as the current file in the servers.
Open the notebook if found. Note that this command will *not*
upload the current file to the server.
@ -901,18 +878,18 @@ FIMXE: document how to use `ein:notebooklist-find-file-callback'
when I am convinced with the API."
(ein:and-let* ((filename buffer-file-name)
((string-match-p "\\.ipynb$" filename)))
(ein:notebooklist-open-notebook-by-file-name
filename t ein:notebooklist-find-file-buffer-callback)))
(ein:notebooklist-open-notebook-by-file-name
filename t ein:notebooklist-find-file-buffer-callback)))
;;; Login
;;;###autoload
(defun ein:notebooklist-login (url-or-port password &optional retry-p)
"Login to IPython notebook server."
(interactive (list (ein:notebooklist-ask-url-or-port)
(read-passwd "Password: ")))
(ein:log 'debug "NOTEBOOKLIST-LOGIN: %s:%s" url-or-port password)
(ein:query-singleton-ajax
(list 'notebooklist-login url-or-port)
(ein:url url-or-port "login")
@ -964,6 +941,7 @@ Now you can open notebook list by `ein:notebooklist-open'." url-or-port))
(ein:notebooklist-login--error-1 url-or-port)))
;;;###autoload
(defun ein:notebooklist-change-url-port (new-url-or-port)
"Update the ipython/jupyter notebook server URL for all the
notebooks currently opened from the current notebooklist buffer.
@ -979,7 +957,7 @@ on all the notebooks opened from the current notebooklist."
(open-nb (ein:notebook-opened-notebooks #'(lambda (nb)
(equal (ein:$notebook-url-or-port nb)
(ein:$notebooklist-url-or-port current-nblist))))))
(ein:notebooklist-open new-url-or-port "/" t)
(ein:notebooklist-open new-url-or-port "" t)
(loop for x upfrom 0 by 1
until (or (get-buffer (format ein:notebooklist-buffer-name-template new-url-or-port))
(= x 100))
@ -987,7 +965,7 @@ on all the notebooks opened from the current notebooklist."
(dolist (nb open-nb)
(ein:notebook-update-url-or-port new-url-or-port nb))
(kill-buffer (ein:notebooklist-get-buffer old-url))
(ein:notebooklist-open new-url-or-port "/" nil)))
(ein:notebooklist-open new-url-or-port "" nil)))
(defun ein:notebooklist-change-url-port--deferred (new-url-or-port)
(lexical-let* ((current-nblist ein:%notebooklist%)
@ -999,22 +977,23 @@ on all the notebooks opened from the current notebooklist."
(ein:$notebooklist-url-or-port current-nblist))))))
(deferred:$
(deferred:next
(lambda ()
(ein:notebooklist-open new-url-or-port "/" t)
(loop until (get-buffer (format ein:notebooklist-buffer-name-template new-url-or-port))
do (sit-for 0.1))))
(lambda ()
(ein:notebooklist-open new-url-or-port "" t)
(loop until (get-buffer (format ein:notebooklist-buffer-name-template new-url-or-port))
do (sit-for 0.1))))
(deferred:nextc it
(lambda ()
(dolist (nb open-nb)
(ein:notebook-update-url-or-port new-url-or-port nb))))
(lambda ()
(dolist (nb open-nb)
(ein:notebook-update-url-or-port new-url-or-port nb))))
(deferred:nextc it
(lambda ()
(kill-buffer (ein:notebooklist-get-buffer old-url))
(ein:notebooklist-open new-url-or-port "/" nil))))))
(lambda ()
(kill-buffer (ein:notebooklist-get-buffer old-url))
(ein:notebooklist-open new-url-or-port "" nil))))))
;;; Generic getter
(defun ein:get-url-or-port--notebooklist ()
(when (ein:$notebooklist-p ein:%notebooklist%)
(ein:$notebooklist-url-or-port ein:%notebooklist%)))
@ -1022,6 +1001,7 @@ on all the notebooks opened from the current notebooklist."
;;; Notebook list mode
(defun ein:notebooklist-prev-item () (interactive) (move-beginning-of-line 0))
(defun ein:notebooklist-next-item () (interactive) (move-beginning-of-line 2))
@ -1046,7 +1026,7 @@ on all the notebooks opened from the current notebooklist."
("New Junk Notebook" ein:junk-new)))))
(defun ein:notebooklist-revert-wrapper (&optional ignore-auto noconfirm preserve-modes)
(ein:notebooklist-reload))
(ein:notebooklist-reload ein:%notebooklist%))
(define-derived-mode ein:notebooklist-mode special-mode "ein:notebooklist"
"IPython notebook list mode.

View file

@ -158,7 +158,9 @@ KEY, then call `request' with URL and SETTINGS. KEY is compared by
(setq settings (plist-put settings :timeout (/ timeout 1000.0))))
(ein:aif (gethash key ein:query-running-process-table)
(unless (request-response-done-p it)
(request-abort it))) ; This will run callbacks
;; This seems to result in clobbered cookie jars
;;(request-abort it) ; This will run callbacks
(ein:log 'info "Race! %s %s" key (request-response-data it))))
(let ((response (apply #'request (url-encode-url (ein:jupyterhub-correct-query-url-maybe url))
(ein:query-prepare-header url settings))))
(puthash key response ein:query-running-process-table)

View file

@ -298,10 +298,13 @@ given in the session parameter."
(when (and (stringp session) (string= session "none"))
(error "You must specify a notebook or kernelspec as the session variable for ein code blocks."))
(multiple-value-bind (url-or-port path) (ein:org-babel-parse-session session)
(if (null (gethash url-or-port ein:available-kernelspecs))
(ein:query-kernelspecs url-or-port))
(if (null kernelspec)
(setq kernelspec (ein:get-kernelspec url-or-port "default")))
(when (null kernelspec)
;; Now is not the time to be getting kernelspecs.
;; If I must do so, need to inject a deferred callback chain like
;; in ein:notebooklist
;; (if (null (gethash url-or-port ein:available-kernelspecs))
;; (ein:query-kernelspecs url-or-port))
(setq kernelspec (ein:get-kernelspec url-or-port "default")))
(cond ((null path)
(let* ((name ein:org-babel-default-session-name)
(new-session (format "%s/%s" url-or-port name)))

View file

@ -45,17 +45,17 @@
;; "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:query-ipython-version (&optional url-or-port force) 3)
(ein:need-ipython-version (url-or-port) 3)
(ein:notebook-start-kernel (notebook))
(ein:notebook-enable-autosaves (notebook)))
(let ((notebook (ein:notebook-new ein:testing-notebook-dummy-url path kernelspec)))
(setf (ein:$notebook-kernel notebook)
(ein:kernel-new 8888 "/kernels" (ein:$notebook-events notebook) (ein:query-ipython-version)))
(ein:kernel-new 8888 "/kernels" (ein:$notebook-events notebook) (ein:need-ipython-version (ein:$notebook-url-or-port notebook))))
(setf (ein:$kernel-events (ein:$notebook-kernel notebook))
(ein:events-new))
; matryoshka: new-content makes a ein:$content using CONTENT as template
; populating its raw_content field with DATA's content field
(ein:notebook-request-open-callback notebook (ein:new-content content nil :data data))
(ein:notebook-request-open-callback notebook (ein:new-content (ein:$notebook-url-or-port notebook) (ein:$notebook-notebook-path notebook) data))
(ein:notebook-buffer notebook)))))
(defun ein:testing-notebook-make-data (name path cells)

View file

@ -26,6 +26,7 @@
;;; Code:
(require 'ein-log)
(require 'request)
(defmacro ein:setq-if-not (sym val)
`(unless ,sym (setq ,sym ,val)))
@ -36,9 +37,11 @@
(defvar ein:testing-dump-file-messages nil
"File to save the ``*Messages*`` buffer.")
(defvar ein:testing-dump-file-debug nil)
(defvar ein:testing-dump-file-server nil
"File to save `ein:jupyter-server-buffer-name`.")
(defvar ein:testing-dump-server-log nil)
(defvar ein:testing-dump-file-request nil
"File to save `request-log-buffer-name`.")
(defun ein:testing-save-buffer (buffer-or-name file-name)
(when (and buffer-or-name (get-buffer buffer-or-name) file-name)
@ -47,29 +50,28 @@
(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-server-log)
(ein:testing-save-buffer ein:log-all-buffer-name ein:testing-dump-file-log))
(ein:testing-save-buffer "*ein:jupyter-server*" ein:testing-dump-file-server)
(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))
(defvar ein:testing-dump-logs--saved nil)
(defun ein:testing-dump-logs-noerror ()
(if ein:testing-dump-logs--saved
(message "EIN:TESTING-DUMP-LOGS-NOERROR called but already saved.")
(condition-case err
(progn (ein:testing-dump-logs)
(setq ein:testing-dump-logs--saved t))
(error
(message "Error while executing EIN:TESTING-DUMP-LOGS. err = %S"
err)
(when ein:testing-dump-file-debug
(signal (car err) (cdr err)))))))
(defun ein:testing-wait-until (predicate &optional predargs ms interval)
"Wait until PREDICATE function returns non-`nil'.
PREDARGS is argument list for the PREDICATE function.
MS is milliseconds to wait. INTERVAL is polling interval in milliseconds."
(let* ((interval (or interval 300))
(count (max 1 (if ms (truncate (/ ms interval)) 25))))
(unless (loop repeat count
when (apply predicate predargs)
return t
do (sleep-for 0 interval))
(error "Timeout: %s" predicate))))
(defadvice ert-run-tests-batch (after ein:testing-dump-logs-hook activate)
"Hook `ein:testing-dump-logs-noerror' because `kill-emacs-hook'
"Hook `ein:testing-dump-logs-hook' because `kill-emacs-hook'
is not run in batch mode before Emacs 24.1."
(ein:testing-dump-logs-noerror))
(ein:testing-dump-logs))
(add-hook 'kill-emacs-hook #'ein:testing-dump-logs-noerror)
(add-hook 'kill-emacs-hook #'ein:testing-dump-logs)
(provide 'ein-testing)

View file

@ -3,9 +3,9 @@
(defun eintest:notebooklist-make-empty (&optional url-or-port)
"Make empty notebook list buffer."
(flet ((ein:query-kernelspecs (url-or-port &optional force-refresh))
(ein:content-query-sessions (session-hash url-or-port &optional force-sync)))
(ein:notebooklist-url-retrieve-callback
(flet ((ein:need-kernelspecs (url-or-port))
(ein:content-query-sessions (session-hash url-or-port)))
(ein:notebooklist-open--finish
(make-ein:$content :url-or-port (or url-or-port ein:testing-notebook-dummy-url)
:ipython-version 3
:path ""))))

View file

@ -16,29 +16,12 @@
(setq message-log-max t)
(defun ein:testing-wait-until (message predicate &optional predargs max-count)
"Wait until PREDICATE function returns non-`nil'.
PREDARGS is argument list for the PREDICATE function.
Make MAX-COUNT larger \(default 50) to wait longer before timeout."
(ein:log 'debug "TESTING-WAIT-UNTIL start")
(ein:log 'debug "TESTING-WAIT-UNTIL waiting on: %s" message)
(unless max-count (setq max-count 50))
(unless (loop repeat max-count
when (apply predicate predargs)
return t
;; borrowed from `deferred:sync!':
do (sit-for 0.2)
do (sleep-for 0.2))
(error "Timeout"))
(ein:log 'debug "TESTING-WAIT-UNTIL end"))
(defun ein:testing-get-notebook-by-name (url-or-port notebook-name &optional path)
(ein:log 'debug "TESTING-GET-NOTEBOOK-BY-NAME start")
(when path
(setq notebook-name (format "%s/%s" path notebook-name)))
(ein:notebooklist-open url-or-port path t)
(ein:testing-wait-until "ein:notebooklist-open"
(lambda () (and (bufferp (get-buffer (format ein:notebooklist-buffer-name-template url-or-port)))
(ein:testing-wait-until (lambda () (and (bufferp (get-buffer (format ein:notebooklist-buffer-name-template url-or-port)))
(ein:notebooklist-get-buffer url-or-port))))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(prog1
@ -60,31 +43,25 @@ Make MAX-COUNT larger \(default 50) to wait longer before timeout."
(ein:notebooklist-new-notebook url-or-port kernelspec path
(lambda (&rest -ignore-)
(setq created t)))
(ein:testing-wait-until "ein:notebooklist-new-notebook"
(lambda () created)))
(ein:testing-wait-until (lambda () created)))
(prog1
(ein:testing-get-notebook-by-name url-or-port "Untitled.ipynb" path)
(ein:log 'debug "TESTING-GET-UNTITLED0-OR-CREATE end")))))
(defvar ein:notebooklist-after-open-hook nil)
(defadvice ein:notebooklist-url-retrieve-callback
(after ein:testing-notebooklist-url-retrieve-callback activate)
(defadvice ein:notebooklist-open--finish
(after ein:testing-notebooklist-open--finish activate)
"Advice to add `ein:notebooklist-after-open-hook'."
(run-hooks 'ein:notebooklist-after-open-hook))
(defun ein:testing-delete-notebook (url-or-port notebook &optional path)
(ein:log 'debug "TESTING-DELETE-NOTEBOOK start")
(ein:notebooklist-open url-or-port (ein:$notebook-notebook-path notebook) t)
(ein:testing-wait-until "ein:notebooklist-open"
(lambda ()
(bufferp (get-buffer (format ein:notebooklist-buffer-name-template url-or-port))))
nil 50)
(ein:testing-wait-until (lambda ()
(bufferp (get-buffer (format ein:notebooklist-buffer-name-template url-or-port)))))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(ein:testing-wait-until "ein:notebooklist-get-buffer"
(lambda () (eql major-mode 'ein:notebooklist-mode))
nil
50)
(ein:testing-wait-until (lambda () (eql major-mode 'ein:notebooklist-mode)))
(ein:log 'debug "TESTING-DELETE-NOTEBOOK deleting notebook")
(ein:notebooklist-delete-notebook (ein:$notebook-notebook-path notebook)))
(ein:log 'debug "TESTING-DELETE-NOTEBOOK end"))
@ -103,29 +80,24 @@ Make MAX-COUNT larger \(default 50) to wait longer before timeout."
(ein:log 'verbose "ERT OPEN-NOTEBOOKLIST start")
(ein:notebooklist-open *ein:testing-port* "/" t)
(ein:testing-wait-until
"ein:notebooklist-open"
(lambda ()
(ein:notebooklist-get-buffer *ein:testing-port*))
nil 5000)
(ein:notebooklist-get-buffer *ein:testing-port*)))
(with-current-buffer (ein:notebooklist-get-buffer *ein:testing-port*)
(should (eql major-mode 'ein:notebooklist-mode))))
(ert-deftest 00-query-kernelspecs ()
(ein:log 'info "ERT QUERY-KERNELSPECS")
(ein:log 'info (format "ERT QUERY-KERNELSPECS: Pre-query kernelspec count %s." (hash-table-count ein:available-kernelspecs)))
(ein:query-kernelspecs *ein:testing-port*)
(should (>= (hash-table-count ein:available-kernelspecs) 1))
(ein:log 'info (format "ERT QUERY-KERNELSPECS: Post-query kernelspec %S." (ein:list-available-kernels *ein:testing-port*))))
(ein:log 'info (format "ERT QUERY-KERNELSPECS: Pre-query kernelspec count %s." (hash-table-count *ein:kernelspecs*)))
(should (>= (hash-table-count *ein:kernelspecs*) 1))
(ein:log 'info (format "ERT QUERY-KERNELSPECS: Post-query kernelspec %S." (ein:need-kernelspecs *ein:testing-port*))))
(ert-deftest 10-get-untitled0-or-create ()
(ein:log 'verbose "ERT TESTING-GET-UNTITLED0-OR-CREATE start")
(let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it)))
nil)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(should (equal (ein:$notebook-notebook-name ein:%notebook%)
"Untitled.ipynb"))))
@ -137,12 +109,10 @@ Make MAX-COUNT larger \(default 50) to wait longer before timeout."
(ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 creating notebook")
(let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
(ein:testing-wait-until
"ein:test-get-untitled0-or-create"
(lambda ()
(ein:aand notebook
(ein:$notebook-kernel it)
(ein:kernel-live-p it)))
nil 50)
(ein:kernel-live-p it))))
(ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 deleting notebook")
(ein:testing-delete-notebook *ein:testing-port* notebook))
(ein:log 'verbose
@ -157,17 +127,13 @@ Make MAX-COUNT larger \(default 50) to wait longer before timeout."
(ert-deftest 11-notebook-execute-current-cell-simple ()
(let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it)))
nil 50)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(call-interactively #'ein:worksheet-insert-cell-below)
(insert "a = 100\na")
(let ((cell (call-interactively #'ein:worksheet-execute-cell)))
(ein:testing-wait-until "ein:worksheet-execute-cell"
(lambda () (not (slot-value cell 'running)))
nil))
(ein:testing-wait-until (lambda () (not (slot-value cell 'running)))))
;; (message "%s" (buffer-string))
(save-excursion
(should (search-forward-regexp "Out \\[[0-9]+\\]" nil t))
@ -183,7 +149,6 @@ See the definition of `create-image' for how it works."
(ert-deftest 12-notebook-execute-current-cell-pyout-image ()
(let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
@ -197,10 +162,7 @@ See the definition of `create-image' for how it works."
;; It seems in this case, watching `:running' does not work
;; well sometimes. Probably "output reply" (iopub) comes
;; before "execute reply" in this case.
(ein:testing-wait-until "ein:worksheet-execute-cell"
(lambda () (slot-value cell 'outputs))
nil
50)
(ein:testing-wait-until (lambda () (slot-value cell 'outputs)))
;; This cell has only one input
(should (= (length (oref cell :outputs)) 1))
;; This output is a SVG image
@ -220,17 +182,14 @@ See the definition of `create-image' for how it works."
(ert-deftest 13-notebook-execute-current-cell-stream ()
(let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it)))
nil 50)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(call-interactively #'ein:worksheet-insert-cell-below)
(insert "print('Hello')")
(let ((cell (call-interactively #'ein:worksheet-execute-cell)))
(ein:testing-wait-until "ein:worksheet-execute-cell"
(lambda () (not (oref cell :running)))
nil))
(ein:testing-wait-until (lambda () (not (oref cell :running)))
))
(save-excursion
(should-not (search-forward-regexp "Out \\[[0-9]+\\]" nil t))
(should (search-forward-regexp "^Hello$" nil t))))))
@ -238,28 +197,23 @@ See the definition of `create-image' for how it works."
(ert-deftest 14-notebook-execute-current-cell-question ()
(let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it)))
nil 50)
)
(with-current-buffer (ein:notebook-buffer notebook)
(call-interactively #'ein:worksheet-insert-cell-below)
(insert "range?")
(let ((cell (call-interactively #'ein:worksheet-execute-cell)))
(ein:testing-wait-until
"ein:worksheet-execute-cell"
(lambda () (not (oref cell :running)))
nil 50))
(lambda () (not (oref cell :running)))))
(with-current-buffer (get-buffer (ein:$notebook-pager notebook))
(should (search-forward "Docstring:"))))))
(ert-deftest 15-notebook-request-help ()
(let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it)))
nil)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(call-interactively #'ein:worksheet-insert-cell-below)
(let ((pager-name (ein:$notebook-pager ein:%notebook%)))
@ -269,9 +223,7 @@ See the definition of `create-image' for how it works."
(call-interactively #'ein:pytools-request-help)
;; Pager buffer will be created when got the response
(ein:testing-wait-until
"ein:pythools-request-help"
(lambda () (get-buffer pager-name))
nil)
(lambda () (get-buffer pager-name)))
(with-current-buffer (get-buffer pager-name)
(should (search-forward "Docstring:")))))))
@ -280,12 +232,10 @@ See the definition of `create-image' for how it works."
(let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it)))
nil)
(ein:kernel-live-p it))))
(cl-letf (((symbol-function 'y-or-n-p) (lambda (prompt) t)))
(ein:jupyter-server-stop t ein:testing-dump-server-log))
(ein:jupyter-server-stop t ein:testing-dump-file-server))
(should-not (processp %ein:jupyter-server-session%))
(cl-flet ((orphans-find (pid) (search (ein:$kernel-kernel-id (ein:$notebook-kernel notebook)) (alist-get 'args (process-attributes pid)))))
(should-not (loop repeat 10

View file

@ -3,6 +3,7 @@
(require 'ein-dev)
(require 'ein-testing)
(require 'ein-jupyter)
(require 'ein-notebooklist)
(require 'deferred)
(ein:log 'info "Starting jupyter notebook server.")
@ -17,20 +18,21 @@
(defvar *ein:testing-port* nil)
(defvar *ein:testing-token* nil)
(ein:setq-if-not ein:testing-dump-file-log "./log/testfunc.log")
(ein:setq-if-not ein:testing-dump-file-messages "./log/testfunc.messages")
(ein:setq-if-not ein:testing-dump-server-log "./log/testfunc.server")
(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-request (concat default-directory "log/testfunc.request"))
(setq message-log-max t)
(setq ein:force-sync t)
(setq ein:jupyter-server-run-timeout 120000)
(setq ein:content-query-timeout nil)
(setq ein:query-timeout nil)
(ein:log 'info "Staring local jupyter notebook server.")
(setq ein:jupyter-server-args '("--no-browser" "--debug"))
(ein:dev-start-debug)
(deferred:sync! (ein:jupyter-server-start *ein:testing-jupyter-server-command* *ein:testing-jupyter-server-directory*))
;; (ein:testing-wait-until (lambda () (not (null (ein:notebooklist-list))))
;; nil 120000 5000)
(multiple-value-bind (url token) (ein:jupyter-server-conn-info)
(ein:log 'info (format "testing-start-server url: %s, token: %s" url token))
(setq *ein:testing-port* url)

View file

@ -145,7 +145,7 @@ class TestRunner(BaseRunner):
self.notebook_dir = os.path.join(EIN_ROOT, "test")
self.lispvars = {
'ein:testing-dump-file-log': quote(self.logpath_log),
'ein:testing-dump-server-log': quote(self.logpath_server),
'ein:testing-dump-file-server': quote(self.logpath_server),
'ein:testing-dump-file-messages': quote(self.logpath_messages),
'ein:log-level': self.ein_log_level,
'ein:force-sync': "t",