mirror of
https://github.com/vale981/emacs-ipython-notebook
synced 2025-03-06 01:21:38 -05:00
commit
b9f8854949
28 changed files with 716 additions and 646 deletions
|
@ -51,6 +51,8 @@ install:
|
|||
pyenv activate $TOXENV ;
|
||||
fi
|
||||
- pip install jupyter ipython\<=$IPYTHON
|
||||
- pip install numpy
|
||||
- pip install matplotlib
|
||||
- |
|
||||
if [[ "x$TRAVIS_PYTHON_VERSION" == x3* ]]; then
|
||||
pip install jupyterhub ;
|
||||
|
@ -77,7 +79,7 @@ before_script:
|
|||
script:
|
||||
- make test-install
|
||||
- |
|
||||
if [ "x$TRAVIS_OS_NAME" = "xlinux" ]; then
|
||||
if [[ "x$TRAVIS_PYTHON_VERSION" == x3* ]]; then
|
||||
make test-jupyterhub
|
||||
fi
|
||||
- make test || ( ( zip -q - log/{testein,testfunc,ecukes}.* 2>/dev/null | uuencode log.zip ) && ( printf "To diagnose, travis logs -i | dos2unix | sed '/^begin 664/,/^end/!d' | uudecode" ) && false)
|
||||
|
|
|
@ -390,14 +390,13 @@ Org-mode integration
|
|||
--------------------
|
||||
|
||||
You can execute org source blocks in EIN by adding `ein` to
|
||||
`org:babel-load-languages`. You need to specify a notebook via the `:session`
|
||||
argument. The format for the session argument is
|
||||
`{url-or-port}/{path-to-notebook}`. You should also specify `:results raw drawer`
|
||||
`org:babel-load-languages`. The format for the `:session` header argument is
|
||||
`{url-or-port}/{path-to-notebook}`. Just specifying `{url-or-port}` executes your source block in a single anonymous notebook (this effects an ipython repl in org). You should also specify `:results raw drawer`
|
||||
for proper rendering inside the org buffer. For example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
#+BEGIN_SRC ein :session 8888/Untitled.ipynb :results raw drawer
|
||||
#+BEGIN_SRC ein :session localhost :results raw drawer
|
||||
import sys
|
||||
|
||||
a = 14500
|
||||
|
@ -405,8 +404,6 @@ for proper rendering inside the org buffer. For example:
|
|||
sys.version
|
||||
#+END_SRC
|
||||
|
||||
By default EIN will execute asynchronously so you can continue to work in Emacs, but you may control this behavior via the :el:symbol:`ein-org-async-p`.
|
||||
|
||||
If your code block generates an image, like from an matplotlib plot, ein will
|
||||
automatically save to a file in the directory specified by
|
||||
:el:symbol:`ein:org-inline-image-directory` and generate an appropriate inline
|
||||
|
@ -415,7 +412,7 @@ argument as in the example below:
|
|||
|
||||
.. code:: python
|
||||
|
||||
#BEGIN_SRC ein :session 8888/Untitled.ipynb :results raw drawer :image output.png
|
||||
#BEGIN_SRC ein :session localhost :results raw drawer :image output.png
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
|
@ -437,7 +434,7 @@ Then org SRC blocks with language "ein-R" will use R syntax highlighting:
|
|||
|
||||
.. code:: python
|
||||
|
||||
#BEGIN_SRC ein-R :session 8888/Untitled.ipynb :results raw drawer :image output.png
|
||||
#BEGIN_SRC ein-R :session localhost :results raw drawer :image output.png
|
||||
plot(1:10, 1:10)
|
||||
#+END_SRC
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
@autosave
|
||||
Scenario: try autosaving
|
||||
Given new default notebook
|
||||
And I call "ein:notebook-enable-autosaves"
|
||||
Then I should see message "ein:notebook-autosave-frequency is 0"
|
||||
|
||||
@eldoc
|
||||
Scenario: not running server locally
|
||||
Given I enable "ein:enable-eldoc-support"
|
||||
|
|
|
@ -38,11 +38,11 @@ Scenario: Stop after closing notebook
|
|||
|
||||
@content
|
||||
Scenario: Read a massive directory
|
||||
Given I create a directory "/var/tmp/fg7Cv8" with depth 5 and width 10
|
||||
Given I create a directory "/var/tmp/fg7Cv8" with depth 4 and width 8
|
||||
And I get into notebook mode "/var/tmp/fg7Cv8" "8/4/3/bar.ipynb"
|
||||
And I open notebook "bar.ipynb"
|
||||
And I open file "foo.txt"
|
||||
And notebooklist-list-paths does not contain "5/5/5/foo.txt"
|
||||
And notebooklist-list-paths does not contain "4/4/4/foo.txt"
|
||||
And notebooklist-list-paths contains "foo.txt"
|
||||
|
||||
@login
|
||||
|
@ -97,8 +97,7 @@ Scenario: Bad curl invocation produces sensible error message
|
|||
|
||||
@login
|
||||
Scenario: With token server
|
||||
Given I start the server configured "\n"
|
||||
And I login if necessary
|
||||
Given I start and login to the server configured "\n"
|
||||
And I switch to log expr "ein:log-all-buffer-name"
|
||||
Then I should not see "[warn]"
|
||||
And I should not see "[error]"
|
||||
|
|
90
features/ob-ein.feature
Normal file
90
features/ob-ein.feature
Normal file
|
@ -0,0 +1,90 @@
|
|||
@org
|
||||
Scenario: Specific port, portless localhost refers to same, concurrent execution
|
||||
Given I stop the server
|
||||
When I open temp file "ecukes.org"
|
||||
And I call "org-mode"
|
||||
And I type "<s"
|
||||
And I press "TAB"
|
||||
And I type session port 8317
|
||||
And I press "RET"
|
||||
And I type "(1 + 5 ** 0.5) / 2"
|
||||
And I dump buffer
|
||||
And I ctrl-c-ctrl-c
|
||||
And I wait for buffer to say "1.618"
|
||||
And I press "M->"
|
||||
And I type "<s"
|
||||
And I press "TAB"
|
||||
And I type "ein :session localhost :results raw drawer"
|
||||
And I press "RET"
|
||||
And I type "import math ; 4 * math.atan(1.0)"
|
||||
And I dump buffer
|
||||
And I clear log expr "ein:log-all-buffer-name"
|
||||
And I ctrl-c-ctrl-c
|
||||
And I wait for buffer to say "3.14159"
|
||||
And I switch to log expr "ein:log-all-buffer-name"
|
||||
Then I should not see "Login to"
|
||||
And I switch to buffer like "ecukes.org"
|
||||
And I clear the buffer
|
||||
And I type "<s"
|
||||
And I press "TAB"
|
||||
And I type "ein :session localhost :results raw drawer"
|
||||
And I press "RET"
|
||||
And I type "(1 + 5 ** 0.5) / 2"
|
||||
And I ctrl-c-ctrl-c
|
||||
And I press "M->"
|
||||
And I type "<s"
|
||||
And I press "TAB"
|
||||
And I type "ein :session localhost :results raw drawer"
|
||||
And I press "RET"
|
||||
And I type "import math ; 4 * math.atan(1.0)"
|
||||
And I ctrl-c-ctrl-c
|
||||
And I dump buffer
|
||||
And I wait for buffer to say "1.618"
|
||||
And I dump buffer
|
||||
And I wait for buffer to say "3.1415"
|
||||
|
||||
@org
|
||||
Scenario: portless url with path, image, C-c ' lets you C-c C-c as well
|
||||
Given I stop the server
|
||||
When I open temp file "path.org"
|
||||
And I call "org-mode"
|
||||
And I type "<s"
|
||||
And I press "TAB"
|
||||
And I type "ein :session localhost/undo.ipynb :results raw drawer"
|
||||
And I press "RET"
|
||||
And I type "(1 + 5 ** 0.5) / 2"
|
||||
And I ctrl-c-ctrl-c
|
||||
And I wait for buffer to say "1.618"
|
||||
And I press "M->"
|
||||
And I type "<s"
|
||||
And I press "TAB"
|
||||
And I type "ein :session localhost :results raw drawer"
|
||||
And I press "RET"
|
||||
And I insert percent sign
|
||||
And I type "matplotlib inline"
|
||||
And I press "RET"
|
||||
And I type "import matplotlib.pyplot as plt ; import numpy as np ; x = np.linspace(0, 1, 100) ; y = np.random.rand(100,1) ; plt.plot(x,y)"
|
||||
And I ctrl-c-ctrl-c
|
||||
And I press "M->"
|
||||
And I type "<s"
|
||||
And I press "TAB"
|
||||
And I type "ein :session localhost :results raw drawer"
|
||||
And I press "RET"
|
||||
And I insert percent sign
|
||||
And I type "matplotlib inline"
|
||||
And I press "RET"
|
||||
And I type "import matplotlib.pyplot as plt ; import numpy as np ; x = np.linspace(0, 1, 100) ; y = np.random.rand(100,1) ; plt.plot(x,y)"
|
||||
And I ctrl-c-ctrl-c
|
||||
And I dump buffer
|
||||
And I wait for buffer to say "file:ein-image"
|
||||
And I press "C-c '"
|
||||
And I switch to buffer like "Org Src"
|
||||
And I press "C-a"
|
||||
And I press "C-k"
|
||||
And I type "import math ; math.e"
|
||||
And I dump buffer
|
||||
And I press "C-c C-c"
|
||||
And I press "C-c C-k"
|
||||
And I switch to buffer like "path.org"
|
||||
And I dump buffer
|
||||
And I wait for buffer to say "2.718"
|
|
@ -1,3 +1,19 @@
|
|||
(When "^I insert percent sign$" ;; https://github.com/ecukes/ecukes/issues/58
|
||||
(lambda ()
|
||||
(insert-char 37)))
|
||||
|
||||
(When "^I type session port \\([0-9]+\\)$"
|
||||
(lambda (port)
|
||||
(ein:process-refresh-processes)
|
||||
(assert (not (ein:process-url-match (ein:url port))))
|
||||
(When (format "I type \"ein :session localhost:%s :result raw drawer\"" port))))
|
||||
|
||||
(When "^I ctrl-c-ctrl-c$"
|
||||
(lambda ()
|
||||
(cl-letf (((symbol-function 'read-directory-name)
|
||||
(lambda (&rest args) ein:testing-jupyter-server-root)))
|
||||
(When "I press \"C-c C-c\""))))
|
||||
|
||||
(When "^with no opened notebooks call \"\\(.+\\)\"$"
|
||||
(lambda (func)
|
||||
(cl-letf (((symbol-function 'ein:notebook-opened-buffer-names) #'ignore))
|
||||
|
@ -103,9 +119,9 @@
|
|||
(When "^new \\(.+\\) notebook$"
|
||||
(lambda (kernel)
|
||||
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)
|
||||
(lexical-let (notebook)
|
||||
(let (notebook)
|
||||
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
|
||||
(lexical-let ((ks (ein:get-kernelspec url-or-port kernel)))
|
||||
(let ((ks (ein:get-kernelspec url-or-port kernel)))
|
||||
(setq notebook (ein:testing-new-notebook url-or-port ks))))
|
||||
(let ((buf-name (format ein:notebook-buffer-name-template
|
||||
(ein:$notebook-url-or-port notebook)
|
||||
|
@ -114,7 +130,7 @@
|
|||
(Then "I should be in buffer \"%s\"" buf-name))))))
|
||||
|
||||
(When "^I \\(finally \\)?stop the server\\(\\)$"
|
||||
(lambda (final-p &rest args)
|
||||
(lambda (final-p _workaround)
|
||||
(cancel-function-timers #'ein:notebooklist-reload)
|
||||
(cl-letf (((symbol-function 'y-or-n-p) #'ignore))
|
||||
(ein:jupyter-server-stop t))
|
||||
|
@ -123,7 +139,12 @@
|
|||
until (null (get-buffer-process buffer))
|
||||
do (sleep-for 0 1000)
|
||||
finally do (ein:aif (get-buffer-process buffer) (delete-process it)))
|
||||
(clrhash ein:notebooklist-map)
|
||||
(condition-case err
|
||||
(ein:testing-wait-until (lambda ()
|
||||
(null (ein:notebooklist-keys)))
|
||||
nil 10000 1000)
|
||||
(error (ein:log 'warn "Stopping server: orphaned %s" (ein:notebooklist-keys))
|
||||
(clrhash ein:notebooklist-map)))
|
||||
(unless final-p
|
||||
(When "I clear log expr \"ein:log-all-buffer-name\"")
|
||||
(When "I clear log expr \"ein:jupyter-server-buffer-name\""))))
|
||||
|
@ -267,6 +288,12 @@
|
|||
(When "^I dump buffer"
|
||||
(lambda () (message "%s" (buffer-string))))
|
||||
|
||||
(When "^I wait for buffer to say \"\\(.+\\)\"$"
|
||||
(lambda (bogey)
|
||||
(ein:testing-wait-until
|
||||
(lambda () (s-contains? bogey (buffer-string)))
|
||||
nil 40000 2000)))
|
||||
|
||||
(When "^I wait for cell to execute$"
|
||||
(lambda ()
|
||||
(let* ((cell (ein:worksheet-get-current-cell :cell-p #'ein:codecell-p))
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
(require 'ein-testing)
|
||||
(require 'ein-ipynb-mode)
|
||||
(require 'ein-contents-api)
|
||||
(require 'ob-ein)
|
||||
|
||||
(if (member "timestamp" ecukes-include-tags)
|
||||
(require 'ein-timestamp)
|
||||
|
@ -25,6 +26,9 @@
|
|||
(if (eq system-type 'darwin)
|
||||
(!cons "switch" ecukes-exclude-tags))
|
||||
|
||||
(if (> (string-to-number org-version) 9.1) ;; they got rid of easy templates
|
||||
(!cons "org" ecukes-exclude-tags))
|
||||
|
||||
(defvar ein:testing-jupyter-server-root (f-parent (f-dirname load-file-name)))
|
||||
|
||||
(defun ein:testing-after-scenario ()
|
||||
|
@ -56,14 +60,15 @@
|
|||
|
||||
(Setup
|
||||
(ein:dev-start-debug)
|
||||
(setq ein:jupyter-server-args '("--no-browser" "--debug"))
|
||||
(setq ein:notebook-autosave-frequency 0)
|
||||
(setq ein:notebook-create-checkpoint-on-save nil)
|
||||
(setq ein:testing-dump-file-log (concat default-directory "log/ecukes.log"))
|
||||
(setq ein:testing-dump-file-messages (concat default-directory "log/ecukes.messages"))
|
||||
(setq ein:testing-dump-file-server (concat default-directory "log/ecukes.server"))
|
||||
(setq ein:testing-dump-file-request (concat default-directory "log/ecukes.request"))
|
||||
(Given "I start and login to the server configured \"\\n\"")
|
||||
)
|
||||
(setq org-confirm-babel-evaluate nil)
|
||||
(Given "I start and login to the server configured \"\\n\""))
|
||||
|
||||
(After
|
||||
(ein:testing-after-scenario))
|
||||
|
|
|
@ -170,7 +170,6 @@ Scenario: Split and merge don't break undo
|
|||
|
||||
@reopened
|
||||
Scenario: Undo needs to at least work for reopened notebooks
|
||||
Given I start the server configured "\n"
|
||||
Given I enable "ein:worksheet-enable-undo"
|
||||
Given old notebook "undo.ipynb"
|
||||
And I type "howdy"
|
||||
|
|
|
@ -199,8 +199,8 @@ a number will limit the number of lines in a cell output."
|
|||
(let* ((img (apply #'create-image args)))
|
||||
(if ein:slice-image
|
||||
(destructuring-bind (&optional rows cols)
|
||||
(when (listp ein:slice-image) ein:slice-image)
|
||||
(insert-sliced-image img "." nil (or rows 20) cols))
|
||||
(when (listp ein:slice-image) ein:slice-image)
|
||||
(insert-sliced-image img "." nil (or rows 20) cols))
|
||||
(insert-image img ".")))
|
||||
(error (ein:log 'warn "Could not insert image: %s" err) nil)))
|
||||
|
||||
|
@ -520,20 +520,16 @@ Return language name as a string or `nil' when not defined.
|
|||
(cl-defmethod ein:cell-insert-input ((cell ein:basecell))
|
||||
"Insert input of the CELL in the buffer.
|
||||
Called from ewoc pretty printer via `ein:cell-pp'."
|
||||
(let ((start (1+ (point))) pos-newline)
|
||||
(let ((start (1+ (point))))
|
||||
;; Newlines must allow insertion before/after its position.
|
||||
(insert (propertize "\n" 'read-only t 'rear-nonsticky t))
|
||||
(setq pos-newline (1- (point)))
|
||||
(insert (or (ein:oref-safe cell 'input) "")
|
||||
(propertize "\n" 'read-only t))
|
||||
;; Highlight background using overlay.
|
||||
(let ((ol (make-overlay start (point))))
|
||||
(overlay-put ol 'face (ein:cell-get-input-area-face cell))
|
||||
;; `evaporate' = `t': Overlay is deleted when the region become empty.
|
||||
(overlay-put ol 'evaporate t))
|
||||
(unless (get-text-property pos-newline 'rear-nonsticky)
|
||||
(put-text-property pos-newline (1+ pos-newline) 'rear-nonsticky t)
|
||||
(ein:log 'debug "ein:cell-insert-input: missing rear-nonsticky at %s" pos-newline))))
|
||||
(overlay-put ol 'evaporate t))))
|
||||
|
||||
(cl-defmethod ein:cell-get-input-area-face ((cell ein:basecell))
|
||||
"Return the face (symbol) for input area."
|
||||
|
@ -611,7 +607,6 @@ Return language name as a string or `nil' when not defined.
|
|||
(when (equal (plist-get last-out :output_type) "stream")
|
||||
(ein:cell-append-stream-text-fontified "\n" last-out)))))
|
||||
|
||||
|
||||
(defun ein:cell-node-p (node &optional element-name)
|
||||
(let* ((path (ein:$node-path node))
|
||||
(p0 (car path))
|
||||
|
@ -1248,17 +1243,15 @@ prettified text thus be used instead of HTML type."
|
|||
(when (or (equal msg-type "pyout")
|
||||
(equal msg-type "execute_result"))
|
||||
(plist-put json :prompt_number (plist-get content :execution_count)))
|
||||
(setq json (ein:output-area-convert-mime-types
|
||||
json (plist-get content :data)))
|
||||
)
|
||||
(setq json
|
||||
(ein:output-area-convert-mime-types json (plist-get content :data))))
|
||||
(("pyerr" "error")
|
||||
(plist-put json :ename (plist-get content :ename))
|
||||
(plist-put json :evalue (plist-get content :evalue))
|
||||
(plist-put json :traceback (plist-get content :traceback))))
|
||||
(ein:cell-append-output cell json t)
|
||||
;; (setf (slot-value cell 'dirty) t)
|
||||
(ein:events-trigger (slot-value cell 'events) 'maybe_reset_undo.Worksheet cell)
|
||||
))
|
||||
(ein:events-trigger (slot-value cell 'events) 'maybe_reset_undo.Worksheet cell)))
|
||||
|
||||
|
||||
(defun ein:output-area-convert-mime-types (json data)
|
||||
|
@ -1305,9 +1298,8 @@ prettified text thus be used instead of HTML type."
|
|||
|
||||
(cl-defmethod ein:cell-get-tb-data ((cell ein:codecell))
|
||||
(loop for out in (slot-value cell 'outputs)
|
||||
when (and
|
||||
(not (null (plist-get out :traceback)))
|
||||
(member (plist-get out :output_type) '("pyerr" "error")))
|
||||
when (and (plist-get out :traceback)
|
||||
(member (plist-get out :output_type) '("pyerr" "error")))
|
||||
return (plist-get out :traceback)))
|
||||
|
||||
(provide 'ein-cell)
|
||||
|
|
|
@ -152,6 +152,9 @@
|
|||
`ein:$notebook-checkpoints'
|
||||
Names auto-saved checkpoints for content. Stored as a list
|
||||
of (<id> . <last_modified>) pairs.
|
||||
|
||||
`ein:$notebook-q-checkpoints'
|
||||
Whether to checkpoint on save. Overrides ein:notebook-create-checkpoint-on-save
|
||||
"
|
||||
url-or-port
|
||||
notebook-id ;; In IPython-2.0 this is "[:path]/[:name].ipynb"
|
||||
|
@ -170,7 +173,8 @@
|
|||
scratchsheets
|
||||
api-version
|
||||
autosave-timer
|
||||
checkpoints)
|
||||
checkpoints
|
||||
q-checkpoints)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ inside the ``if __name__ == \"__main__\":`` block."
|
|||
(deferred:$
|
||||
(deferred:next
|
||||
(lambda ()
|
||||
(ein:shared-output-eval-string (buffer-string) nil nil nil :silent t)))
|
||||
(ein:shared-output-eval-string (ein:connect-get-kernel) (buffer-string) nil :silent t)))
|
||||
(deferred:nextc it
|
||||
(lambda ()
|
||||
(ein:connect-execute-autoexec-cells))))
|
||||
|
@ -239,7 +239,7 @@ Variable `ein:connect-run-command' sets the default command."
|
|||
(cmd (format "%s \"%s\"" command it)))
|
||||
(if (ein:maybe-save-buffer ein:connect-save-before-run)
|
||||
(progn
|
||||
(ein:shared-output-eval-string cmd nil nil nil :silent t)
|
||||
(ein:shared-output-eval-string (ein:connect-get-kernel) cmd nil :silent t)
|
||||
(ein:connect-execute-autoexec-cells)
|
||||
(ein:log 'info "Command sent to the kernel: %s" cmd))
|
||||
(ein:log 'info "Buffer must be saved before %%run.")))
|
||||
|
@ -265,7 +265,7 @@ See also: `ein:connect-run-buffer', `ein:connect-eval-buffer'."
|
|||
|
||||
(defun ein:connect-eval-region (start end)
|
||||
(interactive "r")
|
||||
(ein:shared-output-eval-string (buffer-substring start end))
|
||||
(ein:shared-output-eval-string (ein:connect-get-kernel) (buffer-substring start end) nil)
|
||||
(ein:log 'info "Selected region is sent to the kernel."))
|
||||
|
||||
(define-obsolete-function-alias
|
||||
|
|
|
@ -90,8 +90,7 @@ global setting. For global setting and more information, see
|
|||
: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 callback errback iteration)
|
||||
))
|
||||
:error (apply-partially #'ein:content-query-contents--error url-or-port path callback errback iteration)))
|
||||
|
||||
(defun* ein:content-query-contents--complete (url-or-port path
|
||||
&key data symbol-status response
|
||||
|
@ -99,14 +98,28 @@ global setting. For global setting and more information, see
|
|||
&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 callback errback iteration &key symbol-status response error-thrown &allow-other-keys)
|
||||
(if (< iteration (if noninteractive 6 3))
|
||||
(progn
|
||||
(ein:log 'verbose "Retry content-query-contents #%s in response to %s" iteration (request-response-status-code response))
|
||||
(sleep-for 0 (* (1+ iteration) 500))
|
||||
(ein:content-query-contents url-or-port path callback errback (1+ iteration)))
|
||||
(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))
|
||||
(when errback (funcall errback nil))))
|
||||
(defun* ein:content-query-contents--error (url-or-port path callback errback iteration &key symbol-status response error-thrown data &allow-other-keys)
|
||||
(let ((status-code (request-response-status-code response)))
|
||||
(case status-code
|
||||
(404 (ein:log 'error "ein:content-query-contents--error %s %s"
|
||||
(request-response-status-code response) (plist-get data :message))
|
||||
(when errback (funcall errback url-or-port status-code)))
|
||||
(t (if (< iteration (if noninteractive 6 3))
|
||||
(progn
|
||||
(ein:log 'verbose "Retry content-query-contents #%s in response to %s" iteration status-code)
|
||||
(sleep-for 0 (* (1+ iteration) 500))
|
||||
(ein:content-query-contents url-or-port path callback errback (1+ iteration)))
|
||||
(let ((notice
|
||||
(format "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))))
|
||||
(if (and (= status-code 403) noninteractive)
|
||||
(progn
|
||||
(ein:log 'info notice)
|
||||
(when callback
|
||||
(funcall callback (ein:new-content url-or-port path data))))
|
||||
(ein:log 'error notice)
|
||||
(when errback (funcall errback url-or-port status-code)))))))))
|
||||
|
||||
|
||||
;; TODO: This is one place to check for redirects - update the url slot if so.
|
||||
|
@ -296,11 +309,10 @@ global setting. For global setting and more information, see
|
|||
(when callback
|
||||
(apply callback cbargs)))
|
||||
|
||||
(defun* ein:content-save-error (url errcb errcbargs &key response status-code &allow-other-keys)
|
||||
(defun* ein:content-save-error (url errcb errcbargs &key response data &allow-other-keys)
|
||||
(ein:log 'error
|
||||
"Error thrown: %S" (request-response-error-thrown response))
|
||||
(ein:log 'error
|
||||
"Content save call %s failed with status %s." url status-code)
|
||||
"Content save %s failed %s %s."
|
||||
url (request-response-error-thrown response) (plist-get data :message))
|
||||
(when errcb
|
||||
(apply errcb errcbargs)))
|
||||
|
||||
|
@ -348,11 +360,10 @@ global setting. For global setting and more information, see
|
|||
(when callback
|
||||
(apply callback cbargs)))
|
||||
|
||||
(defun* ein:content-rename-error (path &key symbol-status response &allow-other-keys)
|
||||
(ein:log 'verbose
|
||||
"Error thrown: %S" (request-response-error-thrown response))
|
||||
(defun* ein:content-rename-error (path &key response data &allow-other-keys)
|
||||
(ein:log 'error
|
||||
"Renaming content %s failed with status %s." path symbol-status))
|
||||
"Renaming content %s failed %s %s."
|
||||
path (request-response-error-thrown response) (plist-get data :message)))
|
||||
|
||||
|
||||
;;; Sessions
|
||||
|
|
|
@ -153,6 +153,16 @@ the source is in git repository) or elpa version."
|
|||
(defvar *ein:kernelspecs* (make-hash-table :test #'equal)
|
||||
"url-or-port to kernelspecs")
|
||||
|
||||
(defun ein:get-kernelspec (url-or-port name)
|
||||
(let* ((kernelspecs (ein:need-kernelspecs url-or-port))
|
||||
(name (if (stringp name)
|
||||
(intern (format ":%s" name))
|
||||
name))
|
||||
(ks (plist-get kernelspecs name)))
|
||||
(if (stringp ks)
|
||||
(ein:get-kernelspec url-or-port ks)
|
||||
ks)))
|
||||
|
||||
(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
|
||||
|
@ -381,7 +391,6 @@ but can operate in different contexts."
|
|||
(ein:join-str " " (mapcar #'file-name-nondirectory it))))
|
||||
(message "Compiled %s files" (length files))))
|
||||
|
||||
|
||||
(provide 'ein-core)
|
||||
|
||||
;;; ein-core.el ends here
|
||||
|
|
|
@ -63,22 +63,23 @@ the notebook directory, you can set it here for future calls to
|
|||
|
||||
(defun ein:jupyter-server--run (buf cmd dir &optional args)
|
||||
(let* ((vargs (append (if dir
|
||||
`("notebook" ,(format "--notebook-dir=%s"
|
||||
(convert-standard-filename dir))))
|
||||
(or args ein:jupyter-server-args)))
|
||||
`("notebook"
|
||||
,(format "--notebook-dir=%s"
|
||||
(convert-standard-filename dir))))
|
||||
args
|
||||
ein:jupyter-server-args))
|
||||
(proc (apply #'start-process
|
||||
*ein:jupyter-server-process-name*
|
||||
buf
|
||||
cmd
|
||||
vargs)))
|
||||
*ein:jupyter-server-process-name* buf cmd vargs)))
|
||||
(ein:log 'info "ein:jupyter-server--run: %s %s" cmd (ein:join-str " " vargs))
|
||||
(set-process-query-on-exit-flag proc nil)
|
||||
proc))
|
||||
|
||||
(defun ein:jupyter-server-conn-info (&optional buffer)
|
||||
(defun ein:jupyter-server-conn-info (&optional buffer-name)
|
||||
"Return the url-or-port and password for BUFFER or the global session."
|
||||
(unless buffer
|
||||
(setq buffer (get-buffer ein:jupyter-server-buffer-name)))
|
||||
(let ((result '(nil nil)))
|
||||
(unless buffer-name
|
||||
(setq buffer-name ein:jupyter-server-buffer-name))
|
||||
(let ((buffer (get-buffer buffer-name))
|
||||
(result '(nil nil)))
|
||||
(if buffer
|
||||
(with-current-buffer buffer
|
||||
(save-excursion
|
||||
|
@ -121,7 +122,8 @@ our singleton jupyter server process here."
|
|||
url-or-port (process-sentinel proc))))
|
||||
|
||||
;;;###autoload
|
||||
(defun ein:jupyter-server-start (server-cmd-path notebook-directory &optional no-login-p login-callback)
|
||||
(defun ein:jupyter-server-start (server-cmd-path notebook-directory
|
||||
&optional no-login-p login-callback port)
|
||||
"Start SERVER-CMD_PATH with `--notebook-dir' NOTEBOOK-DIRECTORY. Login after connection established unless NO-LOGIN-P is set. LOGIN-CALLBACK takes two arguments, the buffer created by ein:notebooklist-open--finish, and the url-or-port argument of ein:notebooklist-open*.
|
||||
|
||||
This command opens an asynchronous process running the jupyter
|
||||
|
@ -159,12 +161,14 @@ the log of the running jupyter server."
|
|||
(setf *ein:last-jupyter-command* server-cmd-path
|
||||
*ein:last-jupyter-directory* notebook-directory)
|
||||
(if (ein:jupyter-server-process)
|
||||
(error "Please first M-x ein:jupyter-server-stop"))
|
||||
(error "Please first M-x ein:stop"))
|
||||
(add-hook 'kill-emacs-hook #'(lambda ()
|
||||
(ignore-errors (ein:jupyter-server-stop t))))
|
||||
(let ((proc (ein:jupyter-server--run ein:jupyter-server-buffer-name
|
||||
*ein:last-jupyter-command*
|
||||
*ein:last-jupyter-directory*)))
|
||||
*ein:last-jupyter-directory*
|
||||
(if (numberp port)
|
||||
`("--port" ,(format "%s" port))))))
|
||||
(when (eql system-type 'windows-nt)
|
||||
(accept-process-output proc (/ ein:jupyter-server-run-timeout 1000)))
|
||||
(loop repeat 30
|
||||
|
@ -191,25 +195,21 @@ the log of the running jupyter server."
|
|||
|
||||
;;;###autoload
|
||||
(defun ein:jupyter-server-stop (&optional force log)
|
||||
"Stop a running jupyter notebook server.
|
||||
|
||||
Use this command to stop a running jupyter notebook server. If
|
||||
there is no running server then no action will be taken.
|
||||
"
|
||||
(interactive)
|
||||
(when (and (ein:jupyter-server-process)
|
||||
(or force (y-or-n-p "Kill jupyter server and close all open notebooks?")))
|
||||
(ein:and-let* ((proc (ein:jupyter-server-process))
|
||||
(_ok (or force (y-or-n-p "Stop server and close notebooks?"))))
|
||||
(let ((unsaved (ein:notebook-opened-notebooks #'ein:notebook-modified-p))
|
||||
(check-for-saved (make-hash-table :test #'equal)))
|
||||
(when unsaved
|
||||
(loop for nb in unsaved
|
||||
when (y-or-n-p (format "Save notebook %s before stopping the server?" (ein:$notebook-notebook-name nb)))
|
||||
for nb-name = (ein:$notebook-notebook-name nb)
|
||||
when (y-or-n-p (format "Save %s?" nb-name))
|
||||
do (progn
|
||||
(setf (gethash (ein:$notebook-notebook-name nb) check-for-saved) t)
|
||||
(ein:notebook-save-notebook nb
|
||||
#'(lambda (name check-hash)
|
||||
(remhash name check-hash))
|
||||
(list (ein:$notebook-notebook-name nb) check-for-saved)))))
|
||||
(setf (gethash nb-name check-for-saved) t)
|
||||
(ein:notebook-save-notebook
|
||||
nb
|
||||
(lambda (name check-hash) (remhash name check-hash))
|
||||
(list nb-name check-for-saved)))))
|
||||
(loop for x upfrom 0 by 1
|
||||
until (or (zerop (hash-table-count check-for-saved))
|
||||
(> x 20))
|
||||
|
@ -224,14 +224,18 @@ there is no running server then no action will be taken.
|
|||
do (sleep-for 0 500))
|
||||
|
||||
;; Both (quit-process) and (delete-process) leaked child kernels, so signal
|
||||
(ein:aif (ein:jupyter-server-process)
|
||||
(progn
|
||||
(if (eql system-type 'windows-nt)
|
||||
(delete-process it)
|
||||
(let ((pid (process-id it)))
|
||||
(ein:log 'verbose "Signaled %s with pid %s" it pid)
|
||||
(signal-process pid 15)))
|
||||
(ein:log 'info "Stopped Jupyter notebook server.")))
|
||||
(if (eql system-type 'windows-nt)
|
||||
(delete-process proc)
|
||||
(let ((pid (process-id proc)))
|
||||
(ein:log 'verbose "Signaled %s with pid %s" proc pid)
|
||||
(signal-process pid 15)))
|
||||
|
||||
(ein:log 'info "Stopped Jupyter notebook server.")
|
||||
|
||||
;; `ein:notebooklist-sentinel' frequently does not trigger
|
||||
(multiple-value-bind (url-or-port _password) (ein:jupyter-server-conn-info)
|
||||
(ein:notebooklist-list-remove url-or-port))
|
||||
|
||||
(when log
|
||||
(with-current-buffer ein:jupyter-server-buffer-name
|
||||
(write-region (point-min) (point-max) log)))))
|
||||
|
|
|
@ -319,33 +319,39 @@ will be updated with kernel's cwd."
|
|||
((>= api-version 3)
|
||||
(ein:url url-or-port "api/contents" path))))
|
||||
|
||||
(defun ein:notebook-open--decorate-callback (notebook existing pending-clear callback)
|
||||
(defun ein:notebook-open--decorate-callback (notebook existing pending-clear callback no-pop)
|
||||
"In addition to CALLBACK, also clear the pending semaphore, pop-to-buffer the new notebook, save to disk the kernelspec metadata, and put last warning in minibuffer."
|
||||
(apply-partially (lambda (notebook* created callback* pending-clear*)
|
||||
(funcall pending-clear* nil)
|
||||
(with-current-buffer (ein:notebook-buffer notebook*)
|
||||
(ein:worksheet-focus-cell))
|
||||
(pop-to-buffer (ein:notebook-buffer notebook*))
|
||||
(when (null (plist-member (ein:$notebook-metadata notebook*)
|
||||
:kernelspec))
|
||||
(ein:aif (ein:$notebook-kernelspec notebook*)
|
||||
(progn
|
||||
(setf (ein:$notebook-metadata notebook*)
|
||||
(plist-put (ein:$notebook-metadata notebook*)
|
||||
:kernelspec (ein:kernelspec-for-nb-metadata it)))
|
||||
(ein:notebook-save-notebook notebook*))))
|
||||
(when callback*
|
||||
(funcall callback* notebook* created))
|
||||
(ein:and-let* ((created)
|
||||
(buffer (get-buffer "*Warnings*"))
|
||||
(last-warning (with-current-buffer buffer
|
||||
(thing-at-point 'line t))))
|
||||
(message "%s" last-warning)))
|
||||
notebook (not existing) callback pending-clear))
|
||||
(apply-partially
|
||||
(lambda (notebook* created callback* pending-clear* no-pop*)
|
||||
(funcall pending-clear*)
|
||||
(with-current-buffer (ein:notebook-buffer notebook*)
|
||||
(ein:worksheet-focus-cell))
|
||||
(unless no-pop*
|
||||
(pop-to-buffer (ein:notebook-buffer notebook*)))
|
||||
(when (null (plist-member (ein:$notebook-metadata notebook*)
|
||||
:kernelspec))
|
||||
(ein:aif (ein:$notebook-kernelspec notebook*)
|
||||
(progn
|
||||
(setf (ein:$notebook-metadata notebook*)
|
||||
(plist-put (ein:$notebook-metadata notebook*)
|
||||
:kernelspec (ein:kernelspec-for-nb-metadata it)))
|
||||
(ein:notebook-save-notebook notebook*))))
|
||||
(when callback*
|
||||
(funcall callback* notebook* created))
|
||||
(ein:and-let* ((created)
|
||||
(buffer (get-buffer "*Warnings*"))
|
||||
(last-warning (with-current-buffer buffer
|
||||
(thing-at-point 'line t))))
|
||||
(message "%s" last-warning)))
|
||||
notebook (not existing) callback pending-clear no-pop))
|
||||
|
||||
(defun ein:notebook-open-or-create (url-or-port path &optional kernelspec callback no-pop)
|
||||
"Same as `ein:notebook-open' but create PATH if not found."
|
||||
(let ((if-not-found (lambda (contents status-code) )))
|
||||
(ein:notebook-open url-or-port path kernelspec callback if-not-found no-pop)))
|
||||
|
||||
;;;###autoload
|
||||
|
||||
(defun ein:notebook-open (url-or-port path &optional kernelspec callback)
|
||||
(defun ein:notebook-open (url-or-port path &optional kernelspec callback errback no-pop)
|
||||
"Returns notebook at URL-OR-PORT/PATH.
|
||||
|
||||
Note that notebook sends for its contents and won't have them right away.
|
||||
|
@ -358,9 +364,10 @@ where `created' indicates a new notebook or an existing one.
|
|||
"
|
||||
(interactive
|
||||
(ein:notebooklist-parse-nbpath (ein:notebooklist-ask-path "notebook")))
|
||||
(unless errback (setq errback #'ignore))
|
||||
(let* ((pending-key (cons url-or-port path))
|
||||
(pending-p (gethash pending-key *ein:notebook--pending-query*))
|
||||
(pending-clear (apply-partially (lambda (pending-key* _contents)
|
||||
(pending-clear (apply-partially (lambda (pending-key* &rest args)
|
||||
(remhash pending-key*
|
||||
*ein:notebook--pending-query*))
|
||||
pending-key))
|
||||
|
@ -368,23 +375,25 @@ where `created' indicates a new notebook or an existing one.
|
|||
(notebook (ein:aif existing it
|
||||
(ein:notebook-new url-or-port path kernelspec)))
|
||||
(callback0 (ein:notebook-open--decorate-callback notebook existing pending-clear
|
||||
callback)))
|
||||
callback no-pop)))
|
||||
(if existing
|
||||
(progn
|
||||
(ein:log 'info "Notebook %s is already open"
|
||||
(ein:$notebook-notebook-name notebook))
|
||||
(funcall callback0))
|
||||
(when (or noninteractive
|
||||
(not pending-p)
|
||||
(y-or-n-p (format "Notebook %s pending open! Retry? " path)))
|
||||
(setf (gethash pending-key *ein:notebook--pending-query*) t)
|
||||
(ein:content-query-contents url-or-port path
|
||||
(apply-partially #'ein:notebook-open--callback
|
||||
notebook callback0)
|
||||
pending-clear)))
|
||||
(if (and pending-p noninteractive)
|
||||
(ein:log 'error "Notebook %s pending open!" path)
|
||||
(when (or (not pending-p)
|
||||
(y-or-n-p (format "Notebook %s pending open! Retry? " path)))
|
||||
(setf (gethash pending-key *ein:notebook--pending-query*) t)
|
||||
(add-function :before errback pending-clear)
|
||||
(ein:content-query-contents url-or-port path
|
||||
(apply-partially #'ein:notebook-open--callback
|
||||
notebook callback0 (not no-pop))
|
||||
errback))))
|
||||
notebook))
|
||||
|
||||
(defun ein:notebook-open--callback (notebook callback0 content)
|
||||
(defun ein:notebook-open--callback (notebook callback0 q-checkpoints content)
|
||||
(ein:log 'verbose "Opened notebook %s" (ein:$notebook-notebook-path notebook))
|
||||
(let ((notebook-path (ein:$notebook-notebook-path notebook)))
|
||||
(ein:gc-prepare-operation)
|
||||
|
@ -404,8 +413,8 @@ where `created' indicates a new notebook or an existing one.
|
|||
(symbol-name (ein:get-mode-for-kernel (ein:$notebook-kernelspec notebook)))))
|
||||
(ein:notebook-put-opened-notebook notebook)
|
||||
(ein:notebook--check-nbformat (ein:$content-raw-content content))
|
||||
(if (> ein:notebook-autosave-frequency 0)
|
||||
(ein:notebook-enable-autosaves notebook))
|
||||
(setf (ein:$notebook-q-checkpoints notebook) q-checkpoints)
|
||||
(ein:notebook-enable-autosaves notebook)
|
||||
(ein:gc-complete-operation)
|
||||
(ein:log 'info "Notebook %s is ready" (ein:$notebook-notebook-name notebook)))
|
||||
(when callback0
|
||||
|
@ -456,18 +465,16 @@ of minor mode."
|
|||
(with-current-buffer (ido-completing-read
|
||||
"Notebook: " it nil t)
|
||||
(ein:get-notebook))))))
|
||||
(if (> ein:notebook-autosave-frequency 0)
|
||||
(if notebook
|
||||
(progn (setf (ein:$notebook-autosave-timer notebook)
|
||||
(run-at-time ein:notebook-autosave-frequency
|
||||
ein:notebook-autosave-frequency
|
||||
#'ein:notebook-maybe-save-notebook
|
||||
notebook))
|
||||
(ein:log 'verbose "Enabling autosaves for %s with frequency %s seconds."
|
||||
(ein:$notebook-notebook-name notebook)
|
||||
ein:notebook-autosave-frequency))
|
||||
(message "Open notebook first"))
|
||||
(message "ein:notebook-autosave-frequency is %d" ein:notebook-autosave-frequency)))
|
||||
(when (and (ein:$notebook-q-checkpoints notebook)
|
||||
(> ein:notebook-autosave-frequency 0))
|
||||
(setf (ein:$notebook-autosave-timer notebook)
|
||||
(run-at-time ein:notebook-autosave-frequency
|
||||
ein:notebook-autosave-frequency
|
||||
#'ein:notebook-maybe-save-notebook
|
||||
notebook))
|
||||
(ein:log 'verbose "Enabling autosaves for %s with frequency %s seconds."
|
||||
(ein:$notebook-notebook-name notebook)
|
||||
ein:notebook-autosave-frequency)))
|
||||
|
||||
(defun ein:notebook-disable-autosaves (notebook)
|
||||
"Disable automatic, periodic saving for current notebook."
|
||||
|
@ -539,16 +546,6 @@ notebook buffer."
|
|||
`((:name . ,(ein:$kernelspec-name kernelspec))
|
||||
(:display_name . ,(format "%s" display-name)))))
|
||||
|
||||
(defun ein:get-kernelspec (url-or-port name)
|
||||
(let* ((kernelspecs (ein:need-kernelspecs url-or-port))
|
||||
(name (if (stringp name)
|
||||
(intern (format ":%s" name))
|
||||
name))
|
||||
(ks (plist-get kernelspecs name)))
|
||||
(if (stringp ks)
|
||||
(ein:get-kernelspec url-or-port ks)
|
||||
ks)))
|
||||
|
||||
(defun ein:list-available-kernels (url-or-port)
|
||||
(let ((kernelspecs (ein:need-kernelspecs url-or-port)))
|
||||
(if kernelspecs
|
||||
|
@ -881,13 +878,16 @@ This is equivalent to do ``C-c`` in the console program."
|
|||
|
||||
NAME is any non-empty string that does not contain '/' or '\\'."
|
||||
(interactive
|
||||
(list (read-string "Rename notebook: " (ein:$notebook-notebook-path ein:%notebook%))))
|
||||
(list (read-string "Rename notebook: "
|
||||
(ein:$notebook-notebook-path ein:%notebook%))))
|
||||
(unless (and (string-match ".ipynb" path) (= (match-end 0) (length path)))
|
||||
(setq path (format "%s.ipynb" path)))
|
||||
(let ((content (ein:content-from-notebook ein:%notebook%))
|
||||
(old-name (ein:$notebook-notebook-name ein:%notebook%)))
|
||||
(ein:log 'info "Renaming notebook at URL %s" (ein:notebook-url ein:%notebook%))
|
||||
(ein:content-rename content path #'ein:notebook-rename-success (list ein:%notebook% content))))
|
||||
(ein:log 'info "Renaming notebook at URL %s"
|
||||
(ein:notebook-url ein:%notebook%))
|
||||
(ein:content-rename content path #'ein:notebook-rename-success
|
||||
(list ein:%notebook% content))))
|
||||
|
||||
(defun ein:notebook-save-to-command (path)
|
||||
"Make a copy of the notebook and save it to a new path specified by NAME.
|
||||
|
@ -905,20 +905,11 @@ NAME is any non-empty string that does not contain '/' or '\\'.
|
|||
(list (ein:$notebook-url-or-port ein:%notebook%)
|
||||
path))))
|
||||
|
||||
;; (defun* ein:notebook-rename-error (old new notebook &key symbol-status response
|
||||
;; error-thrown
|
||||
;; &allow-other-keys)
|
||||
;; (if (= (request-response-status-code response) 409)
|
||||
;; (progn
|
||||
;; (ein:log 'warn "IPython returned a 409 status code, but has still renamed the notebook. This may be an IPython bug.")
|
||||
;; (ein:notebook-rename-success notebook new response))
|
||||
;; (ein:log 'error
|
||||
;; "Error (%s :: %s) while renaming notebook %s to %s."
|
||||
;; symbol-status error-thrown old new)))
|
||||
|
||||
(defun* ein:notebook-rename-success (notebook content)
|
||||
(ein:notebook-remove-opened-notebook notebook)
|
||||
(ein:notebook-set-notebook-name notebook (ein:$content-name content))
|
||||
(setf (ein:$notebook-notebook-path notebook) (ein:$content-path content))
|
||||
(ein:notebook-put-opened-notebook notebook)
|
||||
(mapc #'ein:worksheet-set-buffer-name
|
||||
(ein:$notebook-worksheets notebook))
|
||||
(ein:log 'info "Notebook renamed to %s." (ein:$content-name content)))
|
||||
|
@ -962,14 +953,16 @@ notebook worksheets."
|
|||
(defun ein:notebook-create-checkpoint (notebook)
|
||||
"Create checkpoint for current notebook based on most recent save."
|
||||
(interactive (list (ein:get-notebook)))
|
||||
(ein:content-create-checkpoint (ein:fast-content-from-notebook notebook)
|
||||
(lexical-let ((notebook notebook))
|
||||
#'(lambda (content)
|
||||
(ein:log 'verbose "Checkpoint %s for %s generated."
|
||||
(plist-get (first (ein:$content-checkpoints content)) :id)
|
||||
(ein:$notebook-notebook-name notebook))
|
||||
(setf (ein:$notebook-checkpoints notebook)
|
||||
(ein:$content-checkpoints content))))))
|
||||
(if (ein:$notebook-q-checkpoints notebook)
|
||||
(ein:content-create-checkpoint
|
||||
(ein:fast-content-from-notebook notebook)
|
||||
(lexical-let ((notebook notebook))
|
||||
#'(lambda (content)
|
||||
(ein:log 'verbose "Checkpoint %s for %s generated."
|
||||
(plist-get (first (ein:$content-checkpoints content)) :id)
|
||||
(ein:$notebook-notebook-name notebook))
|
||||
(setf (ein:$notebook-checkpoints notebook)
|
||||
(ein:$content-checkpoints content)))))))
|
||||
|
||||
(defun ein:notebook-list-checkpoint-ids (notebook)
|
||||
(unless (ein:$notebook-checkpoints notebook)
|
||||
|
@ -1271,6 +1264,11 @@ worksheet to save result."
|
|||
notebook
|
||||
ein:notebook--opened-map))
|
||||
|
||||
(defun ein:notebook-remove-opened-notebook (notebook)
|
||||
(remhash (list (ein:$notebook-url-or-port notebook)
|
||||
(ein:$notebook-notebook-path notebook))
|
||||
ein:notebook--opened-map))
|
||||
|
||||
(defun ein:notebook-opened-notebooks (&optional predicate)
|
||||
"Return list of opened notebook instances.
|
||||
If PREDICATE is given, notebooks are filtered by PREDICATE.
|
||||
|
|
|
@ -134,6 +134,10 @@ is opened at first time.::
|
|||
"Data store for `ein:notebooklist-list'.
|
||||
Mapping from URL-OR-PORT to an instance of `ein:$notebooklist'.")
|
||||
|
||||
(defun ein:notebooklist-keys ()
|
||||
"Get a list of registered server urls."
|
||||
(hash-table-keys ein:notebooklist-map))
|
||||
|
||||
(defun ein:notebooklist-list ()
|
||||
"Get a list of opened `ein:$notebooklist'."
|
||||
(hash-table-values ein:notebooklist-map))
|
||||
|
@ -170,39 +174,34 @@ This function adds NBLIST to `ein:notebooklist-map'."
|
|||
(get-buffer-create
|
||||
(format ein:notebooklist-buffer-name-template url-or-port)))
|
||||
|
||||
(defun ein:crib-token--all-local-tokens ()
|
||||
"Generate a hash table of authorization tokens (when they
|
||||
exist) for allow local jupyter instances, keyed by they url and
|
||||
port the instance is running on."
|
||||
(let ((lines
|
||||
(condition-case err
|
||||
;; there may be NO local jupyter installation
|
||||
(process-lines ein:jupyter-default-server-command
|
||||
"notebook" "list" "--json")
|
||||
(error (message "Error getting local tokens: %s" err)
|
||||
()))) ; empty list
|
||||
(url-tokens (make-hash-table :test #'equal)))
|
||||
(loop for line in lines
|
||||
do (destructuring-bind
|
||||
(&key password url token &allow-other-keys)
|
||||
(ein:json-read-from-string line)
|
||||
(push (list password token) (gethash (ein:url url) url-tokens))))
|
||||
url-tokens))
|
||||
|
||||
(defun ein:crib-token (url-or-port)
|
||||
(let ((pw-pairs (gethash url-or-port (ein:crib-token--all-local-tokens))))
|
||||
;; pw-pairs is of the form ((PASSWORD-P TOKEN) (PASSWORD-P TOKEN))
|
||||
(cond ((= (length pw-pairs) 1) (car pw-pairs))
|
||||
((> (length pw-pairs) 1)
|
||||
;; orig code: (list :json-false token) meant "no password, yes token"
|
||||
;; It's not clear how two entries for the same url could happen but if it did,
|
||||
;; 1. what if both entries don't have any auth enabled?
|
||||
;; 2. what if an entry required a password and not a token?
|
||||
;; It's best to return "nil nil" in this unlikely (impossible?) event, and let
|
||||
;; the downstream logic handle it.
|
||||
(warn "I see multiple jupyter servers registered on the same url! Please enter the token for one that is actually running.")
|
||||
(list nil nil))
|
||||
(t (list nil nil)))))
|
||||
"Shell out to jupyter for its credentials knowledge. Return list of (PASSWORD TOKEN)."
|
||||
(ein:aif (loop for line in (condition-case err
|
||||
(process-lines ein:jupyter-default-server-command
|
||||
"notebook" "list" "--json")
|
||||
;; often there is no local jupyter installation
|
||||
(error (ein:log 'info "ein:crib-token: %s" err) nil))
|
||||
with token0
|
||||
with password0
|
||||
when (destructuring-bind
|
||||
(&key password url token &allow-other-keys)
|
||||
(ein:json-read-from-string line)
|
||||
(prog1 (equal (ein:url url) url-or-port)
|
||||
(setq password0 password) ;; t or :json-false
|
||||
(setq token0 token)))
|
||||
return (list password0 token0))
|
||||
it (list nil nil)))
|
||||
|
||||
(defun ein:crib-running-servers ()
|
||||
"Shell out to jupyter for running servers."
|
||||
(loop for line in (condition-case err
|
||||
(process-lines ein:jupyter-default-server-command
|
||||
"notebook" "list" "--json")
|
||||
(error (ein:log 'info "ein:crib-running-servers: %s" err)
|
||||
nil))
|
||||
collecting (destructuring-bind
|
||||
(&key url &allow-other-keys)
|
||||
(ein:json-read-from-string line) (ein:url url))))
|
||||
|
||||
(defun ein:notebooklist-token-or-password (url-or-port)
|
||||
"Return token or password (jupyter requires one or the other but not both) for URL-OR-PORT. Empty string token means all authentication disabled. Nil means don't know."
|
||||
|
@ -224,8 +223,7 @@ port the instance is running on."
|
|||
(-distinct (mapcar #'ein:url
|
||||
(append (list default)
|
||||
ein:url-or-port
|
||||
(hash-table-keys
|
||||
(ein:crib-token--all-local-tokens))))))
|
||||
(ein:crib-running-servers)))))
|
||||
(url-or-port (let ((ido-report-no-match nil)
|
||||
(ido-use-faces nil))
|
||||
(ido-completing-read "URL or port: "
|
||||
|
@ -237,7 +235,7 @@ port the instance is running on."
|
|||
(defun ein:notebooklist-open* (url-or-port &optional path resync callback errback)
|
||||
"The main entry to server at URL-OR-PORT. Users should not directly call this, but instead `ein:notebooklist-login'.
|
||||
|
||||
PATH is specifying directory from file navigation. PATH is empty on login. RESYNC is requery server attributes such as ipython version and kernelspecs. CALLBACK takes two arguments, the resulting buffer and URL-OR-PORT. ERRBACK takes one argument, the resulting buffer.
|
||||
A \"notebooklist\" can be opened from any PATH within the server root hierarchy. PATH is empty at the root. RESYNC is requery server attributes such as ipython version and kernelspecs. CALLBACK takes two arguments, the resulting buffer and URL-OR-PORT. ERRBACK takes one argument, the resulting buffer.
|
||||
|
||||
TODO: going to maintain jupyterhub hooks here
|
||||
"
|
||||
|
@ -248,27 +246,19 @@ TODO: going to maintain jupyterhub hooks here
|
|||
(success (apply-partially #'ein:notebooklist-open--finish
|
||||
url-or-port callback))
|
||||
(failure errback))
|
||||
(if (or resync (not (ein:notebooklist-list-get url-or-port)))
|
||||
(deferred:$
|
||||
(deferred:parallel
|
||||
(lexical-let ((d (deferred:new #'identity)))
|
||||
(ein:query-notebook-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))
|
||||
(deferred:nextc it
|
||||
(lambda (&rest ignore)
|
||||
(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 failure))))
|
||||
(ein:content-query-contents url-or-port path success failure))))
|
||||
(if (and (not resync) (ein:notebooklist-list-get url-or-port))
|
||||
(ein:content-query-contents url-or-port path success failure)
|
||||
(ein:query-notebook-version
|
||||
url-or-port
|
||||
(lambda ()
|
||||
(ein:query-kernelspecs
|
||||
url-or-port
|
||||
(lambda ()
|
||||
(deferred:$
|
||||
(deferred:next
|
||||
(lambda ()
|
||||
(ein:content-query-hierarchy url-or-port #'ignore))))
|
||||
(ein:content-query-contents url-or-port path success failure))))))))
|
||||
|
||||
(defcustom ein:notebooklist-keepalive-refresh-time 1
|
||||
"When the notebook keepalive is enabled, the frequency, IN
|
||||
|
@ -351,14 +341,15 @@ automatically be called during calls to `ein:notebooklist-open`."
|
|||
"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 nblist resync)
|
||||
(defun ein:notebooklist-reload (&optional nblist resync callback)
|
||||
"Reload current Notebook list."
|
||||
(interactive)
|
||||
(unless nblist
|
||||
(setq nblist ein:%notebooklist%))
|
||||
(when nblist
|
||||
(ein:notebooklist-open* (ein:$notebooklist-url-or-port nblist)
|
||||
(ein:$notebooklist-path nblist) resync)))
|
||||
(ein:$notebooklist-path nblist) resync
|
||||
callback)))
|
||||
|
||||
(defun ein:notebooklist-refresh-related ()
|
||||
"Reload notebook list in which current notebook locates.
|
||||
|
@ -377,92 +368,79 @@ This function is called via `ein:notebook-after-rename-hook'."
|
|||
(ein:content-upload nb-path upload-path)))
|
||||
|
||||
;;;###autoload
|
||||
(defun ein:notebooklist-new-notebook (&optional url-or-port kernelspec path callback)
|
||||
"Ask server to create a new notebook and open it in a new buffer."
|
||||
(defun ein:notebooklist-new-notebook (url-or-port kernelspec &optional callback no-pop retry)
|
||||
(interactive (list (ein:notebooklist-ask-url-or-port)
|
||||
(ido-completing-read
|
||||
"Select kernel: "
|
||||
(ein:list-available-kernels (ein:$notebooklist-url-or-port ein:%notebooklist%)) nil t nil nil "default" nil)))
|
||||
(let ((path (or path (ein:$notebooklist-path (or ein:%notebooklist%
|
||||
(ein:notebooklist-list-get url-or-port)))))
|
||||
(version (ein:$notebooklist-api-version (or ein:%notebooklist%
|
||||
(ein:notebooklist-list-get url-or-port)))))
|
||||
(unless url-or-port
|
||||
(setq url-or-port (ein:$notebooklist-url-or-port ein:%notebooklist%)))
|
||||
(let ((url (ein:notebooklist-url url-or-port
|
||||
version
|
||||
path)))
|
||||
(ein:query-singleton-ajax
|
||||
(list 'notebooklist-new-notebook url-or-port path)
|
||||
url
|
||||
:type "POST"
|
||||
:data (json-encode '((:type . "notebook")))
|
||||
:parser #'ein:json-read
|
||||
;; (lambda ()
|
||||
;; (ein:html-get-data-in-body-tag "data-notebook-id"))
|
||||
:error (apply-partially #'ein:notebooklist-new-notebook-error
|
||||
url-or-port path callback)
|
||||
:success (apply-partially #'ein:notebooklist-new-notebook-callback
|
||||
url-or-port kernelspec path callback)))))
|
||||
(ein:list-available-kernels
|
||||
(ein:$notebooklist-url-or-port ein:%notebooklist%))
|
||||
nil t nil nil "default" nil)))
|
||||
(let* ((notebooklist (ein:notebooklist-list-get url-or-port))
|
||||
(path (ein:$notebooklist-path notebooklist))
|
||||
(version (ein:$notebooklist-api-version notebooklist))
|
||||
(url (ein:notebooklist-url url-or-port version path)))
|
||||
(ein:query-singleton-ajax
|
||||
(list 'notebooklist-new-notebook url-or-port path)
|
||||
url
|
||||
:type "POST"
|
||||
:data (json-encode '((:type . "notebook")))
|
||||
:parser #'ein:json-read
|
||||
:error (apply-partially #'ein:notebooklist-new-notebook-error
|
||||
url-or-port kernelspec path callback no-pop retry)
|
||||
:success (apply-partially #'ein:notebooklist-new-notebook-success
|
||||
url-or-port kernelspec path callback no-pop))))
|
||||
|
||||
(defun* ein:notebooklist-new-notebook-callback (url-or-port
|
||||
kernelspec
|
||||
path
|
||||
callback
|
||||
&key
|
||||
data
|
||||
&allow-other-keys)
|
||||
(defun* ein:notebooklist-new-notebook-success (url-or-port
|
||||
kernelspec
|
||||
path
|
||||
callback
|
||||
no-pop
|
||||
&key
|
||||
data
|
||||
&allow-other-keys)
|
||||
(let ((nbname (plist-get data :name))
|
||||
(nbpath (plist-get data :path)))
|
||||
(when (< (ein:notebook-version-numeric url-or-port) 3)
|
||||
(if (string= nbpath "")
|
||||
(setq nbpath nbname)
|
||||
(setq nbpath (format "%s/%s" nbpath nbname))))
|
||||
(ein:notebook-open url-or-port nbpath kernelspec callback)
|
||||
(ein:notebook-open url-or-port nbpath kernelspec callback nil no-pop)
|
||||
(ein:notebooklist-open* url-or-port path)))
|
||||
|
||||
(defun* ein:notebooklist-new-notebook-error
|
||||
(url-or-port callback
|
||||
&key response &allow-other-keys
|
||||
&aux
|
||||
(error (request-response-error-thrown response))
|
||||
(dest (request-response-url response)))
|
||||
(ein:log 'verbose
|
||||
"NOTEBOOKLIST-NEW-NOTEBOOK-ERROR url-or-port: %S; error: %S; dest: %S"
|
||||
url-or-port error dest)
|
||||
(ein:log 'error
|
||||
"Failed to open new notebook (error: %S). \
|
||||
You may find the new one in the notebook list." error)
|
||||
(ein:notebooklist-open* url-or-port nil nil (lambda (buffer url-or-port)
|
||||
(pop-to-buffer buffer))))
|
||||
(url-or-port kernelspec path callback no-pop retry
|
||||
&key symbol-status error-thrown &allow-other-keys)
|
||||
(let ((notice (format "ein:notebooklist-new-notebook-error: %s %s"
|
||||
symbol-status error-thrown)))
|
||||
(if retry
|
||||
(ein:log 'error notice)
|
||||
(ein:log 'info notice)
|
||||
(sleep-for 0 1500)
|
||||
(ein:notebooklist-new-notebook url-or-port kernelspec callback no-pop t))))
|
||||
|
||||
;;;###autoload
|
||||
(defun ein:notebooklist-new-notebook-with-name (name kernelspec url-or-port &optional path)
|
||||
(defun ein:notebooklist-new-notebook-with-name
|
||||
(url-or-port kernelspec name &optional callback no-pop)
|
||||
"Open new notebook and rename the notebook."
|
||||
(interactive (let* ((url-or-port (or (ein:get-url-or-port)
|
||||
(ein:default-url-or-port)))
|
||||
(kernelspec (ido-completing-read
|
||||
"Select kernel: "
|
||||
(ein:list-available-kernels url-or-port) nil t nil nil "default" nil))
|
||||
(name (read-from-minibuffer
|
||||
(format "Notebook name (at %s): " url-or-port))))
|
||||
(list name kernelspec url-or-port)))
|
||||
(let ((path (or path (ein:$notebooklist-path
|
||||
(or ein:%notebooklist%
|
||||
(ein:get-notebook)
|
||||
(gethash url-or-port ein:notebooklist-map))))))
|
||||
(ein:notebooklist-new-notebook
|
||||
url-or-port
|
||||
kernelspec
|
||||
path
|
||||
(apply-partially
|
||||
(lambda (name* notebook created)
|
||||
(unless created
|
||||
(ein:log 'warn "Notebook %s already existed" name))
|
||||
(with-current-buffer (ein:notebook-buffer notebook)
|
||||
(ein:notebook-rename-command name*)
|
||||
(pop-to-buffer (current-buffer))))
|
||||
name))))
|
||||
(interactive
|
||||
(let* ((url-or-port (or (ein:get-url-or-port)
|
||||
(ein:default-url-or-port)))
|
||||
(kernelspec (ido-completing-read
|
||||
"Select kernel: "
|
||||
(ein:list-available-kernels url-or-port)
|
||||
nil t nil nil "default" nil))
|
||||
(name (read-from-minibuffer
|
||||
(format "Notebook name (at %s): " url-or-port))))
|
||||
(list name kernelspec url-or-port)))
|
||||
(unless callback
|
||||
(setq callback #'ignore))
|
||||
(add-function :before callback
|
||||
(apply-partially
|
||||
(lambda (name* notebook _created)
|
||||
(with-current-buffer (ein:notebook-buffer notebook)
|
||||
(ein:notebook-rename-command name*)))
|
||||
name))
|
||||
(ein:notebooklist-new-notebook url-or-port kernelspec callback no-pop))
|
||||
|
||||
(defun ein:notebooklist-delete-notebook-ask (path)
|
||||
(when (y-or-n-p (format "Delete notebook %s?" path))
|
||||
|
@ -487,7 +465,7 @@ You may find the new one in the notebook list." error)
|
|||
&allow-other-keys
|
||||
&aux (resp-string (format "STATUS: %s DATA: %s" (request-response-status-code response) data)))
|
||||
(ein:log 'debug "ein:notebooklist-delete-notebook--complete %s" resp-string)
|
||||
(when (and callback (eq symbol-status 'success)) (funcall callback)))
|
||||
(when callback (funcall callback)))
|
||||
|
||||
;; Because MinRK wants me to suffer (not really, I love MinRK)...
|
||||
(defun ein:get-actual-path (path)
|
||||
|
@ -551,7 +529,8 @@ You may find the new one in the notebook list." error)
|
|||
(widget-create
|
||||
'link
|
||||
:notify (lambda (&rest ignore) (ein:notebooklist-new-notebook
|
||||
(ein:$notebooklist-url-or-port ein:%notebooklist%)))
|
||||
(ein:$notebooklist-url-or-port ein:%notebooklist%)
|
||||
nil))
|
||||
"New Notebook")
|
||||
(widget-insert " ")
|
||||
(widget-create
|
||||
|
@ -782,14 +761,13 @@ Notebook list data is passed via the buffer local variable
|
|||
|
||||
(defun ein:notebooklist-parse-nbpath (nbpath)
|
||||
"Return `(,url-or-port ,path) from URL-OR-PORT/PATH"
|
||||
(loop for url-or-port in (hash-table-keys ein:notebooklist-map)
|
||||
(loop for url-or-port in (ein:notebooklist-keys)
|
||||
if (search url-or-port nbpath :end2 (length url-or-port))
|
||||
return (list (substring nbpath 0 (length url-or-port))
|
||||
(substring nbpath (1+ (length url-or-port))))
|
||||
end
|
||||
finally (ein:display-warning
|
||||
(format "%s not among: %s" nbpath
|
||||
(hash-table-keys ein:notebooklist-map))
|
||||
(format "%s not among: %s" nbpath (ein:notebooklist-keys))
|
||||
:error)))
|
||||
|
||||
(defsubst ein:notebooklist-ask-path (&optional content-type)
|
||||
|
|
|
@ -45,9 +45,7 @@
|
|||
(pop-to-buffer (ein:notebook-buffer notebook)))
|
||||
(when search
|
||||
(goto-char (point-min))
|
||||
(search-forward search nil t))
|
||||
;; More to come here:
|
||||
))
|
||||
(search-forward search nil t))))
|
||||
|
||||
;;;###autoload
|
||||
(defun ein:org-open (link-path)
|
||||
|
|
|
@ -125,8 +125,11 @@
|
|||
(defun ein:process-refresh-processes ()
|
||||
"Use `jupyter notebook list --json` to populate ein:%processes%"
|
||||
(clrhash ein:%processes%)
|
||||
(loop for line in (process-lines ein:jupyter-default-server-command
|
||||
"notebook" "list" "--json")
|
||||
(loop for line in (condition-case err
|
||||
(process-lines ein:jupyter-default-server-command
|
||||
"notebook" "list" "--json")
|
||||
;; often there is no local jupyter installation
|
||||
(error (ein:log 'info "ein:process-refresh-processes: %s" err) nil))
|
||||
do (destructuring-bind
|
||||
(&key pid url notebook_dir &allow-other-keys)
|
||||
(ein:json-read-from-string line)
|
||||
|
@ -142,6 +145,15 @@
|
|||
when (search dir filename)
|
||||
return (gethash dir ein:%processes%)))
|
||||
|
||||
(defun ein:process-url-match (url-or-port)
|
||||
"Return ein:process whose url matches URL-OR-PORT."
|
||||
(loop with parsed-url-or-port = (url-generic-parse-url url-or-port)
|
||||
for proc in (ein:process-processes)
|
||||
for parsed-url-proc = (url-generic-parse-url (ein:process-url-or-port proc))
|
||||
when (and (string= (url-host parsed-url-or-port) (url-host parsed-url-proc))
|
||||
(= (url-port parsed-url-or-port) (url-port parsed-url-proc)))
|
||||
return proc))
|
||||
|
||||
(defsubst ein:process-url-or-port (proc)
|
||||
"Naively construct url-or-port from ein:process PROC's port and ip fields"
|
||||
(ein:$process-url proc))
|
||||
|
|
|
@ -292,14 +292,14 @@ given, open the last point in the other window."
|
|||
"Do the doctest of the object at point."
|
||||
(interactive)
|
||||
(let ((object (ein:object-at-point)))
|
||||
(ein:shared-output-eval-string
|
||||
(format "__ein_run_docstring_examples(%s)" object)
|
||||
t)))
|
||||
(ein:shared-output-eval-string (ein:get-kernel)
|
||||
(format "__ein_run_docstring_examples(%s)" object)
|
||||
t)))
|
||||
|
||||
(defun ein:pytools-whos ()
|
||||
"Execute ``%whos`` magic command and popup the result."
|
||||
(interactive)
|
||||
(ein:shared-output-eval-string "%whos" t))
|
||||
(ein:shared-output-eval-string (ein:get-kernel) "%whos" t))
|
||||
|
||||
(defun ein:pytools-hierarchy (&optional ask)
|
||||
"Draw inheritance graph of the class at point.
|
||||
|
@ -313,7 +313,7 @@ You can explicitly specify the object by selecting it.
|
|||
(setq object (read-from-minibuffer "class or object: " object)))
|
||||
(assert (and object (not (equal object "")))
|
||||
nil "Object at point not found.")
|
||||
(ein:shared-output-eval-string (format "%%hierarchy %s" object) t)))
|
||||
(ein:shared-output-eval-string (ein:get-kernel) (format "%%hierarchy %s" object) t)))
|
||||
|
||||
(defun ein:pytools-pandas-to-ses (dataframe)
|
||||
"View pandas_ DataFrame in SES_ (Simple Emacs Spreadsheet).
|
||||
|
@ -404,7 +404,9 @@ Currently EIN/IPython supports exporting to the following formats:
|
|||
(defun ein:pytools-set-figure-size (width height)
|
||||
"Set the default figure size for matplotlib figures. Works by setting `rcParams['figure.figsize']`."
|
||||
(interactive "nWidth: \nnHeight: ")
|
||||
(ein:shared-output-eval-string (format "__ein_set_figure_size(%s,%s)" width height)))
|
||||
(ein:shared-output-eval-string (ein:get-kernel)
|
||||
(format "__ein_set_figure_size(%s,%s)" width height)
|
||||
nil))
|
||||
|
||||
(provide 'ein-pytools)
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
((cell-type :initarg :cell-type :initform "shared-output")
|
||||
;; (element-names :initform (:prompt :output :footer))
|
||||
(popup :initarg :popup :initform nil :type boolean)
|
||||
)
|
||||
(callback :initarg :callback :initform #'ignore :type function))
|
||||
"A singleton cell to show output from non-notebook buffers.")
|
||||
|
||||
(defclass ein:shared-output ()
|
||||
|
@ -50,7 +50,7 @@
|
|||
(events :initarg :events :type ein:events)
|
||||
(ewoc :initarg :ewoc :type ewoc)))
|
||||
|
||||
(defvar ein:%shared-output% nil
|
||||
(defvar *ein:shared-output* nil
|
||||
"Hold an instance of `ein:shared-output'.")
|
||||
|
||||
(defconst ein:shared-output-buffer-name "*ein:shared-output*")
|
||||
|
@ -68,8 +68,8 @@ Called from ewoc pretty printer via `ein:cell-pp'."
|
|||
(when (slot-value cell 'autoexec) " %s" ein:cell-autoexec-prompt))
|
||||
'font-lock-face 'ein:cell-input-prompt))
|
||||
|
||||
(cl-defmethod ein:cell-execute ((cell ein:shared-output-cell) kernel code
|
||||
&optional popup &rest args)
|
||||
(cl-defmethod ein:cell-execute ((cell ein:shared-output-cell) kernel code popup
|
||||
&rest args)
|
||||
(unless (plist-get args :silent)
|
||||
(setq args (plist-put args :silent nil)))
|
||||
(setf (slot-value cell 'popup) popup)
|
||||
|
@ -77,26 +77,28 @@ Called from ewoc pretty printer via `ein:cell-pp'."
|
|||
(apply #'ein:cell-execute-internal cell kernel code args))
|
||||
|
||||
(cl-defmethod ein:cell--handle-output ((cell ein:shared-output-cell)
|
||||
msg-type content _metadata)
|
||||
;; Show short message
|
||||
(ein:case-equal msg-type
|
||||
(("pyout")
|
||||
(let ((num (plist-get content :execution_count))
|
||||
(text (plist-get (plist-get content :data) :text/plain)))
|
||||
(when text
|
||||
(ein:log 'info "Out[%s]: %s" num (car (split-string text "\n"))))))
|
||||
(("stream")
|
||||
(let ((stream (or (plist-get content :stream) "stdout"))
|
||||
(text (plist-get content :data)))
|
||||
(when text
|
||||
(ein:log 'info "%s: %s" stream (car (split-string text "\n"))))))
|
||||
(t
|
||||
(ein:log 'info "Got output '%s' in the shared buffer." msg-type)))
|
||||
;; Open `ein:shared-output-buffer-name' if necessary
|
||||
msg-type _content _metadata)
|
||||
(ein:log 'debug
|
||||
"ein:cell--handle-output (cell ein:shared-output-cell): %s" msg-type)
|
||||
(when (slot-value cell 'popup)
|
||||
(pop-to-buffer (ein:shared-output-create-buffer)))
|
||||
;; Finally do the normal drawing
|
||||
(cl-call-next-method))
|
||||
(cl-call-next-method)
|
||||
(ein:aif (ein:oref-safe cell 'callback)
|
||||
(funcall it cell)))
|
||||
|
||||
(cl-defmethod ein:cell--handle-execute-reply ((cell ein:shared-output-cell)
|
||||
content _metadata)
|
||||
(ein:log 'debug
|
||||
"ein:cell--handle-execute-reply (cell ein:shared-output-cell): %s"
|
||||
content)
|
||||
(cl-call-next-method)
|
||||
(when (ein:oref-safe cell 'callback)
|
||||
;; clear the way for waiting block in `ob-ein--execute-async'
|
||||
;; but only after 2 seconds to get handle-output stragglers
|
||||
;; TODO avoid this hack
|
||||
(run-at-time 2 nil (lambda ()
|
||||
(ein:log 'debug "Clearing callback shared output cell")
|
||||
(setf (slot-value cell 'callback) #'ignore)))))
|
||||
|
||||
|
||||
;;; Main
|
||||
|
@ -106,26 +108,25 @@ Called from ewoc pretty printer via `ein:cell-pp'."
|
|||
(get-buffer-create ein:shared-output-buffer-name))
|
||||
|
||||
(defun ein:shared-output-buffer ()
|
||||
"Get the buffer associated with `ein:%shared-output%'."
|
||||
(ewoc-buffer (slot-value ein:%shared-output% 'ewoc)))
|
||||
"Get the buffer associated with `*ein:shared-output*'."
|
||||
(ewoc-buffer (slot-value *ein:shared-output* 'ewoc)))
|
||||
|
||||
(defun ein:shared-output-buffer-p (&optional buffer)
|
||||
"Return non-`nil' when BUFFER (or current buffer) is shared-output buffer."
|
||||
(eq (or buffer (current-buffer)) (ein:shared-output-buffer)))
|
||||
|
||||
(defun ein:shared-output-healthy-p ()
|
||||
(and (ein:shared-output-p ein:%shared-output%)
|
||||
(and (ein:shared-output-p *ein:shared-output*)
|
||||
(buffer-live-p (ein:shared-output-buffer))))
|
||||
|
||||
(defun ein:shared-output-get-or-create ()
|
||||
(if (ein:shared-output-healthy-p)
|
||||
ein:%shared-output%
|
||||
*ein:shared-output*
|
||||
(with-current-buffer (ein:shared-output-create-buffer)
|
||||
;; FIXME: This is a duplication of `ein:worksheet-render'.
|
||||
;; Must be merged.
|
||||
(let* ((inhibit-read-only t)
|
||||
;; Enable nonsep for ewoc object (the last argument is non-nil).
|
||||
;; This is for putting read-only text properties to the newlines.
|
||||
;; Apply read-only text property to newlines by
|
||||
;; setting nonsep flag to `ein:ewoc-create'
|
||||
(ewoc (let ((buffer-undo-list t))
|
||||
(ein:ewoc-create 'ein:worksheet-pp
|
||||
(ein:propertize-read-only "\n")
|
||||
|
@ -135,13 +136,13 @@ Called from ewoc pretty printer via `ein:cell-pp'."
|
|||
:events events)))
|
||||
(erase-buffer)
|
||||
(ein:shared-output-bind-events events)
|
||||
(setq ein:%shared-output%
|
||||
(setq *ein:shared-output*
|
||||
(ein:shared-output :ewoc ewoc :cell cell
|
||||
:events events))
|
||||
(ein:cell-enter-last cell))
|
||||
(setq buffer-read-only t)
|
||||
(ein:shared-output-mode)
|
||||
ein:%shared-output%)))
|
||||
*ein:shared-output*)))
|
||||
|
||||
(defun ein:shared-output-bind-events (events)
|
||||
"Add dummy event handlers."
|
||||
|
@ -166,19 +167,17 @@ Create a cell if the buffer has none."
|
|||
(pop-to-buffer (ein:shared-output-create-buffer)))
|
||||
|
||||
(cl-defmethod ein:shared-output-show-code-cell ((cell ein:codecell))
|
||||
"Show code CELL in shared-output buffer.
|
||||
Note that this function assumed to be called in the buffer
|
||||
where CELL locates."
|
||||
"Show code CELL in shared-output buffer."
|
||||
(let ((new (ein:cell-convert cell "shared-output")))
|
||||
;; Make sure `ein:%shared-output%' is initialized:
|
||||
;; Make sure `*ein:shared-output*' is initialized:
|
||||
(ein:shared-output-get-or-create)
|
||||
(with-current-buffer (ein:shared-output-create-buffer)
|
||||
(let ((inhibit-read-only t)
|
||||
(ein:cell-max-num-outputs nil))
|
||||
(setf (slot-value new 'ewoc) (slot-value ein:%shared-output% 'ewoc))
|
||||
(setf (slot-value new 'events) (slot-value ein:%shared-output% 'events))
|
||||
(setf (slot-value new 'ewoc) (slot-value *ein:shared-output* 'ewoc))
|
||||
(setf (slot-value new 'events) (slot-value *ein:shared-output* 'events))
|
||||
(erase-buffer) ; because there are only one cell anyway
|
||||
(setf (slot-value ein:%shared-output% 'cell) new)
|
||||
(setf (slot-value *ein:shared-output* 'cell) new)
|
||||
(ein:cell-enter-last new)
|
||||
(pop-to-buffer (current-buffer))))))
|
||||
|
||||
|
@ -197,8 +196,7 @@ See also `ein:cell-max-num-outputs'."
|
|||
"History of the `ein:shared-output-eval-string' prompt.")
|
||||
|
||||
;;;###autoload
|
||||
(defun ein:shared-output-eval-string (code &optional popup verbose kernel
|
||||
&rest args)
|
||||
(defun ein:shared-output-eval-string (kernel code popup &rest args)
|
||||
"Evaluate a piece of code. Prompt will appear asking the code to run.
|
||||
This is handy when you want to execute something quickly without
|
||||
making a cell. If the code outputs something, it will go to the
|
||||
|
@ -208,30 +206,15 @@ shared output buffer. You can open the buffer by the command
|
|||
.. ARGS is passed to `ein:kernel-execute'. Unlike `ein:kernel-execute',
|
||||
`:silent' is `nil' by default."
|
||||
(interactive
|
||||
(let ((kernel (ein:get-kernel-or-error))
|
||||
;; ... so error will be raised before user typing code if it
|
||||
;; is impossible to execute
|
||||
(code (read-string
|
||||
"IP[y]: "
|
||||
(when (region-active-p)
|
||||
(buffer-substring (region-beginning) (region-end)))
|
||||
'ein:shared-output-eval-string-history)))
|
||||
(list code nil t kernel)))
|
||||
(list nil
|
||||
(read-string
|
||||
"IP[y]: "
|
||||
(when (region-active-p)
|
||||
(buffer-substring (region-beginning) (region-end)))
|
||||
'ein:shared-output-eval-string-history)))
|
||||
(unless kernel (setq kernel (ein:get-kernel-or-error)))
|
||||
(let ((cell (ein:shared-output-get-cell)))
|
||||
;; If cell is already running, wait until it is finished
|
||||
;; before executing more code.
|
||||
(deferred:$
|
||||
(deferred:next
|
||||
(deferred:lambda ()
|
||||
(if (not (null (slot-value cell 'running)))
|
||||
(deferred:nextc (deferred:wait 50) self))))
|
||||
(deferred:nextc it
|
||||
(lambda ()
|
||||
(deferred:wait 100) ;; Give everyone a few milliseconds to breath.
|
||||
(apply #'ein:cell-execute cell kernel (ein:trim-indent code) popup args)
|
||||
(when verbose
|
||||
(ein:log 'info "Code \"%s\" is sent to the kernel." code)))))))
|
||||
(apply #'ein:cell-execute cell kernel (ein:trim-indent code) popup args)))
|
||||
|
||||
|
||||
;;; Generic getter
|
||||
|
@ -247,9 +230,9 @@ shared output buffer. You can open the buffer by the command
|
|||
(slot-value cell 'kernel))))
|
||||
|
||||
(defun ein:get-cell-at-point--shared-output ()
|
||||
(when (and (ein:shared-output-p ein:%shared-output%)
|
||||
(when (and (ein:shared-output-p *ein:shared-output*)
|
||||
(ein:shared-output-buffer-p))
|
||||
(slot-value ein:%shared-output% 'cell)))
|
||||
(slot-value *ein:shared-output* 'cell)))
|
||||
|
||||
(defun ein:get-traceback-data--shared-output ()
|
||||
(ein:aand (ein:get-cell-at-point--shared-output) (ein:cell-get-tb-data it)))
|
||||
|
|
|
@ -208,8 +208,10 @@ at point, i.e. any word before then \"(\", if it is present."
|
|||
(setq url-or-port (concat "https://" url-or-port))
|
||||
(setq parsed-url (url-generic-parse-url url-or-port)))
|
||||
(when (or (string= (url-host parsed-url) "localhost")
|
||||
(string= (url-host parsed-url) ein:url-localhost)
|
||||
(string= (url-host parsed-url) ""))
|
||||
(setf (url-host parsed-url) ein:url-localhost))
|
||||
(setf (url-host parsed-url) ein:url-localhost)
|
||||
(setf (url-type parsed-url) "http"))
|
||||
(directory-file-name (concat (file-name-as-directory (url-recreate-url parsed-url))
|
||||
(apply #'ein:glom-paths paths))))))
|
||||
|
||||
|
|
416
lisp/ob-ein.el
416
lisp/ob-ein.el
|
@ -26,58 +26,42 @@
|
|||
;; along with ob-ein.el. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Support executing org-babel source blocks using EIN worksheets.
|
||||
;; Modelled after https://github.com/gregsexton/ob-ipython by Greg Sexton
|
||||
;; Async support based on work by @khinsen on github in ob-ipython-async: https://github.com/khinsen/ob-ipython-async/blob/master/ob-ipython-async.el
|
||||
;; which was in turn inspired by the scimax starter kit by @jkitchin: https://github.com/jkitchin/scimax
|
||||
|
||||
;;; Code:
|
||||
(eval-when-compile (require 'cl))
|
||||
(require 'ob)
|
||||
(require 'ob-python)
|
||||
(require 'ein-shared-output)
|
||||
(require 'ein-utils)
|
||||
(require 'python)
|
||||
(require 'ein-notebooklist)
|
||||
(require 'ein-process)
|
||||
|
||||
(autoload 'org-element-property "org-element")
|
||||
(defvar *ob-ein-sentinel* "[....]"
|
||||
"Placeholder string replaced after async cell execution")
|
||||
|
||||
(defcustom ein:org-async-p t
|
||||
"If non-nil run ein org-babel source blocks asyncronously."
|
||||
:group 'ein
|
||||
:type 'boolean)
|
||||
(defcustom ob-ein-anonymous-path ".ob-ein.ipynb"
|
||||
"When session header specifies only server, prosecute all ob-ein interactions in this single anonymous notebook."
|
||||
:type '(string)
|
||||
:group 'ein)
|
||||
|
||||
(defcustom ein:org-inline-image-directory "ein-images"
|
||||
(defcustom ob-ein-inline-image-directory "ein-images"
|
||||
"Default directory where to save images generated from ein org-babel source blocks."
|
||||
:group 'ein
|
||||
:type '(directory))
|
||||
|
||||
;; declare default header arguments for this language
|
||||
(defvar org-babel-default-header-args:ein '())
|
||||
(defvar org-babel-default-header-args:ein nil)
|
||||
|
||||
(add-to-list 'org-src-lang-modes '("ein" . python))
|
||||
(add-to-list 'org-src-lang-modes '("ein-hy" . hy))
|
||||
|
||||
;; based on ob-ipython--configure-kernel
|
||||
(defun ein:org-register-lang-mode (lang-name lang-mode)
|
||||
"Define org+ein language LANG-NAME with syntax highlighting from LANG-MODE.
|
||||
For example, call (ein:org-register-lang-mode \"ein-R\" 'R) to define a language \"ein-R\" with R syntax highlighting for use with org-babel and ein."
|
||||
(add-to-list 'org-src-lang-modes `(,lang-name . ,lang-mode))
|
||||
(defvaralias (intern (concat "org-babel-default-header-args:" lang-name))
|
||||
'org-babel-default-header-args:ein)
|
||||
(defalias (intern (concat "org-babel-execute:" lang-name))
|
||||
'org-babel-execute:ein)
|
||||
(defalias (intern (concat "org-babel-" lang-name "-initiate-session"))
|
||||
'org-babel-ein-initiate-session))
|
||||
|
||||
;; Handling source block execution results
|
||||
(defun ein:temp-inline-image-info (value)
|
||||
(defun ob-ein--inline-image-info (value)
|
||||
(let* ((f (md5 value))
|
||||
(d ein:org-inline-image-directory)
|
||||
(d ob-ein-inline-image-directory)
|
||||
(tf (concat d "/ob-ein-" f ".png")))
|
||||
(unless (file-directory-p d)
|
||||
(make-directory d 'parents))
|
||||
tf))
|
||||
|
||||
(defun ein:write-base64-image (img-string file)
|
||||
(defun ob-ein--write-base64-image (img-string file)
|
||||
(with-temp-file file
|
||||
(let ((buffer-read-only nil)
|
||||
(buffer-file-coding-system 'binary)
|
||||
|
@ -86,7 +70,7 @@ For example, call (ein:org-register-lang-mode \"ein-R\" 'R) to define a language
|
|||
(insert img-string)
|
||||
(base64-decode-region (point-min) (point-max)))))
|
||||
|
||||
(defun ein:return-mime-type (json file)
|
||||
(defun ob-ein--return-mime-type (json file)
|
||||
(loop
|
||||
for key in ein:output-types-text-preferred
|
||||
for type = (intern (format ":%s" key)) ; something like `:text'
|
||||
|
@ -95,176 +79,116 @@ For example, call (ein:org-register-lang-mode \"ein-R\" 'R) to define a language
|
|||
return
|
||||
(case key
|
||||
((svg image/svg)
|
||||
(let ((file (or file (ein:temp-inline-image-info value))))
|
||||
(ein:write-base64-image value file)
|
||||
(let ((file (or file (ob-ein--inline-image-info value))))
|
||||
(ob-ein--write-base64-image value file)
|
||||
(format "[[file:%s]]" file)))
|
||||
((png image/png jpeg image/jpeg)
|
||||
(let ((file (or file (ein:temp-inline-image-info value))))
|
||||
(ein:write-base64-image value file)
|
||||
(let ((file (or file (ob-ein--inline-image-info value))))
|
||||
(ob-ein--write-base64-image value file)
|
||||
(format "[[file:%s]]" file)))
|
||||
(t (plist-get json type)))))
|
||||
|
||||
(defun org-babel-ein-process-outputs (outputs params)
|
||||
(defun ob-ein--process-outputs (outputs params)
|
||||
(let ((file (cdr (assoc :image params))))
|
||||
(ein:join-str "\n"
|
||||
(loop for o in outputs
|
||||
collecting (ein:return-mime-type o file)))))
|
||||
collecting (ob-ein--return-mime-type o file)))))
|
||||
|
||||
;; Asynchronous execution requires each source code block to
|
||||
;; be named. For blocks that have no name, an automatically
|
||||
;; generated name is added. This variable holds the function
|
||||
;; that generates the name.
|
||||
(defvar *ein:org-name-generator* 'ein:uuid-generator
|
||||
"Function to generate a name for a src block.
|
||||
The default is `ein:uuid-generator'.")
|
||||
|
||||
(defun ein:uuid-generator ()
|
||||
(org-id-new 'none))
|
||||
|
||||
(defun ein:org-get-name-create ()
|
||||
(defun ob-ein--get-name-create (src-block-info)
|
||||
"Get the name of a src block or add a uuid as the name."
|
||||
(if-let ((name (fifth (org-babel-get-src-block-info))))
|
||||
(if-let ((name (fifth src-block-info)))
|
||||
name
|
||||
(save-excursion
|
||||
(let ((el (org-element-context))
|
||||
(id (funcall *ein:org-name-generator*)))
|
||||
(goto-char (org-element-property :begin el))
|
||||
(insert (format "#+NAME: %s\n" id))
|
||||
id))))
|
||||
(id (org-id-new 'none)))
|
||||
(goto-char (org-element-property :begin el))
|
||||
(insert (format "#+NAME: %s\n" id))
|
||||
id))))
|
||||
|
||||
(defcustom ein:org-execute-timeout 30
|
||||
"Query timeout, in seconds, for executing ein source blocks in
|
||||
org files."
|
||||
:type 'number
|
||||
:group 'ein)
|
||||
(defun ein:org-register-lang-mode (lang-name lang-mode)
|
||||
"Define org+ein language LANG-NAME with syntax highlighting from LANG-MODE. Untested.
|
||||
|
||||
;; This is the main function which is called to evaluate a code
|
||||
;; block.
|
||||
;;
|
||||
;; This function will evaluate the body of the source code and
|
||||
;; return the results as emacs-lisp depending on the value of the
|
||||
;; :results header argument
|
||||
;; - output means that the output to STDOUT will be captured and
|
||||
;; returned
|
||||
;; - value means that the value of the last statement in the
|
||||
;; source code block will be returned
|
||||
;;
|
||||
For example, call (ein:org-register-lang-mode \"ein-R\" 'R) to define a language \"ein-R\" with R syntax highlighting for use with org-babel and ein.
|
||||
|
||||
Based on ob-ipython--configure-kernel.
|
||||
"
|
||||
(add-to-list 'org-src-lang-modes `(,lang-name . ,lang-mode))
|
||||
(defvaralias (intern (concat "org-babel-default-header-args:" lang-name))
|
||||
'org-babel-default-header-args:ein)
|
||||
(defalias (intern (concat "org-babel-execute:" lang-name))
|
||||
'org-babel-execute:ein))
|
||||
|
||||
;;;###autoload
|
||||
(defun org-babel-execute:ein (body params)
|
||||
"Execute a block of python code with org-babel by way of
|
||||
emacs-ipython-notebook's facilities for communicating with
|
||||
jupyter kernels.
|
||||
This function is called by `org-babel-execute-src-block'"
|
||||
(let* ((processed-params (org-babel-process-params params))
|
||||
"This function is called by `org-babel-execute-src-block'."
|
||||
(let* ((buffer (current-buffer))
|
||||
(processed-params (org-babel-process-params params))
|
||||
(result-params (cdr (assq :result-params params)))
|
||||
(kernelspec (cdr (assoc :kernelspec params)))
|
||||
;; set the session if the session variable is non-nil
|
||||
(session-kernel (org-babel-ein-initiate-session
|
||||
(cdr (assoc :session processed-params))
|
||||
kernelspec))
|
||||
;; either OUTPUT or VALUE which should behave as described above
|
||||
;; (result-type (cdr (assoc :result-type processed-params)))
|
||||
;; expand the body with `org-babel-expand-body:template'
|
||||
(full-body (org-babel-expand-body:generic (encode-coding-string body 'utf-8)
|
||||
params
|
||||
(org-babel-variable-assignments:python params))))
|
||||
(if ein:org-async-p
|
||||
(ein:ob-ein--execute-async full-body session-kernel processed-params (ein:org-get-name-create) result-params)
|
||||
(ein:ob-ein--execute full-body session-kernel processed-params))))
|
||||
(session (format "%s" (cdr (assoc :session processed-params))))
|
||||
(kernelspec (or (cdr (assoc :kernelspec processed-params)) "default"))
|
||||
(name (ob-ein--get-name-create (org-babel-get-src-block-info)))
|
||||
(full-body (org-babel-expand-body:generic
|
||||
(encode-coding-string body 'utf-8)
|
||||
params
|
||||
(org-babel-variable-assignments:python params)))
|
||||
(callback (lambda (notebook)
|
||||
(ob-ein--execute-async
|
||||
buffer
|
||||
full-body
|
||||
(ein:$notebook-kernel notebook)
|
||||
processed-params
|
||||
result-params
|
||||
name))))
|
||||
(ob-ein--initiate-session session kernelspec callback))
|
||||
*ob-ein-sentinel*)
|
||||
|
||||
;;;###autoload
|
||||
(defun org-babel-execute:ein-hy (body params)
|
||||
(org-babel-execute:ein (ein:pytools-wrap-hy-code body) params))
|
||||
|
||||
(defun ein:ob-ein--execute-async (body kernel params name result-params)
|
||||
(let ((buffer (current-buffer))
|
||||
(name name)
|
||||
(body body)
|
||||
(kernel kernel)
|
||||
(params params))
|
||||
(deferred:$
|
||||
(deferred:next
|
||||
(lambda ()
|
||||
(message "Starting deferred ein execution: %s" name)
|
||||
(ein:shared-output-eval-string body nil nil kernel)))
|
||||
(deferred:nextc it
|
||||
(deferred:lambda ()
|
||||
(let ((cell (ein:shared-output-get-cell)))
|
||||
(if (not (null (slot-value cell 'running)))
|
||||
(deferred:nextc (deferred:wait 50) self)))))
|
||||
(deferred:nextc it
|
||||
(lambda ()
|
||||
(let* ((cell (ein:shared-output-get-cell))
|
||||
(raw (if (and (slot-boundp cell 'traceback)
|
||||
(slot-value cell 'traceback))
|
||||
(ansi-color-apply (apply #'concat (mapcar #'(lambda (s)
|
||||
(format "%s\n" s))
|
||||
(slot-value cell 'traceback))))
|
||||
(org-babel-ein-process-outputs (slot-value cell 'outputs) params))))
|
||||
(org-babel-result-cond result-params raw
|
||||
(org-babel-python-table-or-string raw)))))
|
||||
(deferred:nextc it
|
||||
(lambda (formatted-result)
|
||||
(ein:ob-ein--execute-async-update formatted-result buffer name))))
|
||||
(format "[[ob-ein-async-running: %s]]" name)))
|
||||
(defsubst ob-ein--execute-async-callback (buffer params result-params name)
|
||||
"Callback of 1-arity (the shared output cell) to update org buffer when
|
||||
`ein:shared-output-eval-string' completes."
|
||||
(apply-partially
|
||||
(lambda (buffer* params* result-params* name* cell)
|
||||
(let* ((raw (ein:aif (ein:oref-safe cell 'traceback)
|
||||
(ansi-color-apply (ein:join-str "\n" it))
|
||||
(ob-ein--process-outputs
|
||||
(ein:oref-safe cell 'outputs) params*)))
|
||||
(result (org-babel-result-cond result-params*
|
||||
raw (org-babel-python-table-or-string raw))))
|
||||
(ein:log 'debug "ob-ein--execute-async-callback %s %s" name* result)
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(with-current-buffer buffer*
|
||||
(org-babel-goto-named-src-block name*)
|
||||
(org-babel-remove-result)
|
||||
(org-babel-insert-result
|
||||
result
|
||||
(cdr (assoc :result-params
|
||||
(third (org-babel-get-src-block-info)))))
|
||||
(org-redisplay-inline-images))))))
|
||||
buffer params result-params name))
|
||||
|
||||
(defun ein:ob-ein--execute-async-update (formatted-result buffer name)
|
||||
(message "Finished deferred ein execution: %s" name)
|
||||
(with-current-buffer buffer
|
||||
(save-excursion
|
||||
(org-babel-goto-named-result name)
|
||||
(search-forward (format "[[ob-ein-async-running: %s]]" name))
|
||||
(re-search-backward "\\(call\\|src\\)_\\|^[ \t]*#\\+\\(BEGIN_SRC\\|CALL:\\)")
|
||||
(org-babel-remove-result)
|
||||
(org-babel-insert-result
|
||||
formatted-result
|
||||
(cdr (assoc :result-params (nth 2 (org-babel-get-src-block-info)))))
|
||||
(org-redisplay-inline-images)
|
||||
;; (when (member "drawer" (cdr (assoc :result-params params)))
|
||||
;; ;; open the results drawer
|
||||
;; (org-babel-goto-named-result name)
|
||||
;; (forward-line)
|
||||
;; (org-flag-drawer nil))
|
||||
)))
|
||||
(defun ob-ein--execute-async (buffer body kernel params result-params name)
|
||||
"As `ein:shared-output-get-cell' is a singleton, ob-ein can only execute blocks
|
||||
one at a time. Further, we do not order the queued up blocks!"
|
||||
(deferred:$
|
||||
(deferred:next
|
||||
(deferred:lambda ()
|
||||
(let ((cell (ein:shared-output-get-cell)))
|
||||
(if (eq (slot-value cell 'callback) #'ignore)
|
||||
(let ((callback
|
||||
(ob-ein--execute-async-callback buffer params
|
||||
result-params name)))
|
||||
(setf (slot-value cell 'callback) callback))
|
||||
(deferred:nextc (deferred:wait 1200) self)))))
|
||||
(deferred:nextc it
|
||||
(lambda (_x)
|
||||
(ein:shared-output-eval-string kernel body nil)))))
|
||||
|
||||
(defun ein:ob-ein--execute (full-body session-kernel processed-params)
|
||||
(let* ((d (ein:shared-output-eval-string full-body nil nil session-kernel))
|
||||
(cell (ein:shared-output-get-cell)))
|
||||
(deferred:sync! d)
|
||||
(ein:wait-until #'(lambda ()
|
||||
(null (slot-value cell 'running)))
|
||||
nil ein:org-execute-timeout)
|
||||
(if (and (slot-boundp cell 'traceback)
|
||||
(slot-value cell 'traceback))
|
||||
(ansi-color-apply (apply #'concat (mapcar #'(lambda (s)
|
||||
(format "%s\n" s))
|
||||
(slot-value cell 'traceback))))
|
||||
(org-babel-ein-process-outputs (slot-value cell 'outputs) processed-params))))
|
||||
|
||||
|
||||
(defun ein:org-find-or-open-session (session &optional kernelspec)
|
||||
(multiple-value-bind (url-or-port path) (ein:org-babel-parse-session session)
|
||||
(setf kernelspec (or kernelspec (ein:get-kernelspec url-or-port "default")))
|
||||
(let ((nb (or (ein:notebook-get-opened-notebook url-or-port path)
|
||||
(ein:notebook-open url-or-port
|
||||
path
|
||||
kernelspec
|
||||
(apply-partially
|
||||
(lambda (session* kernelspec* _notebook _created)
|
||||
(org-babel-ein-initiate-session session* kernelspec*))
|
||||
session kernelspec)))))
|
||||
|
||||
(loop repeat 4
|
||||
until (ein:kernel-live-p (ein:$notebook-kernel nb))
|
||||
do (sit-for 1.0))
|
||||
nb)))
|
||||
|
||||
(defun org-babel-edit-prep:ein (babel-info)
|
||||
"Set up source code completion for editing an EIN source block."
|
||||
(let ((nb (ein:org-find-or-open-session (cdr (assoc :session (third babel-info))))))
|
||||
(ein:connect-buffer-to-notebook nb (current-buffer) t)
|
||||
(define-key ein:connect-mode-map "\C-c\C-c" 'org-babel-edit:ein-execute)))
|
||||
|
||||
(defun org-babel-edit:ein-execute ()
|
||||
(defun ob-ein--edit-ctrl-c-ctrl-c ()
|
||||
"C-c C-c mapping in ein:connect-mode-map."
|
||||
(interactive)
|
||||
(org-edit-src-save)
|
||||
(when (boundp 'org-src--beg-marker)
|
||||
|
@ -275,66 +199,88 @@ jupyter kernels.
|
|||
(goto-char beg)
|
||||
(org-ctrl-c-ctrl-c))))))
|
||||
|
||||
;; This function should be used to assign any variables in params in
|
||||
;; the context of the session environment.
|
||||
(defun org-babel-prep-session:ein (_session _params)
|
||||
"Prepare SESSION according to the header arguments specified in PARAMS."
|
||||
)
|
||||
;;;###autoload
|
||||
(defun org-babel-edit-prep:ein (babel-info)
|
||||
"C-c ' enters the lightly tested connect-to-notebook mode."
|
||||
(let* ((buffer (current-buffer))
|
||||
(processed-params (org-babel-process-params (third babel-info))))
|
||||
(ob-ein--initiate-session
|
||||
(format "%s" (cdr (assoc :session processed-params)))
|
||||
(or (cdr (assoc :kernelspec processed-params)) "default")
|
||||
(lambda (notebook)
|
||||
(ein:connect-buffer-to-notebook notebook buffer t)
|
||||
(define-key ein:connect-mode-map "\C-c\C-c" 'ob-ein--edit-ctrl-c-ctrl-c)))))
|
||||
|
||||
(defun org-babel-ein-var-to-template (var)
|
||||
"Convert an elisp var into a string of template source code
|
||||
specifying a var of the same value."
|
||||
(format "%S" var))
|
||||
(defun ob-ein--parse-session (session)
|
||||
(multiple-value-bind (url-or-port _password) (ein:jupyter-server-conn-info)
|
||||
(let ((tokens (split-string session "/"))
|
||||
(parsed-url (url-generic-parse-url session)))
|
||||
(cond ((null (url-host parsed-url))
|
||||
(let* ((candidate (apply #'ein:url (car tokens) (cdr tokens)))
|
||||
(parsed-candidate (url-generic-parse-url candidate))
|
||||
(missing (url-scheme-get-property
|
||||
(url-type parsed-candidate)
|
||||
'default-port)))
|
||||
(if (and url-or-port
|
||||
(= (url-port parsed-candidate) missing))
|
||||
(apply #'ein:url url-or-port (cdr tokens))
|
||||
candidate)))
|
||||
(t (ein:url session))))))
|
||||
|
||||
(defun org-babel-ein-table-or-string (_results)
|
||||
"If the results look like a table, then convert them into an
|
||||
Emacs-lisp table, otherwise return the results as a string."
|
||||
)
|
||||
(defun ob-ein--initiate-session (session kernelspec callback)
|
||||
"Retrieve notebook based on SESSION path and KERNELSPEC, starting jupyter instance
|
||||
if necessary. Install CALLBACK (i.e., cell execution) upon notebook retrieval."
|
||||
(let* ((nbpath (ob-ein--parse-session session))
|
||||
(parsed-url (url-generic-parse-url nbpath))
|
||||
(slash-path (car (url-path-and-query parsed-url)))
|
||||
(path (if (string= slash-path "")
|
||||
ob-ein-anonymous-path
|
||||
(substring slash-path 1)))
|
||||
(url-or-port (if (string= slash-path "")
|
||||
nbpath
|
||||
(substring nbpath 0 (- (length slash-path)))))
|
||||
(notebook (ein:notebook-get-opened-notebook url-or-port path))
|
||||
(callback-nbopen (lambda (nb _created)
|
||||
(loop repeat 50
|
||||
for live-p = (ein:kernel-live-p (ein:$notebook-kernel nb))
|
||||
until live-p
|
||||
do (sleep-for 0 300)
|
||||
finally
|
||||
do (if (not live-p)
|
||||
(ein:log 'error
|
||||
"Kernel for %s failed to launch"
|
||||
(ein:$notebook-notebook-name nb))
|
||||
(funcall callback nb)))))
|
||||
(errback-nbopen (lambda (url-or-port status-code)
|
||||
(if (eq status-code 404)
|
||||
(ein:notebooklist-new-notebook-with-name
|
||||
url-or-port kernelspec path callback-nbopen t))))
|
||||
(callback-login (lambda (_buffer url-or-port)
|
||||
(ein:notebook-open url-or-port path kernelspec
|
||||
callback-nbopen errback-nbopen t))))
|
||||
(cond (notebook (funcall callback notebook))
|
||||
((string= (url-host parsed-url) ein:url-localhost)
|
||||
(ein:process-refresh-processes)
|
||||
(ein:aif (ein:process-url-match nbpath)
|
||||
(ein:notebooklist-login (ein:process-url-or-port it) callback-login)
|
||||
(ein:jupyter-server-start
|
||||
(executable-find ein:jupyter-default-server-command)
|
||||
(read-directory-name "Notebook directory: " default-directory)
|
||||
nil
|
||||
callback-login
|
||||
(let* ((port (url-port parsed-url))
|
||||
(avoid (url-scheme-get-property (url-type parsed-url) 'default-port)))
|
||||
(cond ((= port avoid) nil)
|
||||
(t (url-port parsed-url)))))))
|
||||
(t (ein:notebooklist-login url-or-port callback-login)))))
|
||||
|
||||
(defun ein:org-babel-clean-url (url-or-port)
|
||||
(if (search ":" url-or-port)
|
||||
url-or-port
|
||||
(string-to-number url-or-port)))
|
||||
;;;###autoload
|
||||
(with-eval-after-load "python"
|
||||
(setq python-indent-guess-indent-offset-verbose nil))
|
||||
|
||||
(defun ein:org-babel-parse-session (session)
|
||||
(if (numberp session)
|
||||
(values (ein:url (format "http://localhost:%s" session)) nil)
|
||||
(let ((session-uri (url-generic-parse-url session)))
|
||||
(cond ((url-fullness session-uri)
|
||||
(values (ein:url (format "%s://%s:%s" (url-type session-uri) (url-host session-uri) (url-port session-uri)))
|
||||
(url-filename session-uri)))
|
||||
(t (let* ((url-or-port (ein:org-babel-clean-url (car (split-string session "/"))))
|
||||
(path (ein:join-str "/" (rest (split-string session "/")))))
|
||||
(values (ein:url (format "http://localhost:%s" url-or-port)) path)))))))
|
||||
|
||||
(defcustom ein:org-babel-default-session-name "ein_babel_session.ipynb"
|
||||
"Default name for org babel sessions running ein environments.
|
||||
This is the name of the notebook used when no notebook path is
|
||||
given in the session parameter."
|
||||
:type '(string :tag "Format string")
|
||||
:group 'ein)
|
||||
|
||||
|
||||
(defun org-babel-ein-initiate-session (&optional session kernelspec)
|
||||
"If there is not a current inferior-process-buffer in SESSION then create.
|
||||
Return the initialized session."
|
||||
(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)
|
||||
(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)))
|
||||
(ein:notebooklist-new-notebook-with-name name kernelspec url-or-port)
|
||||
(org-babel-ein-initiate-session new-session kernelspec)))
|
||||
(t (let ((nb (ein:org-find-or-open-session session kernelspec)))
|
||||
(ein:$notebook-kernel nb))))))
|
||||
;;;###autoload
|
||||
(add-hook 'org-mode-hook (lambda ()
|
||||
(add-to-list 'org-src-lang-modes '("ein" . python))
|
||||
(add-to-list 'org-src-lang-modes '("ein-hy" . hy))))
|
||||
|
||||
(provide 'ob-ein)
|
||||
;;; ob-ein.el ends here
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
* Tests
|
||||
|
||||
This is a [[ipynb:(:url-or-port%20"http://localhost:8888"%20:name%20"emacs-ipython-notebook/The%20Emacs%20IPython%20Notebook.ipynb")][link]] to an ein notebook. It might not work if your setup is not the same as mine!
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
(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-open--callback notebook nil (ein:new-content (ein:$notebook-url-or-port notebook) (ein:$notebook-notebook-path notebook) data))
|
||||
(ein:notebook-open--callback notebook nil nil (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)
|
||||
|
|
|
@ -93,21 +93,29 @@ if I call this between links in a deferred chain. Adding a flush-queue."
|
|||
continue)
|
||||
(error "Timeout: %s" predicate))))
|
||||
|
||||
(defun ein:testing-new-notebook (url-or-port ks)
|
||||
(defun ein:testing-new-notebook (url-or-port ks &optional retry)
|
||||
(lexical-let (notebook)
|
||||
(condition-case err
|
||||
(progn
|
||||
(ein:notebooklist-new-notebook url-or-port ks nil
|
||||
(lambda (nb created &rest ignore)
|
||||
(ein:testing-wait-until (lambda ()
|
||||
(ein:notebooklist-list-get url-or-port))
|
||||
nil 10000 1000)
|
||||
(ein:notebooklist-new-notebook url-or-port ks
|
||||
(lambda (nb created)
|
||||
(setq notebook nb)))
|
||||
(ein:testing-wait-until (lambda ()
|
||||
(and notebook
|
||||
(ein:aand (ein:$notebook-kernel notebook)
|
||||
(ein:kernel-live-p it))))
|
||||
nil 10000 2000)
|
||||
nil 10000 1000)
|
||||
notebook)
|
||||
(error (message "ein:testing-new-notebook: %s" (error-message-string err))
|
||||
nil))))
|
||||
(error (let ((notice (format "ein:testing-new-notebook: [%s] %s"
|
||||
url-or-port (error-message-string err))))
|
||||
(if retry
|
||||
(progn (ein:log 'error notice) nil)
|
||||
(ein:log 'info notice)
|
||||
(sleep-for 0 1500)
|
||||
(ein:testing-new-notebook url-or-port ks t)))))))
|
||||
|
||||
(defadvice ert-run-tests-batch (after ein:testing-dump-logs-hook activate)
|
||||
"Hook `ein:testing-dump-logs-hook' because `kill-emacs-hook'
|
||||
|
|
|
@ -17,12 +17,12 @@ sys.version
|
|||
")
|
||||
|
||||
(ert-deftest ein:ob-aware ()
|
||||
(let ((org-babel-load-languages (quote ((ipython . t) (ein . t)))))
|
||||
(let ((org-babel-load-languages (quote ((ein . t)))))
|
||||
(with-temp-buffer
|
||||
(save-excursion
|
||||
(org-mode)
|
||||
(insert eintest:ob-src-block)
|
||||
(search-backward "SRC")
|
||||
(cl-letf (((symbol-function 'ein:org-find-or-open-session) (lambda (&rest args) (make-ein:$notebook))))
|
||||
(setq python-indent-guess-indent-offset-verbose nil)
|
||||
(cl-letf (((symbol-function 'ob-ein--initiate-session)
|
||||
(lambda (&rest args) (make-ein:$notebook))))
|
||||
(should (call-interactively #'org-edit-special)))))))
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
(should (equal (ein:url 8888) "http://127.0.0.1:8888"))
|
||||
(should (equal (ein:url "http://:8000") "http://127.0.0.1:8000"))
|
||||
(should (equal (ein:url "http://localhost") "http://127.0.0.1"))
|
||||
(should (equal (ein:url "https://localhost:8888") "https://127.0.0.1:8888"))
|
||||
(should (equal (ein:url "https://localhost:8888") "http://127.0.0.1:8888"))
|
||||
(should (equal (ein:url "http://localhost:8000" "" "" "" "Untitled.ipynb") "http://127.0.0.1:8000/Untitled.ipynb"))
|
||||
(should (equal (ein:url "http://localhost:8000" "") "http://127.0.0.1:8000"))
|
||||
(should (equal (ein:url "http://localhost:8000") "http://127.0.0.1:8000"))
|
||||
(should (equal (ein:url "localhost" "foo" "bar") "http://127.0.0.1/foo/bar"))
|
||||
(should (equal (ein:url "datasci-1:8888") "https://datasci-1:8888"))
|
||||
(should (equal (ein:url "datasci-1" "foo" "bar") "https://datasci-1/foo/bar"))
|
||||
(should (equal (ein:glom-paths "" nil "Untitled1.ipynb") "Untitled1.ipynb"))
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"TESTING-GET-UNTITLED0-OR-CREATE creating notebook")
|
||||
(lexical-let (done-p
|
||||
(kernelspec (ein:get-kernelspec url-or-port "default")))
|
||||
(ein:notebooklist-new-notebook url-or-port kernelspec path
|
||||
(ein:notebooklist-new-notebook url-or-port kernelspec
|
||||
(lambda (notebook created)
|
||||
(setq *ein:testing-notebook-name*
|
||||
(ein:$notebook-notebook-name notebook))
|
||||
|
@ -46,7 +46,9 @@
|
|||
(prog1
|
||||
(ein:testing-get-notebook url-or-port path *ein:testing-notebook-name*)
|
||||
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
|
||||
(deferred:sync! (ein:notebooklist-reload nil t)))
|
||||
(lexical-let (done-p)
|
||||
(ein:notebooklist-reload nil t (lambda (&rest args) (setq done-p t)))
|
||||
(ein:testing-wait-until (lambda () done-p) nil 10000 1000)))
|
||||
(ein:log 'debug "TESTING-GET-UNTITLED0-OR-CREATE end"))))))
|
||||
|
||||
(defvar ein:notebooklist-after-open-hook nil)
|
||||
|
@ -96,13 +98,21 @@
|
|||
(ein:log 'verbose "----------------------------------")
|
||||
(ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 start")
|
||||
(with-current-buffer (ein:notebooklist-get-buffer *ein:testing-port*)
|
||||
(let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
|
||||
(should (member (ein:url *ein:testing-port* (ein:$notebook-notebook-path notebook))
|
||||
(ein:notebooklist-list-paths "notebook")))
|
||||
(let* ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*))
|
||||
(the-url (ein:url *ein:testing-port* (ein:$notebook-notebook-path notebook))))
|
||||
(should (member the-url (ein:notebooklist-list-paths "notebook")))
|
||||
(ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 deleting notebook")
|
||||
(ein:notebooklist-delete-notebook (ein:$notebook-notebook-path notebook))
|
||||
(deferred:sync! (ein:notebooklist-reload ein:%notebooklist% t))
|
||||
(should-not (member (ein:url *ein:testing-port* (ein:$notebook-notebook-path notebook)) (ein:notebooklist-list-paths "notebook")))))
|
||||
(lexical-let (done-p)
|
||||
(ein:notebooklist-delete-notebook
|
||||
(ein:$notebook-notebook-path notebook)
|
||||
(lambda (&rest args) (setq done-p t)))
|
||||
(ein:testing-wait-until (lambda () done-p) nil 10000 1000))
|
||||
(lexical-let (done-p)
|
||||
(ein:content-query-hierarchy
|
||||
(ein:url *ein:testing-port*)
|
||||
(lambda (&rest args) (setq done-p t)))
|
||||
(ein:testing-wait-until (lambda () done-p) nil 10000 1000))
|
||||
(should-not (member the-url (ein:notebooklist-list-paths "notebook")))))
|
||||
(ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 end"))
|
||||
|
||||
(ert-deftest 11-notebook-execute-current-cell-simple ()
|
||||
|
|
Loading…
Add table
Reference in a new issue