Merge pull request #58 from dickmao/dev

Dev
This commit is contained in:
dickmao 2020-01-07 18:46:02 -05:00 committed by GitHub
commit 0ee5f01fe4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 265 additions and 809 deletions

View file

@ -51,7 +51,7 @@ jobs:
- uses: actions/cache@v1
with:
path: ~/local
key: ${{ runner.os }}-local
key: ${{ runner.os }}-local-julia13
- uses: actions/cache@v1
with:

View file

@ -1,40 +0,0 @@
Getting started
---------------
Fork the repo on github. Clone the fork to your home directory.
Install cask. Run `make dist` to ensure correct cask functionality.
Run `make test` to ensure a correct baseline.
Until we institute a virtualenv for the required testing software (jupyter, R, matplotlib, etc.), out-of-the-box `make test` remains problematic.
Remove the MELPA-installed EIN by deleting the package directory (on my system, it's `~/.emacs.d/elpa/ein-20190122.1341`) or running `M-x package-delete`.
In your `init.el` or `.emacs`, add the following:
```
(add-to-list 'load-path "~/emacs-ipython-notebook/lisp")
(load "ein-autoloads")
```
Now whatever changes you make to the repo will be reflected in new emacs instances.
Dev tools
---------
`M-x ein:dev-start-debug` activates full logging and backtrace on error.
Quick sanity checking
---------------------
`make quick` runs a syntax check and the unit tests. It is far quicker than the laborious `make test`.
Unit tests
----------
Located in `~/emacs-ipython-notebook/test`.
Integration tests
-----------------
If you add a feature, we encourage writing an integration test.
`cask exec ecukes` is the bulk of `make test`. Ecukes is our friend and guardian. We follow its opinionated file structure in `~/emacs-ipython-notebook/features`.
To run say just the login tests, `cask exec ecukes --tags "@login"`.

View file

@ -1,4 +1,5 @@
EMACS ?= $(shell which emacs)
export EMACS ?= $(shell which emacs)
CASK_DIR := $(shell EMACS=$(EMACS) cask package-directory)
SRC=$(shell cask files)
PKBUILD=2.3
ELCFILES = $(SRC:.el=.elc)
@ -52,14 +53,17 @@ clean:
dist-clean: clean
rm -rf dist
.PHONY: cask
cask: $(CASK_DIR)
$(CASK_DIR): Cask
cask install
.PHONY: test-compile
test-compile: clean autoloads
cask install
! (cask eval "(let ((byte-compile-error-on-warn t)) (cask-cli/build))" 2>&1 | egrep -a "(Warning|Error):") ; (ret=$$? ; cask clean-elc && exit $$ret)
.PHONY: quick
quick: test-compile test-ob-ein-recurse test-unit
quick: cask test-compile test-ob-ein-recurse test-unit
.PHONY: test-jupyterhub
test-jupyterhub: test-compile

View file

@ -41,11 +41,11 @@ Scenario: kernel reconnect succeeds
And I switch to log expr "ein:log-all-buffer-name"
Then I should see "WS closed unexpectedly"
And I switch to buffer like "Untitled"
And header says "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command]"
And header says "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command-km]"
And I clear log expr "ein:log-all-buffer-name"
And I press "C-c C-r"
And I wait for the smoke to clear
And header does not say "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command]"
And header does not say "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command-km]"
And I switch to log expr "ein:log-all-buffer-name"
Then I should not see "[warn]"
And I should not see "[error]"
@ -55,10 +55,10 @@ Scenario: kernel reconnect succeeds
And I switch to log expr "ein:log-all-buffer-name"
Then I should see "WS closed unexpectedly"
And I switch to buffer like "Untitled"
And header says "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command]"
And header says "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command-km]"
And I clear log expr "ein:log-all-buffer-name"
And I wait for cell to execute
And header does not say "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command]"
And header does not say "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command-km]"
And I switch to log expr "ein:log-all-buffer-name"
Then I should not see "[warn]"
And I should not see "[error]"
@ -72,7 +72,7 @@ Scenario: kernel reconnect succeeds
And I should see "ein:kernel-retrieve-session--complete"
And I switch to buffer like "Untitled"
And I kill kernel
And header says "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command]"
And header says "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command-km]"
And I clear log expr "ein:log-all-buffer-name"
And my reconnect is questioned
@ -98,7 +98,6 @@ Scenario: Assign variable, save, kill notebook buffer, get it back, check variab
And I type "b"
And I wait for cell to execute
Then I should see "3.1415"
And I press "C-x k"
@julia
Scenario: Smoke test julia

View file

@ -14,6 +14,7 @@ Scenario: New Notebook
Given I am in notebooklist buffer
When I clear log expr "ein:log-all-buffer-name"
And I click on "New Notebook"
And no notebooks pending
And I switch to log expr "ein:log-all-buffer-name"
Then I should see "Opened notebook Untitled"
@ -28,9 +29,11 @@ Scenario: Resync
Scenario: Stop after closing notebook
Given I am in notebooklist buffer
And I click on "New Notebook"
And no notebooks pending
And I switch to buffer like "Untitled"
And I press "C-x k"
And I am in notebooklist buffer
And I clear log expr "ein:log-all-buffer-name"
And I keep clicking "Resync" until "Stop"
And I click on "Stop"
And I switch to log expr "ein:log-all-buffer-name"
@ -38,7 +41,10 @@ Scenario: Stop after closing notebook
And I am in notebooklist buffer
And I go to word "Untitled"
And I go to beginning of line
And I dump buffer
And I click without going top on "Open"
And no notebooks pending
And I switch to buffer like "Untitled"
@content
Scenario: Read a massive directory

View file

@ -113,7 +113,7 @@ Scenario: Specific port, portless localhost refers to same, concurrent execution
And I should not see "[....]"
@org
Scenario: portless url with path, image, C-c ' lets you C-c C-c as well
Scenario: portless url with path, image
When I open temp file "path.org"
And I call "org-mode"
And I type "<s"
@ -134,18 +134,8 @@ Scenario: portless url with path, image, C-c ' lets you C-c C-c as well
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 dump buffer
@export
Scenario: Test ob-exp captures code and results.

View file

@ -115,9 +115,23 @@
(lambda (log-expr)
(switch-to-buffer (symbol-value (intern log-expr)))))
(When "^no notebooks pending$"
(lambda ()
(cl-loop repeat 10
until (zerop (hash-table-count *ein:notebook--pending-query*))
do (sleep-for 0 500)
finally do (should (zerop (hash-table-count *ein:notebook--pending-query*))))))
(When "^I switch to buffer like \"\\(.+\\)\"$"
(lambda (substr)
(switch-to-buffer (car (-non-nil (mapcar (lambda (b) (if (search substr (buffer-name b)) b)) (buffer-list)))))))
(cl-loop repeat 10
for buf = (seq-some (lambda (b)
(and (search substr (buffer-name b)) b))
(buffer-list))
until (buffer-live-p buf)
do (sleep-for 0 500)
finally do (and (should (buffer-live-p buf))
(switch-to-buffer buf)))))
(When "^rename notebook to \"\\(.+\\)\" succeeds$"
(lambda (new-name)
@ -293,7 +307,7 @@
(When "^I click\\( without going top\\)? on \"\\(.+\\)\"$"
(lambda (stay word)
;; from espuds "go to word" without the '\\b's
(when (not stay)
(unless stay
(goto-char (point-min)))
(let ((search (re-search-forward (format "\\[%s\\]" word) nil t))
(msg "Cannot go to link '%s' in buffer: %s"))

View file

@ -41,18 +41,20 @@
(with-current-buffer (ein:notebooklist-get-buffer (car (ein:jupyter-server-conn-info)))
(cl-loop for notebook in (ein:notebook-opened-notebooks)
for path = (ein:$notebook-notebook-path notebook)
do (ein:notebook-kill-kernel-then-close-command notebook)
for done-p = nil
do (ein:notebook-kill-kernel-then-close-command
notebook (lambda (_kernel) (setq done-p t)))
do (cl-loop repeat 16
until (not (ein:notebook-live-p notebook))
until done-p
do (sleep-for 0 1000)
finally do (when (ein:notebook-live-p notebook)
finally do (unless done-p
(ein:display-warning (format "cannot close %s" path))))
do (when (or (ob-ein-anonymous-p path)
(search "Untitled" path)
(search "Renamed" path))
(ein:notebooklist-delete-notebook path)
(cl-loop repeat 16
with fullpath = (concat (file-name-as-directory ein:testing-jupyter-server-root) path)
(cl-loop with fullpath = (concat (file-name-as-directory ein:testing-jupyter-server-root) path)
repeat 10
for extant = (file-exists-p fullpath)
until (not extant)
do (sleep-for 0 1000)
@ -62,7 +64,10 @@
(cl-loop for nb in it
for path = (ein:$notebook-notebook-path nb)
do (ein:log 'debug "Notebook %s still open" path)
finally do (assert nil))))
finally do (assert nil)))
(let ((stragglers (file-name-all-completions "Untitled"
ein:testing-jupyter-server-root)))
(should-not stragglers)))
(Setup
(ein:dev-start-debug)

View file

@ -356,10 +356,10 @@ auto-execution mode flag in the connected buffer is `t'.")))
(status_busy.Kernel . "Kernel is busy...")
(status_restarting.Kernel . "Kernel restarting...")
(status_restarted.Kernel . "Kernel restarted")
(status_dead.Kernel . "Kernel requires restart \\<ein:notebook-mode-map>\\[ein:notebook-restart-session-command]")
(status_dead.Kernel . "Kernel requires restart \\<ein:notebook-mode-map>\\[ein:notebook-restart-session-command-km]")
(status_reconnecting.Kernel . "Kernel reconnecting...")
(status_reconnected.Kernel . "Kernel reconnected")
(status_disconnected.Kernel . "Kernel requires reconnect \\<ein:notebook-mode-map>\\[ein:notebook-reconnect-session-command]")))
(status_disconnected.Kernel . "Kernel requires reconnect \\<ein:notebook-mode-map>\\[ein:notebook-reconnect-session-command-km]")))
:type ein:notification-status))
"Notification widget for Notebook.")

View file

@ -184,21 +184,6 @@ callback (`websocket-callback-debug-on-error') is enabled."
(websocket-get-debug-buffer-create
(ein:$websocket-ws channel)))))
(defun ein:dev-notebook-plain-mode ()
"Use `ein:notebook-plain-mode'."
(interactive)
(setq ein:notebook-modes '(ein:notebook-plain-mode)))
(defun ein:dev-notebook-python-mode ()
"Use `ein:notebook-python-mode'."
(interactive)
(setq ein:notebook-modes '(ein:notebook-python-mode)))
(defun ein:dev-notebook-multilang-mode ()
"Use `ein:notebook-multilang-mode'."
(interactive)
(setq ein:notebook-modes '(ein:notebook-multilang-mode)))
(defun ein:dev-sys-info--lib (name)
(let* ((libsym (intern-soft name))
(version-var (cl-loop for fmt in '("%s-version" "%s:version")

View file

@ -109,6 +109,16 @@ with the call to the jupyter notebook."
(defvar *ein:jupyter-server-buffer-name*
(format "*%s*" *ein:jupyter-server-process-name*))
(defun ein:jupyter-get-default-kernel (kernels)
(cond (ein:%notebooklist-new-kernel%
(ein:$kernelspec-name ein:%notebooklist-new-kernel%))
((eq ein:jupyter-default-kernel 'first-alphabetically)
(car (car kernels)))
((stringp ein:jupyter-default-kernel)
ein:jupyter-default-kernel)
(t
(symbol-name ein:jupyter-default-kernel))))
(defun ein:jupyter-process-lines (url-or-port command &rest args)
"If URL-OR-PORT registered as a k8s url, preface COMMAND ARGS with `kubectl exec'."
(condition-case err
@ -197,6 +207,49 @@ our singleton jupyter server process here."
(funcall #'ein:notebooklist-sentinel url-or-port* proc* event))
url-or-port (process-sentinel proc))))
(defun ein:jupyter-crib-token (url-or-port)
"Shell out to jupyter for its credentials knowledge. Return list of (PASSWORD TOKEN)."
(aif (cl-loop for line in
(apply #'ein:jupyter-process-lines url-or-port
ein:jupyter-server-command
(split-string
(format "%s%s %s"
(aif ein:jupyter-server-use-subcommand
(concat it " ") "")
"list" "--json")))
with token0
with password0
when (destructuring-bind
(&key password url token &allow-other-keys)
(ein:json-read-from-string line)
(prog1 (or (equal (ein:url url) url-or-port)
(equal (url-host (url-generic-parse-url url))
"0.0.0.0"))
(setq password0 password) ;; t or :json-false
(setq token0 token)))
return (list password0 token0))
it (list nil nil)))
(defun ein:jupyter-crib-running-servers ()
"Shell out to jupyter for running servers."
(nconc
(cl-loop for line in
(apply #'ein:jupyter-process-lines nil
ein:jupyter-server-command
(split-string
(format "%s%s %s"
(aif ein:jupyter-server-use-subcommand
(concat it " ") "")
"list" "--json")))
collecting (destructuring-bind
(&key url &allow-other-keys)
(ein:json-read-from-string line)
(ein:url url)))
(aif (ein:k8s-service-url-or-port) (list it))))
;;;###autoload
(defun ein:jupyter-server-start (server-command
notebook-directory
@ -277,14 +330,6 @@ server command."
;;;###autoload
(defalias 'ein:stop 'ein:jupyter-server-stop)
(defun ein:undocumented-shutdown (url-or-port)
(ein:query-singleton-ajax
(list 'shutdown-server url-or-port)
(ein:url url-or-port "api/shutdown")
:type "POST"
:timeout 3 ;; content-query-timeout and query-timeout default nil
:sync t))
;;;###autoload
(defun ein:jupyter-server-stop (&optional force log)
(interactive)

View file

@ -35,14 +35,18 @@
:type 'string
:group 'ein)
(defun ein:k8s-select-context ()
(interactive)
(defun ein:k8s-select-context (&optional query-p)
(interactive "p")
(when (or query-p
(null (kubernetes-state-config (kubernetes-state))))
(kubernetes-contexts-refresh-now)
(if-let ((contexts (ein:k8s-get-contexts)))
(let ((desired-context
(ein:completing-read "Select context: " contexts nil t)))
(message "Rereading state...")
(kubernetes-state-clear)
(let ((response (kubernetes-kubectl-await
(let ((response
(kubernetes-kubectl-await
(apply-partially #'kubernetes-kubectl
kubernetes-props
(kubernetes-state)
@ -67,25 +71,22 @@
(unless (string= current-name desired-context)
(error "ein:k8s-select-context': could not update state for %s"
desired-context))
(if (kubernetes-kubectl-await
(apply-partially #'kubernetes-kubectl
kubernetes-props
(kubernetes-state)
(split-string "get nodes -o json"))
(lambda (buf)
(prog1 t
(with-current-buffer buf
(kubernetes-state-update-nodes
(json-read-from-string (buffer-string))))))
nil #'ignore)
(message "Selected %s" current-name)
(if (kubernetes-nodes-refresh-now)
(progn
(mapc (lambda (resource)
(when-let ((refresh-f
(intern-soft (format "kubernetes-%s-refresh-now" resource))))
(when (fboundp refresh-f)
(funcall refresh-f))))
(cl-remove-if (apply-partially #'eq 'nodes)
(mapcar #'car kubernetes-overview-views-alist)))
(message ""))
(error "ein:k8s-select-context: %s is down" current-name))))
(error "ein:k8s-select-context: use-context returned %s, expected %s"
response desired-context))))
(error "ein:k8s-select-context: No contexts found")))
(error "ein:k8s-select-context: No contexts found"))))
(defun ein:k8s-get-contexts ()
(kubernetes-contexts-refresh-now)
(let ((response (kubernetes-kubectl-await-on-async kubernetes-props
(kubernetes-state)
#'kubernetes-kubectl-config-view)))
@ -96,7 +97,6 @@
names)))
(defun ein:k8s-get-deployment ()
(kubernetes-deployments-refresh-now)
(-let* [(deployments (kubernetes-state-deployments (kubernetes-state)))
((&alist 'items items) deployments)]
(seq-some (lambda (it)
@ -105,12 +105,10 @@
items)))
(defun ein:k8s-get-pod ()
(kubernetes-pods-refresh-now)
(when-let ((deployment (ein:k8s-get-deployment)))
(cl-first (kubernetes-overview--pods-for-deployment (kubernetes-state)
deployment))))
(defun ein:k8s-get-service ()
(kubernetes-services-refresh-now)
(-let* [(services (kubernetes-state-services (kubernetes-state)))
((&alist 'items items) services)]
(seq-some (lambda (it)
@ -119,21 +117,20 @@
items)))
(defun ein:k8s-get-node ()
(kubernetes-nodes-refresh-now)
(-when-let* ((pod (ein:k8s-get-pod))
((&alist 'spec (&alist 'nodeName)) pod)
(node (kubernetes-state-lookup-node nodeName (kubernetes-state)))
((&alist 'metadata (&alist 'name)) node))
node))
(defsubst ein:k8s-p ()
(defsubst ein:k8s-ensure ()
(and (executable-find kubernetes-kubectl-executable)
(or (kubernetes-state-current-context (kubernetes-state))
(unless noninteractive
(condition-case err
(progn
(ein:k8s-select-context)
(error (ein:log 'info "ein:k8s-p %s" (error-message-string err))
nil))))))
(kubernetes-state-current-context (kubernetes-state)))
(error (ein:log 'info "ein:k8s-ensure: %s" (error-message-string err))
nil))))
(defsubst ein:k8s-in-cluster (addr)
"Is ein client inside the k8s cluster?"
@ -149,8 +146,8 @@
(kubernetes-state))))))
(defun ein:k8s-service-url-or-port ()
(-when-let* ((k8s-p (ein:k8s-p))
(service (ein:k8s-get-service))
(ein:k8s-ensure)
(-when-let* ((service (ein:k8s-get-service))
((&alist 'spec (&alist 'ports [(&alist 'nodePort)])) service)
(node (ein:k8s-get-node))
((&alist 'status (&alist 'addresses)) node)

View file

@ -1,78 +0,0 @@
;;; ein-multilang-fontify.el --- Syntax highlighting for multiple-languages
;; Copyright (C) 2012 Takafumi Arakaki
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
;; This file is NOT part of GNU Emacs.
;; ein-multilang-fontify.el is free software: you can redistribute it
;; and/or modify it under the terms of the GNU General Public License
;; as published by the Free Software Foundation, either version 3 of
;; the License, or (at your option) any later version.
;; ein-multilang-fontify.el is distributed in the hope that it will be
;; useful, but WITHOUT ANY WARRANTY; without even the implied warranty
;; of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with ein-multilang-fontify.el.
;; If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
;; It would be nice if org-src is available, but this module should
;; work without org-src. Data on `org-src-lang-modes' is used
;; if this variable is bound.
(require 'org-src nil t)
(defun ein:mlf-get-lang-mode (lang)
"Return major mode for LANG.
Modified version of `org-src-get-lang-mode'."
(when (symbolp lang)
(setq lang (symbol-name lang)))
(intern
(format "%s-mode"
(or (and (bound-and-true-p org-src-lang-modes)
(cdr (assoc lang org-src-lang-modes)))
lang))))
(defun ein:mlf-font-lock-fontify-block (lang start end)
"Patched version of `org-src-font-lock-fontify-block'."
(let ((lang-mode (ein:mlf-get-lang-mode lang)))
(if (fboundp lang-mode)
(let ((string (buffer-substring-no-properties start end))
(modified (buffer-modified-p))
(orig-buffer (current-buffer))
pos
next)
(remove-text-properties start end '(face nil))
(with-current-buffer
(get-buffer-create
(concat " ein:mlf-fontification:" (symbol-name lang-mode)))
(delete-region (point-min) (point-max))
(insert string)
(unless (eq major-mode lang-mode) (funcall lang-mode))
(font-lock-ensure)
(setq pos (point-min))
(cl-loop for next = (next-single-property-change pos 'face nil (point-max))
do (put-text-property
;; `font-lock-face' property is used instead of `font'.
;; This is the only difference from org-src.
(+ start (1- pos)) (+ start next) 'font-lock-face
(get-text-property pos 'face) orig-buffer)
do (setq pos next)
until (eq pos (point-max))))
(add-text-properties
start end
'(font-lock-fontified t fontified t font-lock-multiline t))
(set-buffer-modified-p modified)))))
(provide 'ein-multilang-fontify)
;;; ein-multilang-fontify.el ends here

View file

@ -1,354 +0,0 @@
;;; ein-multilang.el --- Notebook mode with multiple language fontification
;; Copyright (C) 2012 Takafumi Arakaki
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
;; This file is NOT part of GNU Emacs.
;; ein-multilang.el is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; ein-multilang.el is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with ein-multilang.el.
;; If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(eval-when-compile (defvar markdown-mode-map))
(require 'ein-worksheet)
(require 'ein-multilang-fontify)
(require 'python)
(declare-function ess-indent-line "ess")
(declare-function ess-r-eldoc-function "ess-r-completion")
(declare-function ess-setq-vars-local "ess-utils")
(declare-function julia-indent-line "julia-mode")
(defun ein:ml-fontify (limit)
"Fontify next input area comes after the current point then
return `t' or `nil' if not found.
See info node `(elisp) Search-based Fontification'."
(ein:log-ignore-errors
(ein:ml-fontify-1 limit)))
(defun ein:ml-current-or-next-input-cell (ewoc-node)
"Almost identical to `ein:worksheet-next-input-cell' but return
the current cell if EWOC-NODE is the input area node."
(let* ((ewoc-data (ewoc-data ewoc-node))
(cell (ein:$node-data ewoc-data))
(path (ein:$node-path ewoc-data))
(element (nth 1 path)))
(if (memql element '(prompt input))
cell
(ein:cell-next cell))))
(defun ein:ml-fontify-1 (limit)
"Actual implementation of `ein:ml-fontify'.
This function may raise an error."
(ein:and-let* ((pos (point))
(node (ein:worksheet-get-nearest-cell-ewoc-node pos limit))
(cell (ein:ml-current-or-next-input-cell node))
(start (ein:cell-input-pos-min cell))
(end (ein:cell-input-pos-max cell))
((<= end limit))
((< start end))
(lang (ein:cell-language cell)))
(let ((inhibit-read-only t))
(ein:mlf-font-lock-fontify-block lang start end)
;; Emacs fontification mechanism requires the function to move
;; the point. Do *not* use `(goto-char end)'. As END is in the
;; input area, fontification falls into an infinite loop.
(ewoc-goto-node (slot-value cell 'ewoc) (ein:cell-element-get cell :footer)))
t))
(defun ein:ml-back-to-prev-node ()
(ein:aand (ein:worksheet-get-ewoc) (ewoc-goto-prev it 1)))
(defvar ein:ml-font-lock-keywords
'((ein:ml-fontify))
"Default `font-lock-keywords' for `ein:notebook-multilang-mode'.")
(defun ein:ml-set-font-lock-defaults ()
(setq-local font-lock-defaults
'(ein:ml-font-lock-keywords
;; The following are adapted from org-mode but I am not sure
;; if I need them:
t nil nil
ein:ml-back-to-prev-node)))
;;;###autoload
(define-derived-mode ein:notebook-multilang-mode prog-mode "EIN"
"A mode for fontifying multiple languages.
\\{ein:notebook-multilang-mode-map}
"
(setq-local beginning-of-defun-function
'ein:worksheet-beginning-of-cell-input)
(setq-local end-of-defun-function
'ein:worksheet-end-of-cell-input)
(ein:ml-set-font-lock-defaults))
;;; Language setup functions
(defun ein:ml-narrow-to-cell ()
"Narrow to the current cell."
(ein:and-let* ((pos (point))
(node (ein:worksheet-get-nearest-cell-ewoc-node pos))
(cell (ein:ml-current-or-next-input-cell node))
(start (ein:cell-input-pos-min cell))
(end (ein:cell-input-pos-max cell))
((< start end)))
(narrow-to-region start end)))
(defun ein:ml-indent-line-function (lang-func)
(save-restriction
(ein:ml-narrow-to-cell)
(funcall lang-func)))
(defun ein:ml-indent-region (lang-func start end)
(save-restriction
(ein:ml-narrow-to-cell)
(funcall lang-func start end)))
(defun ein:ml-lang-setup-python ()
"Presumably tkf had good reasons to choose only these forms from `python-mode'."
(setq-local mode-name "EIN[Py]")
(setq-local comment-start "# ")
(setq-local comment-start-skip "#+\\s-*")
(setq-local parse-sexp-lookup-properties t)
(setq-local indent-line-function
(apply-partially #'ein:ml-indent-line-function #'python-indent-line-function))
(setq-local indent-region-function
(apply-partially #'ein:ml-indent-region #'python-indent-region))
(set-syntax-table python-mode-syntax-table)
(set-keymap-parent ein:notebook-multilang-mode-map python-mode-map))
(defun ein:ml-lang-setup-julia ()
(require 'julia-mode nil t)
(when (featurep 'julia-mode)
(setq-local mode-name "EIN[julia]")
(setq-local comment-start "# ")
(setq-local comment-start-skip "#+\\s-*")
(setq-local indent-line-function
(apply-partially #'ein:ml-indent-line-function #'julia-indent-line))
(when (boundp 'julia-mode-syntax-table)
(set-syntax-table julia-mode-syntax-table))
(when (boundp 'julia-mode-map)
(set-keymap-parent ein:notebook-multilang-mode-map julia-mode-map))))
(defun ein:ml-lang-setup-R ()
(require 'ess-r-mode nil t)
(require 'ess-custom nil t)
(when (and (featurep 'ess-r-mode) (featurep 'ess-custom))
(setq-local mode-name "EIN[R]")
(when (boundp 'ess-r-customize-alist)
(ess-setq-vars-local ess-r-customize-alist))
(setq-local paragraph-start (concat "\\s-*$\\|" page-delimiter))
(setq-local paragraph-separate (concat "\\s-*$\\|" page-delimiter))
(setq-local paragraph-ignore-fill-prefix t)
(setq-local indent-line-function
(apply-partially #'ein:ml-indent-line-function #'ess-indent-line))
(when (and (boundp 'ess-style) (boundp 'ess-default-style))
(setq-local ess-style ess-default-style))
(when (and (boundp 'prettify-symbols-alist) (boundp 'ess-r-prettify-symbols))
(setq-local prettify-symbols-alist ess-r-prettify-symbols))
(when (boundp 'ess-r-mode-syntax-table)
(set-syntax-table ess-r-mode-syntax-table))
(when (boundp 'ess-r-mode-map)
(set-keymap-parent ein:notebook-multilang-mode-map ess-r-mode-map))))
(defun ein:ml-lang-setup (kernelspec)
(let ((setup-func (intern (concat "ein:ml-lang-setup-" (ein:$kernelspec-language kernelspec)))))
(if (fboundp setup-func)
(funcall setup-func)
(error "ein:ml-lang-setup: kernelspec language '%s' unsupported" (ein:$kernelspec-language kernelspec)))))
;; (defun ein:ml-lang-setup-markdown ()
;; "Use `markdown-mode-map'. NOTE: This function is not used now."
;; (when (featurep 'markdown-mode)
;; (set-keymap-parent ein:notebook-multilang-mode-map markdown-mode-map)))
;;; yasnippet
(defvar ein:ml-yasnippet-parents '(python-mode markdown-mode)
"Parent modes for `ein:notebook-multilang-mode' to register in yasnippet.")
(defun ein:ml-setup-yasnippet ()
(cl-loop for define-parents in '(yas/define-parents
yas--define-parents)
when (fboundp define-parents)
do (ignore-errors
;; `let' is for workaround the bug in yasnippet
(let ((mode-sym 'ein:notebook-multilang-mode))
(funcall define-parents
mode-sym
ein:ml-yasnippet-parents)))))
(eval-after-load "yasnippet" '(ein:ml-setup-yasnippet))
;;; Imenu Support
;; Most of this is borrowed from python.el
;; Just replace python with ein in most cases.
(defvar ein:imenu-format-item-label-function
'ein:imenu-format-item-label
"Imenu function used to format an item label.
It must be a function with two arguments: TYPE and NAME.")
(defvar ein:imenu-format-parent-item-label-function
'ein:imenu-format-parent-item-label
"Imenu function used to format a parent item label.
It must be a function with two arguments: TYPE and NAME.")
(defvar ein:imenu-format-parent-item-jump-label-function
'ein:imenu-format-parent-item-jump-label
"Imenu function used to format a parent jump item label.
It must be a function with two arguments: TYPE and NAME.")
(defun ein:imenu-format-item-label (type name)
"Return Imenu label for single node using TYPE and NAME."
(format "%s (%s)" name type))
(defun ein:imenu-format-parent-item-label (type name)
"Return Imenu label for parent node using TYPE and NAME."
(format "%s..." (ein:imenu-format-item-label type name)))
(defun python-imenu-format-parent-item-jump-label (type _name)
"Return Imenu label for parent node jump using TYPE and NAME."
(if (string= type "class")
"*class definition*"
"*function definition*"))
(defun ein:imenu--put-parent (type name pos tree)
"Add the parent with TYPE, NAME and POS to TREE."
(let ((label
(funcall ein:imenu-format-item-label-function type name))
(jump-label
(funcall ein:imenu-format-parent-item-jump-label-function type name)))
(if (not tree)
(cons label pos)
(cons label (cons (cons jump-label pos) tree)))))
(defun ein:imenu--build-tree (&optional min-indent prev-indent tree)
"Recursively build the tree of nested definitions of a node.
Arguments MIN-INDENT, PREV-INDENT and TREE are internal and should
not be passed explicitly unless you know what you are doing."
(setq min-indent (or min-indent 0)
prev-indent (or prev-indent python-indent-offset))
(let* ((pos (python-nav-backward-defun))
(type)
(name (when (and pos (looking-at python-nav-beginning-of-defun-regexp))
(let ((split (split-string (match-string-no-properties 0))))
(setq type (car split))
(cadr split))))
(label (when name
(funcall ein:imenu-format-item-label-function type name)))
(indent (current-indentation))
(children-indent-limit (+ python-indent-offset min-indent)))
(cond ((not pos)
;; Nothing found, probably near to bobp.
nil)
((<= indent min-indent)
;; The current indentation points that this is a parent
;; node, add it to the tree and stop recursing.
(ein:imenu--put-parent type name pos tree))
(t
(ein:imenu--build-tree
min-indent
indent
(if (<= indent children-indent-limit)
;; This lies within the children indent offset range,
;; so it's a normal child of its parent (i.e., not
;; a child of a child).
(cons (cons label pos) tree)
;; Oh no, a child of a child?! Fear not, we
;; know how to roll. We recursively parse these by
;; swapping prev-indent and min-indent plus adding this
;; newly found item to a fresh subtree. This works, I
;; promise.
(cons
(ein:imenu--build-tree
prev-indent indent (list (cons label pos)))
tree)))))))
(defun ein:imenu-create-index ()
"Return tree Imenu alist for the current Python buffer.
Change `ein:imenu-format-item-label-function',
`ein:imenu-format-parent-item-label-function',
`ein:imenu-format-parent-item-jump-label-function' to
customize how labels are formatted."
(goto-char (point-max))
(let ((index)
(tree))
(while (setq tree (ein:imenu--build-tree))
(setq index (cons tree index)))
index))
(defun ein:imenu-create-flat-index (&optional alist prefix)
"Return flat outline of the current Python buffer for Imenu.
Optional argument ALIST is the tree to be flattened; when nil
`ein:imenu-build-index' is used with
`ein:imenu-format-parent-item-jump-label-function'
`ein:imenu-format-parent-item-label-function'
`ein:imenu-format-item-label-function' set to
(lambda (type name) name)
Optional argument PREFIX is used in recursive calls and should
not be passed explicitly.
Converts this:
((\"Foo\" . 103)
(\"Bar\" . 138)
(\"decorator\"
(\"decorator\" . 173)
(\"wrap\"
(\"wrap\" . 353)
(\"wrapped_f\" . 393))))
To this:
((\"Foo\" . 103)
(\"Bar\" . 138)
(\"decorator\" . 173)
(\"decorator.wrap\" . 353)
(\"decorator.wrapped_f\" . 393))"
;; Inspired by imenu--flatten-index-alist removed in revno 21853.
(apply
'nconc
(mapcar
(lambda (item)
(let ((name (if prefix
(concat prefix "." (car item))
(car item)))
(pos (cdr item)))
(cond ((or (numberp pos) (markerp pos))
(list (cons name pos)))
((listp pos)
(cons
(cons name (cdar pos))
(python-imenu-create-flat-index (cddr item) name))))))
(or alist
(let* ((fn (lambda (_type name) name))
(ein:imenu-format-item-label-function fn)
(ein:imenu-format-parent-item-label-function fn)
(ein:imenu-format-parent-item-jump-label-function fn))
(python-imenu-create-index))))))
(provide 'ein-multilang)
;;; ein-multilang.el ends here

View file

@ -61,7 +61,6 @@
(require 'ein-traceback)
(require 'ein-shared-output)
(require 'ein-notebooklist)
(require 'ein-multilang)
(require 'ob-ein)
(require 'poly-ein)
@ -874,21 +873,25 @@ NAME is any non-empty string that does not contain '/' or '\\'.
(apply #'apply-partially callback cbargs)))
(ein:notebook-ask-save notebook callback0)))
(defun ein:notebook-kill-kernel-then-close-command (notebook)
(defun ein:notebook-kill-kernel-then-close-command (notebook &optional callback1)
"Kill kernel and then kill notebook buffer.
To close notebook without killing kernel, just close the buffer
as usual."
(interactive (list (ein:notebook--get-nb-or-error)))
(declare (indent defun))
(interactive (list (ein:notebook--get-nb-or-error) nil))
(unless callback1 (setq callback1 #'ignore))
(lexical-let ((callback1 callback1))
(let ((kernel (ein:$notebook-kernel notebook))
(callback1 (apply-partially
(lambda (notebook* kernel)
(ein:notebook-close notebook*))
(callback (apply-partially
(lambda (notebook* kernel*)
(ein:notebook-close notebook*)
(funcall callback1 kernel*))
notebook)))
(if (ein:kernel-live-p kernel)
(ein:message-whir "Ending session"
(add-function :before callback1 done-callback)
(ein:kernel-delete-session kernel callback1))
(funcall callback1 nil))))
(add-function :before callback done-callback)
(ein:kernel-delete-session kernel callback))
(funcall callback nil)))))
(defun ein:fast-content-from-notebook (notebook)
"Quickly generate a basic content structure from notebook. This
@ -1234,27 +1237,12 @@ associated with current buffer (if any)."
;;; Notebook mode
(defcustom ein:notebook-modes
'(ein:notebook-multilang-mode)
"Obsolete."
:type '(repeat (choice (const :tag "Multi-lang" ein:notebook-multilang-mode)
(const :tag "Only Python" ein:notebook-python-mode)
(const :tag "Plain" ein:notebook-plain-mode)))
:group 'ein)
(defun ein:notebook-choose-mode ()
"Return usable (defined) notebook mode."
(autoload 'ein:notebook-multilang-mode "ein-multilang")
(cl-loop for mode in ein:notebook-modes
if (functionp mode)
return mode))
(defvar ein:notebook-mode-map (make-sparse-keymap))
(defmacro ein:notebook--define-key (keymap key defn)
"Ideally we could override just the keymap binding with a (string . wrapped) cons pair (as opposed to messing with the DEFN itself), but then describe-minor-mode unhelpfully shows ?? for the keymap commands.
"Ideally we could override just the keymap binding with a
(string . wrapped) cons pair (as opposed to messing with the DEFN itself),
but then describe-minor-mode unhelpfully shows ?? for the keymap commands.
Tried add-function: the &rest from :around is an emacs-25 compilation issue."
(let ((km (intern (concat (symbol-name defn) "-km"))))
@ -1557,15 +1545,6 @@ the first argument and CBARGS as the rest of arguments."
"Add \"notebook destructor\" to `kill-buffer-hook'."
(add-hook 'kill-buffer-hook 'ein:notebook-kill-buffer-callback nil t))
(lexical-let* ((the-mode (ein:notebook-choose-mode))
(incompatible-func (lambda ()
(when (boundp 'undo-tree-incompatible-major-modes)
(nconc undo-tree-incompatible-major-modes
(list the-mode))))))
(unless (funcall incompatible-func)
(with-eval-after-load 'undo-tree
(funcall incompatible-func))))
(provide 'ein-notebook)
;;; ein-notebook.el ends here

View file

@ -38,21 +38,17 @@
(require 'dash)
(require 'ido)
(autoload 'ein:jupyterhub-connect "ein-jupyterhub")
(declare-function ein:jupyterhub-connect "ein-jupyterhub")
(declare-function ein:jupyter-crib-token "ein-jupyter")
(declare-function ein:jupyter-server-conn-info "ein-jupyter")
(declare-function ein:jupyter-get-default-kernel "ein-jupyter")
(declare-function ein:jupyter-crib-running-servers "ein-jupyter")
(defcustom ein:notebooklist-login-timeout (truncate (* 6.3 1000))
"Timeout in milliseconds for logging into server"
:group 'ein
:type 'integer)
(defcustom ein:notebooklist-render-order
'(render-header
render-directory)
"Order of notebook list sections.
Must contain render-header, and render-directory."
:group 'ein
:type 'list)
(defcustom ein:notebooklist-first-open-hook nil
"Hooks to run when the notebook list is opened at first time.
@ -113,7 +109,7 @@ is opened at first time.::
`(widget-create
'menu-choice :tag ,tag
:value ,custom-var
:notify (lambda (widget &rest ignore)
:notify (lambda (widget &rest _ignore)
(run-at-time 1 nil #'ein:notebooklist-reload)
(setq ,custom-var (widget-value widget)))
,@(mapcar (lambda (const)
@ -168,54 +164,13 @@ This function adds NBLIST to `ein:notebooklist-map'."
(get-buffer-create
(format ein:notebooklist-buffer-name-template url-or-port)))
(defun ein:crib-token (url-or-port)
"Shell out to jupyter for its credentials knowledge. Return list of (PASSWORD TOKEN)."
(aif (cl-loop for line in
(apply #'ein:jupyter-process-lines url-or-port
ein:jupyter-server-command
(split-string
(format "%s%s %s"
(aif ein:jupyter-server-use-subcommand
(concat it " ") "")
"list" "--json")))
with token0
with password0
when (destructuring-bind
(&key password url token &allow-other-keys)
(ein:json-read-from-string line)
(prog1 (or (equal (ein:url url) url-or-port)
(equal (url-host (url-generic-parse-url url))
"0.0.0.0"))
(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."
(nconc
(cl-loop for line in
(apply #'ein:jupyter-process-lines nil
ein:jupyter-server-command
(split-string
(format "%s%s %s"
(aif ein:jupyter-server-use-subcommand
(concat it " ") "")
"list" "--json")))
collecting (destructuring-bind
(&key url &allow-other-keys)
(ein:json-read-from-string line)
(ein:url url)))
(aif (ein:k8s-service-url-or-port) (list it))))
(defun ein:notebooklist-token-or-password (url-or-port)
"Return token or password for URL-OR-PORT.
Jupyter requires one or the other but not both.
Return empty string token if all authentication disabled.
Return nil if unclear what, if any, authentication applies."
(multiple-value-bind (password-p token) (ein:crib-token url-or-port)
(autoload 'ein:jupyter-server-conn-info "ein-jupyter")
(multiple-value-bind (password-p token) (ein:jupyter-crib-token url-or-port)
(multiple-value-bind (my-url-or-port my-token) (ein:jupyter-server-conn-info)
(cond ((eq password-p t) (read-passwd (format "Password for %s: " url-or-port)))
((and (stringp token) (eql password-p :json-false)) token)
@ -223,7 +178,6 @@ Return nil if unclear what, if any, authentication applies."
(t nil)))))
(defun ein:notebooklist-ask-url-or-port ()
(call-interactively #'ein:k8s-select-context)
(let* ((default (ein:url (aif (ein:get-notebook)
(ein:$notebook-url-or-port it)
(aif ein:%notebooklist%
@ -235,7 +189,7 @@ Return nil if unclear what, if any, authentication applies."
(if (atom ein:url-or-port)
(list ein:url-or-port)
ein:url-or-port)
(ein:crib-running-servers)))))
(ein:jupyter-crib-running-servers)))))
(url-or-port (let (ido-report-no-match ido-use-faces)
(ein:completing-read "URL or port: "
url-or-port-list
@ -285,7 +239,7 @@ refresh the notebook connection."
:type 'boolean
:group 'ein)
(defcustom ein:notebooklist-date-format "%x"
(defcustom ein:notebooklist-date-format "%F"
"The format spec for date in notebooklist mode.
See `ein:format-time-string'."
:type '(or string function)
@ -516,53 +470,14 @@ This function is called via `ein:notebook-after-rename-hook'."
sort-order)))
(-concat dirs nbs files)))
(defun render-header-ipy2 (&rest args)
"Render the header (for ipython2)."
;; Create notebook list
(widget-insert (format "IPython %s Notebook list\n\n" (ein:$notebooklist-api-version ein:%notebooklist%)))
(let ((breadcrumbs (generate-breadcrumbs (ein:$notebooklist-path ein:%notebooklist%))))
(dolist (p breadcrumbs)
(lexical-let ((name (car p))
(path (cdr p)))
(widget-insert " | ")
(widget-create
'link
:notify (lambda (&rest ignore)
(ein:notebooklist-login
(ein:$notebooklist-url-or-port ein:%notebooklist%) path))
name)))
(widget-insert " |\n\n"))
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-new-notebook
(ein:$notebooklist-url-or-port ein:%notebooklist%)
(unless (eq ein:jupyter-default-kernel
'first-alphabetically)
ein:jupyter-default-kernel)))
"New Notebook")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-reload nil t))
"Reload List")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore)
(browse-url
(ein:url (ein:$notebooklist-url-or-port ein:%notebooklist%))))
"Open In Browser")
(widget-insert "\n"))
(defun render-header* (url-or-port &rest args)
"Render the header (for ipython>=3)."
(defun render-header (url-or-port &rest args)
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(widget-insert
(format "Contents API %s (%s)\n\n" (ein:need-notebook-version url-or-port) url-or-port))
(let ((breadcrumbs (generate-breadcrumbs (ein:$notebooklist-path ein:%notebooklist%))))
(format "Contents API %s (%s)\n\n"
(ein:need-notebook-version url-or-port)
url-or-port))
(let ((breadcrumbs (generate-breadcrumbs
(ein:$notebooklist-path ein:%notebooklist%))))
(dolist (p breadcrumbs)
(lexical-let ((url-or-port url-or-port)
(name (car p))
@ -570,39 +485,29 @@ This function is called via `ein:notebook-after-rename-hook'."
(widget-insert " | ")
(widget-create
'link
:notify (lambda (&rest ignore)
:notify (lambda (&rest _ignore)
(ein:notebooklist-open* url-or-port path nil nil
(lambda (buffer url-or-port)
(pop-to-buffer buffer))))
name)))
(widget-insert " |\n\n"))
(lexical-let* ((url-or-port url-or-port)
(kernels (ein:list-available-kernels url-or-port)))
(unless ein:%notebooklist-new-kernel%
(setq ein:%notebooklist-new-kernel%
(if (eq ein:jupyter-default-kernel 'first-alphabetically)
(ein:get-kernelspec url-or-port (caar kernels))
(ein:get-kernelspec
url-or-port
(if (stringp ein:jupyter-default-kernel)
ein:jupyter-default-kernel
(symbol-name ein:jupyter-default-kernel))))))
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-new-notebook
:notify (lambda (&rest _ignore) (ein:notebooklist-new-notebook
url-or-port
ein:%notebooklist-new-kernel%))
"New Notebook")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore) (ein:notebooklist-reload nil t))
:notify (lambda (&rest _ignore) (ein:notebooklist-reload nil t))
"Resync")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest ignore)
:notify (lambda (&rest _ignore)
(browse-url (ein:url url-or-port)))
"Open In Browser")
@ -610,30 +515,26 @@ This function is called via `ein:notebook-after-rename-hook'."
(let ((radio-widget
(widget-create
'radio-button-choice
:value (and ein:%notebooklist-new-kernel%
(ein:$kernelspec-name
ein:%notebooklist-new-kernel%))
:notify (lambda (widget &rest _args)
(setq ein:%notebooklist-new-kernel%
(ein:get-kernelspec
url-or-port
(widget-value widget)))
(let ((update (ein:get-kernelspec url-or-port
(widget-value widget))))
(unless (equal ein:%notebooklist-new-kernel% update)
(when ein:%notebooklist-new-kernel%
(message "New notebooks started with %s kernel"
(ein:$kernelspec-display-name
ein:%notebooklist-new-kernel%))))))
(if (null kernels)
(widget-insert "\n No kernels found.")
(ein:$kernelspec-display-name update)))
(setq ein:%notebooklist-new-kernel% update)))))))
(if kernels
(let ((initial (ein:jupyter-get-default-kernel kernels)))
(dolist (k kernels)
(widget-radio-add-item radio-widget (list 'item
:value (car k)
:format (format "%s\n" (cdr k)))))
(unless (eq ein:jupyter-default-kernel 'first-alphabetically)
(widget-radio-value-set
(let ((child (widget-radio-add-item
radio-widget
(if (stringp ein:jupyter-default-kernel)
ein:jupyter-default-kernel
(symbol-name ein:jupyter-default-kernel))))
(widget-insert "\n"))))))
(list 'item
:value (car k)
:format (format "%s\n" (cdr k))))))
(when (string= initial (car k))
(widget-apply-action (widget-get child :button)))))
(widget-insert "\n"))
(widget-insert "\n No kernels found."))))))
(defun ein:format-nbitem-data (name last-modified)
(let ((dt (date-to-time last-modified)))
@ -661,7 +562,7 @@ This function is called via `ein:notebook-after-rename-hook'."
'link
:notify (lexical-let ((url-or-port url-or-port)
(name name))
(lambda (&rest ignore)
(lambda (&rest _ignore)
;; each directory creates a whole new notebooklist
(ein:notebooklist-open* url-or-port
(concat (file-name-as-directory
@ -682,7 +583,7 @@ This function is called via `ein:notebook-after-rename-hook'."
(widget-create
'link
:notify (lexical-let ((path path))
(lambda (&rest ignore)
(lambda (&rest _ignore)
(message "[EIN]: NBlist delete file command. Implement me!")))
"Delete")
(widget-insert " : " (ein:format-nbitem-data name last-modified))
@ -693,7 +594,7 @@ This function is called via `ein:notebook-after-rename-hook'."
'link
:notify (lexical-let ((url-or-port url-or-port)
(path path))
(lambda (&rest ignore)
(lambda (&rest _ignore)
(run-at-time 3 nil #'ein:notebooklist-reload)
(ein:notebook-open url-or-port path)))
"Open")
@ -703,7 +604,7 @@ This function is called via `ein:notebook-after-rename-hook'."
'link
:notify (lexical-let ((url url-or-port)
(session (car (gethash path sessions))))
(lambda (&rest ignore)
(lambda (&rest _ignore)
(ein:kernel-delete--from-session-id url session #'ein:notebooklist-reload)))
"Stop")
(widget-insert "------"))
@ -711,7 +612,7 @@ This function is called via `ein:notebook-after-rename-hook'."
(widget-create
'link
:notify (lexical-let ((path path))
(lambda (&rest ignore)
(lambda (&rest _ignore)
(ein:notebooklist-delete-notebook-ask
path)))
"Delete")
@ -734,11 +635,8 @@ Notebook list data is passed via the buffer local variable
nil)))
(defun ein:notebooklist-render--finish (nb-version url-or-port restore-point sessions)
(cl-letf (((symbol-function 'render-header) (if (< nb-version 3)
#'render-header-ipy2
#'render-header*)))
(mapc (lambda (x) (funcall (symbol-function x) url-or-port sessions))
ein:notebooklist-render-order))
(render-header url-or-port sessions)
(render-directory url-or-port sessions)
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(ein:notebooklist-mode)
(widget-setup)
@ -945,15 +843,12 @@ and the url-or-port argument of ein:notebooklist-open*."
("New Notebook (with name)"
ein:notebooklist-new-notebook-with-name)))))
(defun ein:notebooklist-revert-wrapper (&optional ignore-auto noconfirm preserve-modes)
(ein:notebooklist-reload))
(define-derived-mode ein:notebooklist-mode special-mode "ein:notebooklist"
"IPython notebook list mode.
Commands:
\\{ein:notebooklist-mode-map}"
(set (make-local-variable 'revert-buffer-function)
'ein:notebooklist-revert-wrapper))
(lambda (&rest _args) (ein:notebooklist-reload))))
(provide 'ein-notebooklist)

View file

@ -29,6 +29,7 @@
(require 'ein-jupyter)
(require 'ein-file)
(require 'ein-notebooklist)
(require 'ein-k8s)
(defcustom ein:process-jupyter-regexp "\\(jupyter\\|ipython\\)\\(-\\|\\s-+\\)note"
"Regexp by which we recognize notebook servers."
@ -208,9 +209,10 @@ is used instead. BUFFER-CALLBACK is called after notebook opened."
(defun ein:process-find-file-callback ()
"A callback function for `find-file-hook' to open notebook."
(interactive)
(cl-letf (((symbol-function 'ein:k8s-select-context) #'ignore))
(-when-let* ((filename buffer-file-name)
(match-p (string-match-p "\\.ipynb$" filename)))
(ein:process-open-notebook filename #'kill-buffer-if-not-modified)))
(ein:process-open-notebook filename #'kill-buffer-if-not-modified))))
(provide 'ein-process)

View file

@ -395,7 +395,7 @@ Adapted from twittering-mode.el's `case-string'."
;;; Text manipulation on buffer
(defun ein:find-leftmot-column (beg end)
(defun ein:find-leftmost-column (beg end)
"Return the leftmost column in region BEG to END."
(save-excursion
(let (mincol)
@ -407,16 +407,18 @@ Adapted from twittering-mode.el's `case-string'."
(min mincol (current-column))
(current-column))))
(unless (= (forward-line 1) 0)
(return-from ein:find-leftmot-column mincol)))
(return-from ein:find-leftmost-column mincol)))
mincol)))
;;; Misc
(defun ein:completing-read (&rest args)
(if (eq completing-read-function 'completing-read-default)
(apply #'ido-completing-read args)
(apply completing-read-function args)))
(cond (noninteractive (if (consp (cl-second args))
(car (cl-second args))
(cl-second args)))
((eq completing-read-function 'completing-read-default)
(apply #'ido-completing-read args))
(t (apply completing-read-function args))))
(defun ein:plist-iter (plist)
"Return list of (key . value) in PLIST."

View file

@ -1180,7 +1180,7 @@ in the history."
(let* ((beg (ein:cell-input-pos-min cell))
(end (ein:cell-input-pos-max cell)))
(indent-rigidly
beg end (- (ein:find-leftmot-column beg end)))))
beg end (- (ein:find-leftmost-column beg end)))))
(defun ein:worksheet--cells-before-cell (ws cell)
(let ((cells (ein:worksheet-get-cells ws)))

View file

@ -12,6 +12,11 @@
(defsubst poly-ein--neuter-markdown-mode ()
"Consolidate fragility here."
(unless (eq 'ein:notebook-mode (caar minor-mode-map-alist))
(when-let ((entry (assq 'ein:notebook-mode minor-mode-map-alist)))
(setf minor-mode-map-alist
(cons entry
(assq-delete-all 'ein:notebook-mode minor-mode-map-alist)))))
(when (eq major-mode 'markdown-mode)
(poly-ein--remove-hook "markdown" after-change-functions)
(poly-ein--remove-hook "markdown" jit-lock-after-change-extend-region-functions)

View file

@ -44,7 +44,7 @@
'notebook_saving.Notebook)
(should (string-prefix-p
(concat "IP[y]: Saving Notebook... | "
(substitute-command-keys "Kernel requires restart \\<ein:notebook-mode-map>\\[ein:notebook-restart-session-command] | ")
(substitute-command-keys "Kernel requires restart \\<ein:notebook-mode-map>\\[ein:notebook-restart-session-command-km] | ")
;;"Kernel requires restart C-c C-x C-r | "
"/1\\ /2\\ /3\\ [+]") (ein:header-line)))))

View file

@ -98,7 +98,7 @@ def func():
;;; Text manipulation on buffer
(ert-deftest ein:find-leftmot-column-simple-cases ()
(ert-deftest ein:find-leftmost-column-simple-cases ()
(cl-loop for (indent text) in
'(;; No indent
(0 "\
@ -123,7 +123,7 @@ def f():
)
do (with-temp-buffer
(insert text)
(should (= (ein:find-leftmot-column (point-min) (point-max))
(should (= (ein:find-leftmost-column (point-min) (point-max))
indent)))))