├── .envrc
├── thumbnail.png
├── test
├── testein-loader.el
├── redirecting-server.py
├── test-ein-process.el
├── testein.el
├── test-ein-core.el
├── test-ein-modes.el
├── test-ein-ob.el
├── test-ein-shared-output.el
├── test-ein-content.el
├── test-ein-notebooklist.el
├── test-ein-kill-ring.el
├── test-ein-worksheet.el
├── test-ein-output-area.el
├── test-ein-worksheet-notebook.el
├── ein-testing-cell.el
├── test-ein-kernel.el
├── test-ein-notification.el
├── test-ein-node.el
├── ein-testing-notebook.el
├── ein-testing.el
├── test-ein-utils.el
└── test-ein-cell-notebook.el
├── .gitignore
├── features
├── dev.feature
├── jupyterhub.feature
├── connect.feature
├── gat.feature
├── polymode.feature
├── execute-all-cells.feature
├── ipdb.feature
├── undo.ipynb
├── notebook.feature
├── notebooklist.feature
├── ob-ein.feature
├── support
│ └── env.el
└── undo.feature
├── Cask
├── tools
├── readme-sed.sh
├── install-virtualenv.sh
├── install-julia.sh
├── retry.sh
├── install-R.sh
└── install-cask.sh
├── lisp
├── ein-completer.el
├── ein-scratchsheet.el
├── ein-kernelinfo.el
├── ein-pytools.el
├── ein-kill-ring.el
├── ein-events.el
├── ein.el
├── ein-node.el
├── ein-file.el
├── ein-pager.el
├── ein-ipynb-mode.el
├── ein-log.el
├── ein-websocket.el
├── ein-ipdb.el
├── ein-python-send.el
├── ein-core.el
├── ein-output-area.el
├── ein-notification.el
└── ein-traceback.el
├── .github
└── workflows
│ └── test.yml
├── README.in.rst
├── Makefile
├── .appveyor.yml
└── README.rst
/.envrc:
--------------------------------------------------------------------------------
1 | export VIRTUAL_ENV="venv"
2 | layout python
3 |
--------------------------------------------------------------------------------
/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/millejoh/emacs-ipython-notebook/HEAD/thumbnail.png
--------------------------------------------------------------------------------
/test/testein-loader.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (load "testein.el" nil t)
3 | (dolist (file (directory-files (file-name-directory load-file-name) t "test-ein.*\\.el"))
4 | (load file nil t))
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | doc/build/
2 | .cask/*
3 | *.pyc
4 | *.elc
5 | log/*
6 | *.zip
7 | .gitattributes
8 | .ecukes*
9 | dist
10 | .*ein*ipynb
11 | .Rhistory
12 | .ipynb_checkpoints
13 | ein-autoloads.el
14 | ert-profile
15 | ein-images
16 |
--------------------------------------------------------------------------------
/features/dev.feature:
--------------------------------------------------------------------------------
1 | @dev
2 | Scenario: dev-bug-report-template needs to work
3 | Given I call "ein:dev-bug-report-template"
4 | And I switch to buffer "*ein:bug-report*"
5 | Then I should see ":lib"
6 | And I should see "#### *ein:log-all*"
7 |
--------------------------------------------------------------------------------
/Cask:
--------------------------------------------------------------------------------
1 | (source gnu)
2 | (source melpa)
3 |
4 | (package-file "lisp/ein.el")
5 | (files "lisp/*.el")
6 |
7 | (development
8 | (depends-on "ert-runner")
9 | (depends-on "ecukes")
10 | (depends-on "espuds")
11 | (depends-on "mocker")
12 | (depends-on "julia-mode")
13 | (depends-on "magit")
14 | (depends-on "ess")
15 | (depends-on "f")
16 | (depends-on "s")
17 | (depends-on "exec-path-from-shell"))
18 |
--------------------------------------------------------------------------------
/test/redirecting-server.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, redirect
2 |
3 | app = Flask(__name__)
4 |
5 | @app.route('/')
6 | def jupyter_redirect():
7 | return redirect("http://127.0.0.1:8888/", code=302)
8 |
9 | @app.route('/api')
10 | def api_check():
11 | return redirect("http://127.0.0.1:8888/api", code=302)
12 |
13 | if __name__=='__main__':
14 | port = int(8000)
15 | app.run(host='127.0.0.1', port=port)
16 |
17 |
18 |
--------------------------------------------------------------------------------
/features/jupyterhub.feature:
--------------------------------------------------------------------------------
1 | @jupyterhub
2 | Scenario: basic spawn
3 | Given I start jupyterhub configured "c.JupyterHub.answer_yes=True\nc.JupyterHub.authenticator_class='dummyauthenticator.DummyAuthenticator'\nc.JupyterHub.cookie_secret_file = '/var/tmp/jupyterhub_cookie_secret'\nc.JupyterHub.spawner_class = 'simplespawner.SimpleLocalProcessSpawner'\n"
4 | And I switch to log expr "ein:log-all-buffer-name"
5 | Then I should not see "[warn]"
6 | And I should not see ": [error]"
7 | Then I login to jupyterhub
8 |
--------------------------------------------------------------------------------
/test/test-ein-process.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 | (require 'ein-process)
4 |
5 | (ert-deftest ein:process-check-suitable ()
6 | (should-not (equal (ein:process-suitable-notebook-dir (concat default-directory "features/support")) (concat default-directory "features/support"))))
7 |
8 | (ert-deftest ein:process-divine ()
9 | (with-current-buffer "*scratch*"
10 | (erase-buffer))
11 | (ein:process-divine-dir 1 "" "*scratch*")
12 | (ein:process-divine-port 1 "" "*scratch*"))
13 |
--------------------------------------------------------------------------------
/test/testein.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (custom-set-variables '(python-indent-guess-indent-offset-verbose nil)
3 | '(ein:jupyter-use-containers nil)
4 | '(ein:output-area-inlined-images t))
5 |
6 | (require 'python)
7 | (require 'ein-dev)
8 | (require 'ein-testing)
9 |
10 | (ein:setq-if-not ein:testing-dump-file-log "./log/testein.log")
11 | (ein:setq-if-not ein:testing-dump-file-messages "./log/testein.messages")
12 | (setq ert-runner-profile nil)
13 |
14 | (setq message-log-max t)
15 |
--------------------------------------------------------------------------------
/tools/readme-sed.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # by mklement0 https://stackoverflow.com/a/29613573/5132008
4 |
5 | # Define sample multi-line literal.
6 | input=`cat`
7 | replace="$input"
8 | if [ ! -z "$3" ]; then
9 | replace=$(awk "BEGIN{IGNORECASE=1} /$3/,EOF { print \" \" \$0 }" <<<"$input")
10 | fi
11 |
12 | # Escape it for use as a Sed replacement string.
13 | IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g' <<<"$replace")
14 | replaceEscaped=${REPLY%$'\n'}
15 |
16 | # If ok, outputs $replace as is.
17 | sed "/$1/c\\$replaceEscaped" $2
18 |
--------------------------------------------------------------------------------
/tools/install-virtualenv.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Create virtualenvs for python{2,3} for Travis CI on OSX
4 |
5 | set -x
6 |
7 | WORKDIR=${HOME}/local
8 |
9 | . tools/retry.sh
10 |
11 | if [ "x$TRAVIS_OS_NAME" = "xosx" ]; then
12 | brew list pyenv-virtualenv &>/dev/null || HOMEBREW_NO_AUTO_UPDATE=1 brew install pyenv-virtualenv
13 |
14 | case "${TOXENV}" in
15 | py27)
16 | pyenv install -s 2.7.13
17 | pyenv virtualenv -f 2.7.13 py27
18 | ;;
19 | py35)
20 | pyenv install -s 3.5.2
21 | pyenv virtualenv -f 3.5.2 py35
22 | ;;
23 | esac
24 | fi
25 |
--------------------------------------------------------------------------------
/test/test-ein-core.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 |
4 | (require 'tramp)
5 | (require 'ein-core)
6 |
7 | ;; Generic getter
8 |
9 | (defmacro eintest:generic-getter-should-return-nil (func)
10 | "In an \"empty\" context, generic getter should return nil."
11 | `(ert-deftest ,(intern (format "%s--nil name" func)) ()
12 | (with-temp-buffer
13 | (should (not (,func))))))
14 |
15 | (eintest:generic-getter-should-return-nil ein:get-url-or-port)
16 | (eintest:generic-getter-should-return-nil ein:get-notebook)
17 | (eintest:generic-getter-should-return-nil ein:get-kernel)
18 | (eintest:generic-getter-should-return-nil ein:get-cell-at-point)
19 | (eintest:generic-getter-should-return-nil ein:get-traceback-data)
20 |
--------------------------------------------------------------------------------
/tools/install-julia.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # install julia for Actions CI
4 |
5 | set -ex
6 |
7 | WORKDIR=${HOME}/local
8 | UNAME=$(uname -s)
9 | cd $WORKDIR
10 | if [ "x$UNAME" = "xLinux" ] ; then
11 | if [ ! -d ${WORKDIR}/julia-1.3.1 ]; then
12 | wget https://julialang-s3.julialang.org/bin/linux/x64/1.3/julia-1.3.1-linux-x86_64.tar.gz
13 | tar zxvf julia-1.3.1-linux-x86_64.tar.gz
14 | rm -f julia-1.3.1-linux-x86_64.tar.gz
15 | fi
16 | hash
17 | julia --version
18 | julia -e 'import Pkg; Pkg.add("IJulia")'
19 | elif [ "x$UNAME" = "xDarwin" ]; then
20 | brew update
21 | brew cask list julia &>/dev/null || HOMEBREW_NO_AUTO_UPDATE=1 brew cask install julia
22 | julia --version
23 | julia -e 'import Pkg; Pkg.add("IJulia")'
24 | fi
25 |
--------------------------------------------------------------------------------
/tools/retry.sh:
--------------------------------------------------------------------------------
1 | # Copied retry logic from Travis CI [http://bit.ly/2jPDCtV]
2 | # Author: gonewest818 https://github.com/clojure-emacs/cider/pull/2139
3 |
4 | ANSI_RED="\033[31;1m"
5 | ANSI_GREEN="\033[32;1m"
6 | ANSI_RESET="\033[0m"
7 | ANSI_CLEAR="\033[0K"
8 |
9 | travis_retry() {
10 | local result=0
11 | local count=1
12 | while [ $count -le 3 ]; do
13 | [ $result -ne 0 ] && {
14 | echo -e "\n${ANSI_RED}The command \"$@\" failed. Retrying, $count of 3.${ANSI_RESET}\n" >&2
15 | }
16 | "$@"
17 | result=$?
18 | [ $result -eq 0 ] && break
19 | count=$(($count + 1))
20 | sleep 1
21 | done
22 |
23 | [ $count -gt 3 ] && {
24 | echo -e "\n${ANSI_RED}The command \"$@\" failed 3 times.${ANSI_RESET}\n" >&2
25 | }
26 |
27 | return $result
28 | }
29 |
--------------------------------------------------------------------------------
/tools/install-R.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # install R for Actions CI
4 |
5 | set -ex
6 |
7 | WORKDIR=${HOME}/local
8 | UNAME=$(uname -s)
9 | cd $WORKDIR
10 | if [ "x$UNAME" = "xLinux" ] ; then
11 | if [ ! -d ${WORKDIR}/R ]; then
12 | wget http://cran.mirrors.hoobly.com/src/base/R-3/R-3.6.3.tar.gz
13 | tar xvf R-3.6.3.tar.gz
14 | cd R-3.6.3
15 | ./configure --prefix=${WORKDIR}/R
16 | make && make install
17 | find ${WORKDIR}/R -name R -print
18 | fi
19 | elif [ "x$UNAME" = "xDarwin" ]; then
20 | brew list r &>/dev/null || HOMEBREW_NO_AUTO_UPDATE=1 brew install r
21 | fi
22 | mkdir -p ${WORKDIR}/R
23 | echo ".libPaths( c( '${WORKDIR}/R' , .libPaths() ) )" > ${HOME}/.Rprofile
24 | R -e "install.packages(c('pbdZMQ', 'repr', 'IRdisplay', 'IRkernel'), type='source', repos='http://cran.mirrors.hoobly.com', lib='${WORKDIR}/R')"
25 | R -e "IRkernel::installspec()"
26 | R --version
27 |
--------------------------------------------------------------------------------
/features/connect.feature:
--------------------------------------------------------------------------------
1 | @connect
2 | Scenario: Connect buffer to an open notebook
3 | Given new python notebook
4 | And eval "(get-buffer-create "connect.feature.py")"
5 | And I switch to buffer "connect.feature.py"
6 | And I call "python-mode"
7 | And I type "a = 10"
8 | And I press "C-c C-/ e"
9 | And I wait for the smoke to clear
10 | And I switch to buffer like "Untitled"
11 | And I type "a"
12 | And I wait for cell to execute
13 | Then I should see "10"
14 | And I switch to buffer "connect.feature.py"
15 | And I press "RET"
16 | And I press "RET"
17 | And I type "for i in range(10):"
18 | And I press "RET"
19 | And I type " j = i"
20 | And I press "C-p"
21 | And I press "C-a"
22 | And I press "C-SPC"
23 | And I press "C-x ]"
24 | And I press "C-c C-/ r"
25 | And I switch to buffer like "Untitled"
26 | And I press "C-c C-b"
27 | And I type "j"
28 | And I wait for cell to execute
29 | Then I should see "9"
30 |
--------------------------------------------------------------------------------
/test/test-ein-modes.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 |
4 | (require 'ein-dev)
5 |
6 | (defun eintest:assert-keymap-fboundp (keymap)
7 | (cl-labels ((assert-fboundp (event value)
8 | (cond
9 | ((keymapp value)
10 | (map-keymap #'assert-fboundp value))
11 | ((and (listp value) (eq (car value) 'menu-item))
12 | (funcall #'assert-fboundp (cadr value) (cl-caddr value)))
13 | ((not (equal (car-safe value) "--"))
14 | (if (consp value)
15 | (should (functionp (cdr value)))
16 | (when value ; nil is also valid in keymap
17 | (should (commandp value))))))))
18 | (map-keymap #'assert-fboundp keymap)))
19 |
20 | (defmacro eintest:test-keymap (keymap)
21 | `(ert-deftest ,(intern (format "%s--assert-fboundp" keymap)) ()
22 | (eintest:assert-keymap-fboundp ,keymap)))
23 |
24 | (eintest:test-keymap ein:notebooklist-mode-map)
25 | (eintest:test-keymap ein:notebook-mode-map)
26 | (eintest:test-keymap ein:traceback-mode-map)
27 | (eintest:test-keymap ein:shared-output-mode-map)
28 |
--------------------------------------------------------------------------------
/test/test-ein-ob.el:
--------------------------------------------------------------------------------
1 | ; -*- lexical-binding:t -*-
2 | (require 'ert)
3 | (require 'ob-ein)
4 | (require 'org)
5 |
6 | (ert-deftest ein:ob-anonymous-p ()
7 | (should (ob-ein-anonymous-p "ob-ein-python.ipynb"))
8 | (should (ob-ein-anonymous-p "ob-ein.ipynb"))
9 | (should-not (ob-ein-anonymous-p "ein-python.ipynb"))
10 | (should-not (ob-ein-anonymous-p "Untitled.ipynb")))
11 |
12 | ;;; This is the content portion of a response from the content API.
13 | (defvar eintest:ob-src-block
14 | "#+BEGIN_SRC ein :session 8888/Untitled.ipynb
15 | import sys
16 |
17 | a = 14500
18 | b = a+1000
19 | sys.version
20 | #+END_SRC
21 | ")
22 |
23 | (ert-deftest ein:ob-aware ()
24 | (let ((org-babel-load-languages (quote ((ein . t)))))
25 | (with-temp-buffer
26 | (save-excursion
27 | (org-mode)
28 | (insert eintest:ob-src-block)
29 | (search-backward "SRC")
30 | (cl-letf (((symbol-function 'ob-ein--initiate-session)
31 | (lambda (&rest _args) (make-ein:$notebook))))
32 | (should (call-interactively #'org-edit-special)))))))
33 |
--------------------------------------------------------------------------------
/test/test-ein-shared-output.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ein-shared-output)
3 |
4 | (defmacro eintest:shared-output-with-buffer (&rest body)
5 | (declare (indent 0))
6 | `(with-current-buffer (ein:shared-output-create-buffer)
7 | (ein:shared-output-get-or-create)
8 | ,@body))
9 |
10 | (defmacro eintest:shared-output-is-empty-context-of (func)
11 | `(ert-deftest ,(intern (format "%s--shared-output" func)) ()
12 | (eintest:shared-output-with-buffer
13 | (should-not (,func)))))
14 |
15 |
16 | ;; Generic getter
17 |
18 | (ert-deftest ein:get-cell-at-point--shared-output ()
19 | (eintest:shared-output-with-buffer
20 | (should (eq (ein:get-cell-at-point)
21 | (ein:shared-output-get-cell))))
22 | (with-temp-buffer
23 | (should-not (ein:get-cell-at-point--shared-output))))
24 |
25 | ;; FIXME: Add tests with non-empty shared output buffer.
26 | (eintest:shared-output-is-empty-context-of ein:get-url-or-port)
27 | (eintest:shared-output-is-empty-context-of ein:get-notebook)
28 | (eintest:shared-output-is-empty-context-of ein:get-kernel)
29 | (eintest:shared-output-is-empty-context-of ein:get-traceback-data)
30 |
--------------------------------------------------------------------------------
/test/test-ein-content.el:
--------------------------------------------------------------------------------
1 | ;;; test-ein-content.el --- Testing content interface -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2015 John Miller
4 |
5 | ;; Authors: Takafumi Arakaki
6 | ;; John M. Miller
7 |
8 | ;; This file is NOT part of GNU Emacs.
9 |
10 | ;; test-ein-content.el is free software: you can redistribute it
11 | ;; and/or modify it under the terms of the GNU General Public License
12 | ;; as published by the Free Software Foundation, either version 3 of
13 | ;; the License, or (at your option) any later version.
14 |
15 | ;; test-ein-content.el is distributed in the hope that it will be useful,
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ;; GNU General Public License for more details.
19 |
20 | ;; You should have received a copy of the GNU General Public License
21 | ;; along with ein-testing-notebook.el.
22 | ;; If not, see .
23 |
24 | ;;; Commentary:
25 |
26 | ;;
27 |
28 | ;;; Code:
29 |
30 | (require 'ein-contents-api)
31 |
32 | (defvar *list-content-result* nil)
33 |
34 | (defun ein-test-list-contents-1 ()
35 | (ein:content-list-contents "" )
36 | )
37 |
--------------------------------------------------------------------------------
/lisp/ein-completer.el:
--------------------------------------------------------------------------------
1 | ;;; -*- mode: emacs-lisp; lexical-binding: t -*-
2 | ;;; ein-completer.el --- Completion module
3 |
4 | ;; Copyright (C) 2018- Takafumi Arakaki / John Miller
5 |
6 | ;; Author: Takafumi Arakaki / John Miller
7 |
8 | ;; This file is NOT part of GNU Emacs.
9 |
10 | ;; ein-completer.el is free software: you can redistribute it and/or modify
11 | ;; it under the terms of the GNU General Public License as published by
12 | ;; the Free Software Foundation, either version 3 of the License, or
13 | ;; (at your option) any later version.
14 |
15 | ;; ein-completer.el is distributed in the hope that it will be useful,
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ;; GNU General Public License for more details.
19 |
20 | ;; You should have received a copy of the GNU General Public License
21 | ;; along with ein-completer.el. If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | ;; This needs to get re-written.
26 |
27 | ;;; Code:
28 |
29 | (make-obsolete-variable 'ein:complete-on-dot nil "0.15.0")
30 | (make-obsolete-variable 'ein:completion-backend nil "0.17.0")
31 |
32 | (provide 'ein-completer)
33 |
34 | ;;; ein-completer.el ends here
35 |
--------------------------------------------------------------------------------
/features/gat.feature:
--------------------------------------------------------------------------------
1 | @gat
2 | Scenario: gat create from repo, then create from worktree, then run-local
3 | Given new git repo "test-repo"
4 | Given new python notebook in "test-repo"
5 | And I type "99"
6 | And I wait for cell to execute
7 | And I clear log expr "ein:log-all-buffer-name"
8 | And I press "C-x C-s"
9 | And I switch to log expr "ein:log-all-buffer-name"
10 | And I wait for buffer to say "Notebook is saved"
11 | And I switch to buffer like "Untitled"
12 | When I gat create "foo" in "test-repo"
13 | Then I possibly wait for gat install
14 | Then I switch to buffer like ".gat/foo/Untitled"
15 | When I gat create "baz" in "test-repo"
16 | Then I switch to buffer like ".gat/baz/Untitled"
17 | When I call "ein:gat-run-local"
18 | And I switch to buffer like "Dockerfile.Untitled"
19 | And I should see "FROM dickmao"
20 | And I press "C-c C-c"
21 | And I switch to buffer like "ein-gat: test-repo"
22 | Then I wait for buffer to say "Building image"
23 | And I go to word "run"
24 | And eval "(ignore-errors (kill-process (magit-section-value-if 'process)))"
25 | And I wait for buffer to not say "run . gat"
26 | And I am in notebooklist buffer
27 | And I click on "Home"
28 | Then I wait for buffer to say "support"
29 | And remove git repo "test-repo"
30 | And eval "(kill-buffer)"
31 |
--------------------------------------------------------------------------------
/test/test-ein-notebooklist.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ein-notebooklist)
3 | (require 'ein-testing-notebook)
4 |
5 | (defun eintest:notebooklist-make-empty (&optional url-or-port)
6 | "Make empty notebook list buffer."
7 | (let ((url-or-port (or url-or-port ein:testing-notebook-dummy-url)))
8 | (cl-letf (((symbol-function 'ein:need-kernelspecs) #'ignore)
9 | ((symbol-function 'ein:content-query-sessions) #'ignore))
10 | (ein:notebooklist-open--finish url-or-port #'ignore
11 | (make-ein:$content :url-or-port url-or-port
12 | :notebook-api-version "5"
13 | :path "")))))
14 |
15 | (defmacro eintest:notebooklist-is-empty-context-of (func)
16 | `(ert-deftest ,(intern (format "%s--notebooklist" func)) ()
17 | (with-current-buffer (eintest:notebooklist-make-empty)
18 | (should-not (,func)))))
19 |
20 | (ert-deftest ein:get-url-or-port--notebooklist ()
21 | (with-current-buffer (eintest:notebooklist-make-empty)
22 | (should (equal (ein:get-url-or-port) ein:testing-notebook-dummy-url))))
23 |
24 | (eintest:notebooklist-is-empty-context-of ein:get-notebook)
25 | (eintest:notebooklist-is-empty-context-of ein:get-kernel)
26 | (eintest:notebooklist-is-empty-context-of ein:get-cell-at-point)
27 | (eintest:notebooklist-is-empty-context-of ein:get-traceback-data)
28 |
--------------------------------------------------------------------------------
/test/test-ein-kill-ring.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 |
4 | (require 'ein-kill-ring)
5 |
6 | (ert-deftest ein:kill-ring-simple ()
7 | (let (ein:kill-ring
8 | ein:kill-ring-yank-pointer)
9 | (ein:kill-new 1)
10 | (should (equal (ein:current-kill 0) 1))))
11 |
12 | (defun eintest:kill-ring-simple-repeat-setup ()
13 | (cl-loop for i from 0 below 5
14 | do (ein:kill-new i)
15 | do (should (equal (ein:current-kill 0) i))))
16 |
17 | (ert-deftest ein:kill-ring-simple-repeat ()
18 | (let (ein:kill-ring
19 | ein:kill-ring-yank-pointer)
20 | (eintest:kill-ring-simple-repeat-setup)
21 | (should (equal ein:kill-ring ein:kill-ring-yank-pointer))
22 | (should (equal ein:kill-ring '(4 3 2 1 0)))))
23 |
24 | (ert-deftest ein:kill-ring-repeat-n-1 ()
25 | (let (ein:kill-ring
26 | ein:kill-ring-yank-pointer)
27 | (eintest:kill-ring-simple-repeat-setup)
28 | (cl-loop for i in '(3 2 1 0 4 3 2)
29 | do (should (equal (ein:current-kill 1) i)))
30 | (should-not (equal ein:kill-ring ein:kill-ring-yank-pointer))
31 | (should (equal ein:kill-ring '(4 3 2 1 0)))
32 | (should (equal ein:kill-ring-yank-pointer '(2 1 0)))))
33 |
34 | (ert-deftest ein:kill-ring-exceeds-max ()
35 | (let (ein:kill-ring
36 | ein:kill-ring-yank-pointer
37 | (ein:kill-ring-max 3))
38 | (eintest:kill-ring-simple-repeat-setup)
39 | (should (equal ein:kill-ring ein:kill-ring-yank-pointer))
40 | (should (equal (length ein:kill-ring) ein:kill-ring-max))
41 | (should (equal ein:kill-ring '(4 3 2)))))
42 |
--------------------------------------------------------------------------------
/test/test-ein-worksheet.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 |
4 | (require 'ein-worksheet)
5 | (require 'ein-testing-cell)
6 |
7 | (defvar ein:testing-worksheet-example-data
8 | (list (ein:testing-codecell-data "code example input")
9 | (ein:testing-markdowncell-data "markdown example input")
10 | (ein:testing-rawcell-data "raw example input")
11 | (ein:testing-htmlcell-data "html example input")))
12 |
13 | (defun ein:testing-worksheet-new ()
14 | (make-instance 'ein:worksheet))
15 |
16 | (defun ein:testing-worksheet-to-json (cells &optional metadata)
17 | (let* ((ws-0 (ein:worksheet-from-json (ein:testing-worksheet-new)
18 | (list :cells cells
19 | :metadata metadata)))
20 | (ws-1 (ein:testing-worksheet-new))
21 | (json-0 (ein:worksheet-to-json ws-0))
22 | (json-1 (ein:worksheet-to-json
23 | (ein:worksheet-from-json ws-1
24 | (ein:json-read-from-string
25 | (ein:json-encode json-0))))))
26 | (let* ((found (assoc 'metadata json-0)))
27 | (when found
28 | (should (cdr found))))
29 | (should (equal json-0 json-1))))
30 |
31 | (ert-deftest ein:worksheet-to-json/empty ()
32 | (ein:testing-worksheet-to-json nil))
33 |
34 | (ert-deftest ein:worksheet-to-json/example-data ()
35 | (ein:testing-worksheet-to-json ein:testing-worksheet-example-data))
36 |
37 | (ert-deftest ein:worksheet-to-json/example-data-with-metadata ()
38 | (ein:testing-worksheet-to-json ein:testing-worksheet-example-data
39 | '(:name "Worksheet name")))
40 |
--------------------------------------------------------------------------------
/test/test-ein-output-area.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 |
4 | (require 'ein-output-area)
5 |
6 | (defun ein:testing-insert-html--fix-urls-do-test (source desired)
7 | (let ((s (ein:xml-parse-html-string source))
8 | (d (ein:xml-parse-html-string desired)))
9 | (ein:insert-html--fix-urls s 8888)
10 | (should (equal s d))))
11 |
12 | (defmacro ein:testing-insert-html--fix-urls-deftests (args-list)
13 | `(progn
14 | ,@(cl-loop for i from 0
15 | for args in args-list
16 | for test = (intern (format "ein:insert-html--fix-urls/%s" i))
17 | collect
18 | `(ert-deftest ,test ()
19 | (ein:testing-insert-html--fix-urls-do-test ,@args)))))
20 |
21 | (ein:testing-insert-html--fix-urls-deftests
22 | (;; Simple replaces
23 | ("text"
24 | "text")
25 | ("text"
26 | "text")
27 | ("
"
28 | "
")
29 | ("
"
30 | "
")
31 | ;; Do not modify dom in these cases:
32 | ("text"
33 | "text")
34 | ("text"
35 | "text")
36 | ("
"
37 | "
")
38 | ;; Bit more complicated cases:
39 | ("link normal
"
40 | "link normal
")
41 | ("![]()
normal
"
42 | "![]()
normal")))
43 |
--------------------------------------------------------------------------------
/lisp/ein-scratchsheet.el:
--------------------------------------------------------------------------------
1 | ;;; ein-scratchsheet.el --- Worksheet without needs for saving -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012 Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-scratchsheet.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-scratchsheet.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-scratchsheet.el.
21 | ;; If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | ;;
26 |
27 | ;;; Code:
28 |
29 | (require 'ein-worksheet)
30 |
31 | (defclass ein:scratchsheet (ein:worksheet)
32 | ((data :initarg :data :initform nil))
33 | :documentation "Worksheet without needs for saving.")
34 |
35 | (defun ein:scratchsheet-new (nbformat notebook-path kernel events &rest args)
36 | (apply #'make-instance 'ein:scratchsheet
37 | :nbformat nbformat
38 | :notebook-path notebook-path
39 | :kernel kernel
40 | :events events
41 | args))
42 |
43 | (cl-defmethod ein:worksheet--buffer-name ((ws ein:scratchsheet))
44 | (format "*ein:scratch %s/%s*"
45 | (ein:worksheet-url-or-port ws)
46 | (ein:worksheet-notebook-path ws)))
47 |
48 | (provide 'ein-scratchsheet)
49 |
50 | ;;; ein-scratchsheet.el ends here
51 |
--------------------------------------------------------------------------------
/features/polymode.feature:
--------------------------------------------------------------------------------
1 | @poly
2 | Scenario: selection spans cells
3 | Given new python notebook
4 | And I press "C-c C-b"
5 | And I press "C-"
6 | And I press "C-p"
7 | And I press "C-SPC"
8 | And I press "C-n"
9 | And I press "C-n"
10 | And I press "C-n"
11 | And I press "C-n"
12 | Then newlined region should be "In [ ]:\n\n\nIn [ ]:\n"
13 | And I press "C-g"
14 | Then the region should not be active
15 |
16 | @poly
17 | Scenario: markdown often erroneously fontifies the whole buffer
18 | Given new python notebook
19 | And I press "C-c C-t"
20 | And I type "# Header"
21 | And I press "RET"
22 | And I press "C-p"
23 | And I press "C-p"
24 | And I press "C-e"
25 | Then text property at point includes "rear-nonsticky"
26 |
27 | @poly
28 | Scenario: moving cells requires refontification
29 | Given new python notebook
30 | And I press "C-c C-t"
31 | And I type "# Header"
32 | And I press "RET"
33 | And I press "C-c C-b"
34 | And I type "import math"
35 | And I press "C-c C-c"
36 | And I press "M-"
37 | And I press "C-"
38 | And I go to word "Header"
39 |
40 | @poly
41 | Scenario: Indirect buffers confuse buffer switching without hacks
42 | Given new python notebook
43 | And I type "print("hello")"
44 | And I switch to buffer "*Messages*"
45 | And I switch to buffer like "Untitled"
46 | And I press "C-x b"
47 | Then I should be in buffer "*Messages*"
48 | And I press "C-x b"
49 | Then eval "(string-prefix-p " *ein:" (buffer-name))"
50 |
51 | @poly
52 | Scenario: ido-mode spits back the indirect buffer we're already in
53 | Given eval "(ido-mode t)"
54 | Given new python notebook
55 | And I type "print("hello")"
56 | And I press "C-x b"
57 | And I press "C-x b"
58 | Then eval "(string-prefix-p " *ein:" (buffer-name))"
59 | And eval "(ido-mode -1)"
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | paths-ignore:
6 | - '**.md'
7 | - '**.rst'
8 | push:
9 | paths-ignore:
10 | - '**.md'
11 | - '**.rst'
12 | branches-ignore:
13 | - 'master'
14 |
15 | jobs:
16 | build:
17 | runs-on: ${{ matrix.os }}
18 | strategy:
19 | matrix:
20 | os: [ubuntu-latest]
21 | emacs_version: [27.2, 28.2, 29.1]
22 | python-version: [3.8]
23 |
24 | steps:
25 | - uses: actions/checkout@v4
26 |
27 | - name: python ${{ matrix.python-version }}
28 | uses: actions/setup-python@v4
29 | with:
30 | python-version: ${{ matrix.python-version }}
31 |
32 | - name: nix
33 | uses: purcell/setup-emacs@master
34 | with:
35 | version: ${{ matrix.emacs_version }}
36 |
37 | - uses: actions/cache@v4
38 | id: cache-cask-packages
39 | with:
40 | path: .cask
41 | key: cache-cask-packages-001
42 |
43 | - uses: actions/cache@v4
44 | id: cache-cask-executable
45 | with:
46 | path: ~/.cask
47 | key: cache-cask-executable-001
48 |
49 | - uses: cask/setup-cask@master
50 | if: steps.cache-cask-executable.outputs.cache-hit != 'true'
51 | with:
52 | version: snapshot
53 |
54 | - name: paths
55 | run: |
56 | echo "$HOME/.cask/bin" >> $GITHUB_PATH
57 |
58 | - name: apt-get
59 | if: startsWith(runner.os, 'disable')
60 | run: |
61 | sudo apt-get -yq update
62 | DEBIAN_FRONTEND=noninteractive sudo apt-get -yq install gnutls-bin sharutils nodejs gfortran gnupg2 dirmngr libreadline-dev libcurl4-openssl-dev texlive-latex-base libfuse-dev libxml2-dev libssl-dev libzmq3-dev jupyter-core jupyter-client
63 |
64 | - name: test-mem-constrained
65 | run: |
66 | pip install yq
67 | make quick
68 | continue-on-error: ${{ matrix.emacs_version == 'snapshot' }}
69 |
--------------------------------------------------------------------------------
/tools/install-cask.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Install cask for Travis CI
4 | # or if already installed, then check for updates
5 | # Author: gonewest818 https://github.com/clojure-emacs/cider/pull/2139
6 |
7 | set -x
8 |
9 | WORKDIR=${HOME}/local
10 | CASKDIR=$WORKDIR/cask
11 |
12 | . tools/retry.sh
13 |
14 | update_elpa_keys() {
15 | mkdir -p $HOME/.emacs.d/elpa/gnupg || true
16 | chmod 700 $HOME/.emacs.d/elpa/gnupg
17 | GPG=gpg
18 | if which gpg2 ; then
19 | GPG=gpg2
20 | fi
21 | for i in 1 2 3 ; do
22 | if ${GPG} -q --homedir $HOME/.emacs.d/elpa/gnupg -k | grep 81E42C40 ; then
23 | return 0
24 | fi
25 | if [ $i -gt 1 ] ; then
26 | sleep 5
27 | fi
28 | ${GPG} --keyserver hkp://ipv4.pool.sks-keyservers.net --homedir $HOME/.emacs.d/elpa/gnupg --recv-keys 066DAFCB81E42C40
29 | done
30 | return 1
31 | }
32 |
33 | copy_keys() {
34 | mkdir -p $(cask package-directory) || true
35 | mkdir -p $HOME/.cask || true
36 | rsync -azSHe ssh $HOME/.cask $(dirname $(dirname $(dirname $(cask package-directory))))
37 | rsync -azSHe ssh $HOME/.emacs.d/elpa/gnupg $(cask package-directory)
38 | }
39 |
40 | cask_upgrade_cask_or_reset() {
41 | cask upgrade-cask || { rm -rf $HOME/.emacs.d/.cask && false; }
42 | }
43 |
44 | cask_install_or_reset() {
45 | cask install
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-kernelinfo.el is free software: you can redistribute it and/or
10 | ;; modify it under the terms of the GNU General Public License as
11 | ;; published by the Free Software Foundation, either version 3 of the
12 | ;; License, or (at your option) any later version.
13 |
14 | ;; ein-kernelinfo.el is distributed in the hope that it will be
15 | ;; useful, but WITHOUT ANY WARRANTY; without even the implied warranty
16 | ;; of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-kernelinfo.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'eieio)
29 | (require 'ein-kernel)
30 |
31 | (defclass ein:kernelinfo ()
32 | ((kernel
33 | :initarg :kernel :type ein:$kernel
34 | :documentation "Kernel instance.")
35 | (get-buffers
36 | :initarg :get-buffers
37 | :documentation "A packed function to get buffers associated
38 | with the kernel. The buffer local `default-directory' variable
39 | in these buffer will be synced with the kernel's cwd.")
40 | (hostname
41 | :initarg :hostname :type string
42 | :documentation "Host name of the machine where the kernel is running on.")
43 | (ccwd
44 | :initarg :ccwd :type string
45 | :documentation "cached CWD (last time checked CWD)."))
46 | :documentation "Info related (but unimportant) to kernel")
47 |
48 | (defun ein:kernelinfo-new (kernel get-buffers)
49 | "Make a new `ein:kernelinfo' instance based on KERNEL and GET-BUFFERS."
50 | (let ((kerinfo (make-instance 'ein:kernelinfo)))
51 | (setf (slot-value kerinfo 'kernel) kernel)
52 | (setf (slot-value kerinfo 'get-buffers) get-buffers)
53 | kerinfo))
54 | (provide 'ein-kernelinfo)
55 |
56 | ;;; ein-kernelinfo.el ends here
57 |
--------------------------------------------------------------------------------
/test/test-ein-worksheet-notebook.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 |
4 | (require 'ein-notebook)
5 | (require 'ein-testing-notebook)
6 |
7 |
8 | ;;; Event handler
9 |
10 | (defun ein:testing-worksheet-set-dirty
11 | (pre-dirty value post-dirty fired-in)
12 | (with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
13 | (when pre-dirty
14 | (ein:cell-goto (ein:worksheet-get-current-cell))
15 | (insert "something"))
16 | (should (equal (ein:worksheet-modified-p ein:%worksheet%) pre-dirty))
17 | (with-current-buffer (funcall fired-in)
18 | (let ((events (oref ein:%worksheet% :events))
19 | (cell (ein:worksheet-get-current-cell)))
20 | (ein:events-trigger events 'set_dirty.Worksheet
21 | (list :cell cell :value value))))
22 | (should (equal (ein:worksheet-modified-p ein:%worksheet%) post-dirty))))
23 |
24 | (defun ein:testing-scratchsheet-buffer ()
25 | (ein:worksheet-buffer (ein:notebook-scratchsheet-open ein:%notebook%)))
26 |
27 | (defmacro ein:testing-worksheet-set-dirty-deftest
28 | (pre-dirty value post-dirty &optional fired-in)
29 | (let ((name (intern (format "ein:worksheet-set-dirty/%s-to-%s-fired-in-%s"
30 | pre-dirty value
31 | (or fired-in "current-buffer"))))
32 | (fired-in-defun
33 | (cl-case fired-in
34 | (scratchsheet 'ein:testing-scratchsheet-buffer)
35 | (t 'current-buffer))))
36 | `(ert-deftest ,name ()
37 | (ein:testing-worksheet-set-dirty ,pre-dirty ,value ,post-dirty
38 | #',fired-in-defun))))
39 |
40 | (ein:testing-worksheet-set-dirty-deftest t nil nil)
41 | (ein:testing-worksheet-set-dirty-deftest t t t )
42 | (ein:testing-worksheet-set-dirty-deftest nil nil nil)
43 | (ein:testing-worksheet-set-dirty-deftest nil t t )
44 | (ein:testing-worksheet-set-dirty-deftest t nil t scratchsheet)
45 | (ein:testing-worksheet-set-dirty-deftest t t t scratchsheet)
46 | (ein:testing-worksheet-set-dirty-deftest nil nil nil scratchsheet)
47 | (ein:testing-worksheet-set-dirty-deftest nil t nil scratchsheet)
48 |
--------------------------------------------------------------------------------
/features/ipdb.feature:
--------------------------------------------------------------------------------
1 | @ipdb
2 | Scenario: ein-ipdb works... poorly
3 | Given new python notebook
4 | And I type "from IPython.core.debugger import set_trace"
5 | And I press "RET"
6 | And I type "for i in range(2):"
7 | And I press "RET"
8 | And I type " set_trace()"
9 | And I press "RET"
10 | And I type " print(i)"
11 | And I press "RET"
12 | And I press "C-c C-c"
13 | And I switch to buffer like "*ipdb: "
14 | And I wait for buffer process
15 | And I type "c"
16 | And I press "RET"
17 | And I wait for buffer to say "0\n"
18 | And I type "c"
19 | And I press "RET"
20 | And I wait for buffer to say "Finished"
21 | And eval "(kill-buffer)"
22 |
23 | @ipdb
24 | Scenario: ein-ipdb needs to not flail on C-d
25 | Given new python notebook
26 | And I type "from IPython.core.debugger import set_trace"
27 | And I press "RET"
28 | And I type "for i in range(2):"
29 | And I press "RET"
30 | And I type " set_trace()"
31 | And I press "RET"
32 | And I type " print(i)"
33 | And I press "RET"
34 | And I press "C-c C-c"
35 | And I switch to buffer like "*ipdb: "
36 | And I wait for buffer process
37 | And I type "c"
38 | And I press "RET"
39 | And I wait for buffer to say "0\n"
40 | And I press "C-d"
41 | And I wait for buffer to say "Finished"
42 | And eval "(kill-buffer)"
43 |
44 | @ipdb
45 | Scenario: kill buffer
46 | Given new python notebook
47 | And I type "from IPython.core.debugger import set_trace"
48 | And I press "RET"
49 | And I type "for i in range(2):"
50 | And I press "RET"
51 | And I type " set_trace()"
52 | And I press "RET"
53 | And I type " print(i)"
54 | And I press "RET"
55 | And I press "C-c C-c"
56 | And I switch to buffer like "*ipdb: "
57 | And I wait for buffer process
58 | And I type "c"
59 | And I press "RET"
60 | And I wait for buffer to say "0\n"
61 | And I switch to buffer like "Untitled"
62 | Then I should see "In [*]"
63 | And header says "Kernel busy..."
64 | And I switch to buffer like "*ipdb: "
65 | And I press "C-x k"
66 | And I switch to buffer like "Untitled"
67 | And I wait for buffer to not say "In [*]"
68 | And header does not say "Kernel busy..."
69 |
--------------------------------------------------------------------------------
/lisp/ein-pytools.el:
--------------------------------------------------------------------------------
1 | ;;; ein-pytools.el --- Python tools build on top of kernel -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012- Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-pytools.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-pytools.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-pytools.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'ein-kernel)
29 |
30 | (defun ein:pytools-jump-to-source-command (&optional other-window)
31 | "Jump to the source code of the object at point.
32 | When the prefix argument ``C-u`` is given, open the source code
33 | in the other window. You can explicitly specify the object by
34 | selecting it."
35 | (interactive "P")
36 | (cl-letf (((symbol-function 'xref--prompt-p) #'ignore))
37 | (if other-window
38 | (call-interactively #'xref-find-definitions-other-window)
39 | (call-interactively #'xref-find-definitions))))
40 |
41 | (defun ein:pytools-jump-back-command (&optional _other-window)
42 | "Go back to the point where `ein:pytools-jump-to-source-command'
43 | is executed last time. When the prefix argument ``C-u`` is
44 | given, open the last point in the other window."
45 | (interactive "P")
46 | (call-interactively (if (fboundp 'xref-go-back)
47 | #'xref-go-back
48 | (symbol-function 'xref-pop-marker-stack))))
49 |
50 | (define-obsolete-function-alias
51 | 'ein:pytools-eval-string-internal
52 | 'ein:shared-output-eval-string "0.1.2")
53 |
54 | (provide 'ein-pytools)
55 |
56 | ;;; ein-pytools.el ends here
57 |
--------------------------------------------------------------------------------
/lisp/ein-kill-ring.el:
--------------------------------------------------------------------------------
1 | ;;; ein-kill-ring.el --- Kill-ring for cells -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012- Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-kill-ring.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-kill-ring.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-kill-ring.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;; Stolen from simple.el.
25 |
26 | ;;; Code:
27 |
28 | (defvar ein:kill-ring nil)
29 | (defvar ein:kill-ring-yank-pointer nil)
30 | (defvar ein:kill-ring-max kill-ring-max)
31 |
32 | (defun ein:kill-new (obj)
33 | "Make OBJ the latest kill in the kill ring `ein:kill-ring'.
34 | Set `ein:kill-ring-yank-pointer' to point to it."
35 | (push obj ein:kill-ring)
36 | (if (> (length ein:kill-ring) ein:kill-ring-max)
37 | (setcdr (nthcdr (1- ein:kill-ring-max) ein:kill-ring) nil))
38 | (setq ein:kill-ring-yank-pointer ein:kill-ring))
39 |
40 | (defun ein:current-kill (n &optional do-not-move)
41 | "Rotate the yanking point by N places, and then return that kill.
42 | If optional arg DO-NOT-MOVE is non-nil, then don't actually
43 | move the yanking point; just return the Nth kill forward."
44 | (unless ein:kill-ring (error "Kill ring is empty"))
45 | (let ((ARGth-kill-element
46 | (nthcdr (mod (- n (length ein:kill-ring-yank-pointer))
47 | (length ein:kill-ring))
48 | ein:kill-ring)))
49 | (unless do-not-move
50 | (setq ein:kill-ring-yank-pointer ARGth-kill-element))
51 | (car ARGth-kill-element)))
52 |
53 | (provide 'ein-kill-ring)
54 |
55 | ;;; ein-kill-ring.el ends here
56 |
--------------------------------------------------------------------------------
/lisp/ein-events.el:
--------------------------------------------------------------------------------
1 | ;;; ein-events.el --- Event module -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012- Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-events.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-events.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-events.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'eieio)
29 |
30 | (require 'ein-core)
31 | (require 'ein-classes)
32 | (require 'ein-log)
33 |
34 | (defun ein:events-new ()
35 | "Return a new event handler instance."
36 | (make-instance 'ein:events))
37 |
38 | (defun ein:events-trigger (events event-type &optional data)
39 | "Trigger EVENT-TYPE and let event handler EVENTS handle that event."
40 | (ein:log 'debug "Event: %S" event-type)
41 | (aif (gethash event-type (slot-value events 'callbacks))
42 | (mapc (lambda (cb-arg) (ein:funcall-packed cb-arg data)) it)
43 | (ein:log 'info "Unknown event: %S" event-type)))
44 |
45 |
46 | (cl-defmethod ein:events-on ((events ein:events) event-type
47 | callback &optional arg)
48 | "Set event trigger hook.
49 |
50 | When EVENT-TYPE is triggered on the event handler EVENTS,
51 | CALLBACK is called. CALLBACK must take two arguments:
52 | ARG as the first argument and DATA, which is passed via
53 | `ein:events-trigger', as the second."
54 | (cl-assert (symbolp event-type) t "%s not symbol" event-type)
55 | (let* ((table (slot-value events 'callbacks))
56 | (cbs (gethash event-type table)))
57 | (push (cons callback arg) cbs)
58 | (puthash event-type cbs table)))
59 |
60 |
61 | (provide 'ein-events)
62 |
63 | ;;; ein-events.el ends here
64 |
--------------------------------------------------------------------------------
/lisp/ein.el:
--------------------------------------------------------------------------------
1 | ;;; ein.el --- jupyter notebook client -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012-2019 The Authors of the Emacs IPython Notebook (EIN)
4 |
5 | ;; Authors: dickmao
6 | ;; John Miller
7 | ;; Takafumi Arakaki
8 | ;; Version: 0.17.1pre
9 | ;; Package-Requires: ((emacs "26.1") (websocket "1.12") (anaphora "1.0.4") (request "0.3.3") (deferred "0.5") (polymode "0.2.2") (dash "2.13.0") (with-editor "0pre"))
10 | ;; URL: https://github.com/dickmao/emacs-ipython-notebook
11 | ;; Keywords: jupyter, literate programming, reproducible research
12 |
13 | ;; This file is NOT part of GNU Emacs.
14 |
15 | ;; ein.el is free software: you can redistribute it and/or modify
16 | ;; it under the terms of the GNU General Public License as published by
17 | ;; the Free Software Foundation, either version 3 of the License, or
18 | ;; (at your option) any later version.
19 |
20 | ;; ein.el is distributed in the hope that it will be useful,
21 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 | ;; GNU General Public License for more details.
24 |
25 | ;; You should have received a copy of the GNU General Public License
26 | ;; along with ein.el. If not, see .
27 |
28 | ;;; Commentary:
29 |
30 | ;; Emacs IPython Notebook (EIN), despite its name, is a jupyter client for all
31 | ;; languages. It does not work under non-WSL Windows environments.
32 | ;;
33 | ;; As of 2023, EIN has been sunset for a number of years having been
34 | ;; unable to keep up with jupyter's web-first ecosystem. Even during
35 | ;; its heyday EIN never fully reconciled emac's monolithic buffer
36 | ;; architecture to the notebook's by-cell discretization, leaving
37 | ;; gaping functional holes like crippled undo.
38 | ;;
39 | ;; **As of 2025, a greenfield notebook implementation resides at,**
40 | ;;
41 | ;; https://github.com/commercial-emacs/xjupyter.git
42 | ;;
43 | ;; It features full-fledged undo and relies on "mode overlays"
44 | ;; instead of the complex and fragile polymode.
45 |
46 | ;;; Code:
47 |
48 | (when (boundp 'mouse-buffer-menu-mode-groups)
49 | (add-to-list 'mouse-buffer-menu-mode-groups
50 | '("^ein:" . "ein")))
51 |
52 | (provide 'ein)
53 |
54 | ;;; ein.el ends here
55 |
--------------------------------------------------------------------------------
/lisp/ein-node.el:
--------------------------------------------------------------------------------
1 | ;;; ein-node.el --- Structure to hold data in ewoc node -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012- Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-node.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-node.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-node.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'ewoc)
29 | (require 'ein-core)
30 |
31 | (cl-defstruct ein:$node
32 | path ; list of path
33 | data ; actual data
34 | class ; list
35 | )
36 |
37 | (defun ein:node-new (path data &optional class &rest args)
38 | (apply #'make-ein:$node :path path :data data :class class args))
39 |
40 | (defun ein:node-add-class (node &rest classes)
41 | (mapc (lambda (c) (cl-pushnew c (ein:$node-class node))) classes))
42 |
43 | (defun ein:node-remove-class (node &rest classes)
44 | (let ((node-class (ein:$node-class node)))
45 | (mapc (lambda (c) (setq node-class (delq c node-class))) classes)
46 | (setf (ein:$node-class node) node-class)))
47 |
48 | (defun ein:node-has-class (node class)
49 | (memq class (ein:$node-class node)))
50 |
51 | (defun ein:node-filter (ewoc-node-list &rest args)
52 | (cl-loop for (key . class) in (ein:plist-iter args)
53 | do (setq ewoc-node-list
54 | (cl-loop for ewoc-node in ewoc-node-list
55 | for node = (ewoc-data ewoc-node)
56 | when (cl-case key
57 | (:is (ein:node-has-class node class))
58 | (:not (not (ein:node-has-class node class)))
59 | (t (error "%s is not supported" key)))
60 | collect ewoc-node)))
61 | ewoc-node-list)
62 |
63 | (provide 'ein-node)
64 |
65 | ;;; ein-node.el ends here
66 |
--------------------------------------------------------------------------------
/features/undo.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {
7 | "autoscroll": false,
8 | "collapsed": false,
9 | "ein.hycell": false,
10 | "ein.tags": "worksheet-0",
11 | "slideshow": {
12 | "slide_type": "-"
13 | }
14 | },
15 | "outputs": [
16 | {
17 | "name": "stdout",
18 | "output_type": "stream",
19 | "text": [
20 | "hello\n"
21 | ]
22 | }
23 | ],
24 | "source": [
25 | "print(\"hello\")"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {
32 | "autoscroll": false,
33 | "collapsed": false,
34 | "ein.hycell": false,
35 | "ein.tags": "worksheet-0",
36 | "slideshow": {
37 | "slide_type": "-"
38 | }
39 | },
40 | "outputs": [],
41 | "source": [
42 | "abba\n",
43 | "\n",
44 | "\n",
45 | "abab\n",
46 | "baba"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": 3,
52 | "metadata": {
53 | "autoscroll": false,
54 | "collapsed": false,
55 | "ein.hycell": false,
56 | "ein.tags": "worksheet-0",
57 | "slideshow": {
58 | "slide_type": "-"
59 | }
60 | },
61 | "outputs": [
62 | {
63 | "data": {
64 | "text/plain": [
65 | "12.182493960703473"
66 | ]
67 | },
68 | "execution_count": 3,
69 | "metadata": {},
70 | "output_type": "execute_result"
71 | }
72 | ],
73 | "source": [
74 | "import math\n",
75 | "math.exp(2.5)"
76 | ]
77 | },
78 | {
79 | "cell_type": "code",
80 | "execution_count": null,
81 | "metadata": {
82 | "autoscroll": false,
83 | "collapsed": false,
84 | "ein.hycell": false,
85 | "ein.tags": "worksheet-0",
86 | "slideshow": {
87 | "slide_type": "-"
88 | }
89 | },
90 | "outputs": [],
91 | "source": [
92 | "abab\n",
93 | "abab"
94 | ]
95 | }
96 | ],
97 | "metadata": {
98 | "kernelspec": {
99 | "argv": [
100 | "python",
101 | "-m",
102 | "ipykernel_launcher",
103 | "-f",
104 | "{connection_file}"
105 | ],
106 | "display_name": "Python 3",
107 | "env": null,
108 | "interrupt_mode": "signal",
109 | "language": "python",
110 | "metadata": null,
111 | "name": "python3"
112 | },
113 | "name": "undo.ipynb"
114 | },
115 | "nbformat": 4,
116 | "nbformat_minor": 2
117 | }
118 |
--------------------------------------------------------------------------------
/test/ein-testing-cell.el:
--------------------------------------------------------------------------------
1 | ;;; ein-testing-cell.el --- Testing utilities for cell module -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012 Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-testing-cell.el is free software: you can redistribute it
10 | ;; and/or modify it under the terms of the GNU General Public License
11 | ;; as published by the Free Software Foundation, either version 3 of
12 | ;; the License, or (at your option) any later version.
13 |
14 | ;; ein-testing-cell.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-testing-cell.el.
21 | ;; If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | ;;
26 |
27 | ;;; Code:
28 |
29 | (require 'json)
30 |
31 | (defvar ein:testing-example-svg "\
32 |
33 |
35 |
36 | ")
39 |
40 | (defun ein:testing-codecell-pyout-data (text &optional prompt-number)
41 | "Create a plist representing JSON data for code-cell output.
42 | TEXT is a string and PROMPT-NUMBER is an integer."
43 | (list :output_type "pyout"
44 | :prompt_number (or prompt-number 0)
45 | :text text))
46 |
47 | (defun ein:testing-codecell-data (&optional input prompt-number outputs)
48 | "Create a plist representing JSON data for code-type cell.
49 | To make OUTPUTS data, use `ein:testing-codecell-pyout-data'."
50 | (list :cell_type "code"
51 | :source (or input "")
52 | :language "python"
53 | :outputs outputs
54 | :metadata (list :collapsed json-false :autoscroll json-false)
55 | :execution_count prompt-number))
56 |
57 | (defun ein:testing-textcell-data (&optional source cell-type)
58 | (list :cell_type cell-type
59 | :source (or source "")))
60 |
61 | (defun ein:testing-markdowncell-data (&optional source)
62 | (ein:testing-textcell-data source "markdown"))
63 |
64 | (defun ein:testing-rawcell-data (&optional source)
65 | (ein:testing-textcell-data source "raw"))
66 |
67 | (defun ein:testing-htmlcell-data (&optional source)
68 | (ein:testing-textcell-data source "html"))
69 |
70 | (provide 'ein-testing-cell)
71 |
72 | ;;; ein-testing-cell.el ends here
73 |
--------------------------------------------------------------------------------
/lisp/ein-file.el:
--------------------------------------------------------------------------------
1 | ;;; ein-file.el --- Editing files downloaded from jupyter -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2017- John M. Miller
4 |
5 | ;; Authors: Takafumi Arakaki
6 | ;; John M. Miller
7 |
8 | ;; This file is NOT part of GNU Emacs.
9 |
10 | ;; ein-file.el is free software: you can redistribute it and/or modify
11 | ;; it under the terms of the GNU General Public License as published by
12 | ;; the Free Software Foundation, either version 3 of the License, or
13 | ;; (at your option) any later version.
14 |
15 | ;; ein-file.el is distributed in the hope that it will be useful,
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ;; GNU General Public License for more details.
19 |
20 | ;; You should have received a copy of the GNU General Public License
21 | ;; along with ein-notebooklist.el. If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | (defvar *ein:file-buffername-template* "'/ein:%s:%s")
26 | (ein:deflocal ein:content-file-buffer--content nil)
27 |
28 | ;; (push '("^ein:.*" . ein:content-file-handler)
29 | ;; file-name-handler-alist)
30 |
31 | (defun ein:file-buffer-name (urlport path)
32 | (format *ein:file-buffername-template*
33 | urlport
34 | path))
35 |
36 | (defun ein:file-open (url-or-port path)
37 | (interactive (ein:notebooklist-parse-nbpath (ein:notebooklist-ask-path "file")))
38 | (ein:content-query-contents url-or-port path #'ein:file-open-finish nil))
39 |
40 | (defun ein:file-open-finish (content)
41 | (with-current-buffer (get-buffer-create (ein:file-buffer-name (ein:$content-url-or-port content)
42 | (ein:$content-path content)))
43 | (setq ein:content-file-buffer--content content)
44 | (let ((raw-content (ein:$content-raw-content content)))
45 | (if (eq system-type 'windows-nt)
46 | (insert (decode-coding-string raw-content 'utf-8))
47 | (insert raw-content)))
48 | (set-visited-file-name (buffer-name))
49 | (set-auto-mode)
50 | (add-hook 'write-contents-functions 'ein:content-file-save nil t) ;; FIXME Brittle, will not work
51 | ;; if user changes major mode.
52 | (ein:log 'verbose "Opened file %s" (ein:$content-name content))
53 | (set-buffer-modified-p nil)
54 | (goto-char (point-min))
55 | (pop-to-buffer (buffer-name))))
56 |
57 | (defun ein:content-file-save ()
58 | (setf (ein:$content-raw-content ein:content-file-buffer--content) (buffer-string))
59 | (ein:content-save ein:content-file-buffer--content)
60 | (set-buffer-modified-p nil)
61 | t)
62 |
63 | (provide 'ein-file)
64 |
--------------------------------------------------------------------------------
/lisp/ein-pager.el:
--------------------------------------------------------------------------------
1 | ;;; ein-pager.el --- Pager module -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012- Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-pager.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-pager.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-pager.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'ansi-color)
29 |
30 | (require 'ein-core)
31 | (require 'ein-events)
32 | (require 'view)
33 |
34 | ;; FIXME: Make a class with `:get-notebook-name' slot like `ein:worksheet'
35 |
36 | (declare-function ess-help-underline "ess-help")
37 |
38 | (defun ein:pager-new (name events)
39 | ;; currently pager = name.
40 | (ein:pager-bind-events name events)
41 | name)
42 |
43 | (defun ein:pager-bind-events (pager events)
44 | "Bind events related to PAGER to the event handler EVENTS."
45 | (ein:events-on events
46 | 'open_with_text.Pager
47 | #'ein:pager--open-with-text
48 | pager))
49 |
50 | (defun ein:pager--open-with-text (pager data)
51 | (let ((text (plist-get data :text)))
52 | (unless (equal (ein:trim text) "")
53 | (ein:pager-clear pager)
54 | (ein:pager-expand pager)
55 | (ein:pager-append-text pager text))))
56 |
57 | (defun ein:pager-clear (pager)
58 | (ein:with-read-only-buffer (get-buffer-create pager)
59 | (erase-buffer)))
60 |
61 | (defun ein:pager-expand (pager)
62 | (pop-to-buffer (get-buffer-create pager))
63 | (goto-char (point-min)))
64 |
65 | (defun ein:pager-append-text (pager text)
66 | (ein:with-read-only-buffer (get-buffer-create pager)
67 | (insert (ansi-color-apply text))
68 | (if (featurep 'ess-help)
69 | (ess-help-underline))
70 | (unless (eql 'ein:pager-mode major-mode)
71 | (ein:pager-mode))))
72 |
73 | ;; FIXME: this should be automatically called when opening pager.
74 | (defun ein:pager-goto-docstring-bset-loc ()
75 | "Goto the best location of the documentation."
76 | (interactive)
77 | (goto-char (point-min))
78 | (search-forward-regexp "^Docstring:")
79 | (beginning-of-line 0)
80 | (recenter 0))
81 |
82 | (defvar ein:pager-mode-map
83 | (let ((map (make-sparse-keymap)))
84 | (define-key map "\C-c\C-b" 'ein:pager-goto-docstring-bset-loc)
85 | map)
86 | "Keymap for ein:pager-mode.")
87 |
88 | (define-derived-mode ein:pager-mode view-mode "ein:pager"
89 | "IPython notebook pager mode.
90 | Commands:
91 | \\{ein:pager-mode-map}"
92 | (setq-local view-no-disable-on-exit t)
93 | (font-lock-mode))
94 |
95 |
96 | (provide 'ein-pager)
97 |
98 | ;;; ein-pager.el ends here
99 |
--------------------------------------------------------------------------------
/lisp/ein-ipynb-mode.el:
--------------------------------------------------------------------------------
1 | ;;; ein-ipynb-mode.el --- A simple mode for ipynb file -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012 Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-ipynb-mode.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-ipynb-mode.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-ipynb-mode.el.
21 | ;; If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | ;;
26 |
27 | ;;; Code:
28 |
29 | (require 'ein-process)
30 | (require 'js)
31 |
32 | ;;;###autoload
33 | (define-derived-mode ein:ipynb-mode js-mode "ein:ipynb"
34 | "A simple mode for ipynb file.
35 |
36 | \\{ein:ipynb-mode-map}
37 | "
38 | :group 'ein
39 | :after-hook
40 | (let* ((filename (file-name-nondirectory buffer-file-truename))
41 | (remote-filename (concat (file-name-as-directory "run-remote") filename)))
42 | ;; fragile hack to refresh s3fuse
43 | (call-process "cat" nil nil nil remote-filename)
44 | (when (and (file-readable-p remote-filename)
45 | (file-newer-than-file-p remote-filename filename)
46 | (prog1
47 | (let ((inhibit-quit t))
48 | (prog1
49 | (with-local-quit
50 | (y-or-n-p "Corresponding run-remote is newer. Replace? (will first backup) "))
51 | (setq quit-flag nil)))
52 | (message "")))
53 | (if-let ((make-backup-files t)
54 | (where-to (funcall make-backup-file-name-function buffer-file-name)))
55 | (let* (backup-inhibited
56 | (orig-hooks find-file-hook)
57 | (reassure (lambda ()
58 | (message "Backed up to %s" where-to)
59 | (setq find-file-hook orig-hooks))))
60 | (backup-buffer)
61 | (copy-file remote-filename filename t t)
62 | (add-hook 'find-file-hook reassure nil)
63 | (find-file-noselect filename t))
64 | (message "Backup failed. Not replaced")))))
65 |
66 | (let ((map ein:ipynb-mode-map))
67 | (set-keymap-parent map js-mode-map)
68 | (define-key map "\C-c\C-z" 'ein:process-find-file-callback)
69 | (define-key map "\C-c\C-o" 'ein:process-find-file-callback)
70 | (define-key map "\C-c\C-r" 'ein:gat-run-remote)
71 | (easy-menu-define ein:ipynb-menu map "EIN IPyNB Mode Menu"
72 | `("EIN IPyNB File"
73 | ,@(ein:generate-menu
74 | '(("Open notebook" ein:process-find-file-callback))))))
75 |
76 | ;;;###autoload
77 | (add-to-list 'auto-mode-alist '("\\.ipynb\\'" . ein:ipynb-mode))
78 |
79 | (provide 'ein-ipynb-mode)
80 |
81 | ;;; ein-ipynb-mode.el ends here
82 |
--------------------------------------------------------------------------------
/test/test-ein-kernel.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 |
3 | (require 'ert)
4 |
5 | (require 'ein-kernel)
6 | (require 'ein-testing-notebook)
7 |
8 | (defun eintest:kernel-new (port)
9 | (ein:kernel-new port "" nil "/api/kernels"
10 | (get-buffer-create "*eintest: dummy for kernel test*")))
11 |
12 | (ert-deftest ein:kernel-restart-check-url ()
13 | (let* ((notebook (ein:notebook-new ein:testing-notebook-dummy-url "" nil))
14 | (kernel (eintest:kernel-new 8888))
15 | (kernel-id "KERNEL-ID")
16 | (desired-url "http://127.0.0.1:8888/api/sessions/KERNEL-ID")
17 | (dummy-response (make-request-response))
18 | got-url)
19 | (setf (ein:$notebook-kernel notebook) kernel)
20 | (cl-letf (((symbol-function 'request)
21 | (lambda (url &rest _ignore) (setq got-url url) dummy-response))
22 | ((symbol-function 'set-process-query-on-exit-flag) #'ignore)
23 | ((symbol-function 'ein:kernel-stop-channels) #'ignore)
24 | ((symbol-function 'ein:websocket) (lambda (&rest _ignore) (make-ein:$websocket :ws nil :kernel kernel :closed-by-client nil)))
25 | ((symbol-function 'ein:events-trigger) #'ignore)
26 | ((symbol-function 'ein:get-notebook-or-error) (lambda () (ein:get-notebook))))
27 | (ein:kernel-retrieve-session--success
28 | kernel nil :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id))
29 | (ein:kernel-restart-session (ein:$notebook-kernel notebook))
30 | (should (equal got-url desired-url)))))
31 |
32 | (ert-deftest ein:kernel-interrupt-check-url ()
33 | (let* ((kernel (eintest:kernel-new 8888))
34 | (kernel-id "KERNEL-ID")
35 | (desired-url "http://127.0.0.1:8888/api/kernels/KERNEL-ID/interrupt")
36 | (dummy-response (make-request-response))
37 | got-url)
38 |
39 | (cl-letf (((symbol-function 'request)
40 | (lambda (url &rest _ignore) (setq got-url url) dummy-response))
41 | ((symbol-function 'set-process-query-on-exit-flag) #'ignore)
42 | ((symbol-function 'ein:kernel-stop-channels) #'ignore)
43 | ((symbol-function 'ein:websocket) (lambda (&rest _ignore) (make-ein:$websocket :ws nil :kernel kernel :closed-by-client nil)))
44 | ((symbol-function 'ein:websocket-open-p) (lambda (&rest _ignore) t)))
45 | (ein:kernel-retrieve-session--success
46 | kernel nil :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id))
47 | (ein:kernel-interrupt kernel)
48 | (should (equal got-url desired-url)))))
49 |
50 | (ert-deftest ein:kernel-kill-check-url ()
51 | (let* ((kernel (eintest:kernel-new 8888))
52 | (kernel-id "KERNEL-ID")
53 | (desired-url "http://127.0.0.1:8888/api/sessions/KERNEL-ID")
54 | (dummy-response (make-request-response))
55 | got-url)
56 | (cl-letf (((symbol-function 'request)
57 | (lambda (url &rest _ignore) (setq got-url url) dummy-response))
58 | ((symbol-function 'set-process-query-on-exit-flag) #'ignore)
59 | ((symbol-function 'ein:kernel-stop-channels) #'ignore)
60 | ((symbol-function 'ein:websocket)
61 | (lambda (&rest _ignore) (make-ein:$websocket :ws nil :kernel kernel
62 | :closed-by-client nil)))
63 | ((symbol-function 'ein:websocket-open-p) (lambda (&rest _ignore) t)))
64 | (ein:kernel-retrieve-session--success
65 | kernel nil :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id))
66 | (ein:kernel-delete-session nil :kernel kernel))
67 | (should (equal got-url desired-url))))
68 |
--------------------------------------------------------------------------------
/test/test-ein-notification.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 |
4 | (require 'ein-notification)
5 |
6 | (defun ein:testing-notification-tab-mock ()
7 | (make-instance 'ein:notification-tab
8 | :get-list (lambda () '(a b c))
9 | :get-current (lambda () 'a)))
10 |
11 | (ert-deftest ein:header-line-normal ()
12 | (let* ((ein:%notification% (ein:notification))
13 | (kernel (oref ein:%notification% :kernel)))
14 | (ignore kernel)
15 | (oset ein:%notification% :tab (ein:testing-notification-tab-mock))
16 | (should (string-prefix-p "IP[y]: " (ein:header-line)))))
17 |
18 | (ert-deftest ein:header-line-kernel-status-busy ()
19 | (let* ((ein:%notification% (ein:notification))
20 | (kernel (oref ein:%notification% :kernel)))
21 | (oset ein:%notification% :tab (ein:testing-notification-tab-mock))
22 | (ein:notification-status-set kernel
23 | 'status_busy.Kernel)
24 | (should (string-prefix-p "IP[y]: Kernel busy..."
25 | (ein:header-line)))))
26 |
27 | (ert-deftest ein:header-line-notebook-status-busy ()
28 | (let* ((ein:%notification% (ein:notification))
29 | (notebook (oref ein:%notification% :notebook)))
30 | (oset ein:%notification% :tab (ein:testing-notification-tab-mock))
31 | (ein:notification-status-set notebook
32 | 'notebook_saved.Notebook)
33 | (should (string-prefix-p "IP[y]: Notebook saved"
34 | (ein:header-line)))))
35 |
36 | (ert-deftest ein:header-line-notebook-complex ()
37 | (let* ((ein:%notification% (ein:notification))
38 | (kernel (oref ein:%notification% :kernel))
39 | (notebook (oref ein:%notification% :notebook)))
40 | (oset ein:%notification% :tab (ein:testing-notification-tab-mock))
41 | (ein:notification-status-set kernel
42 | 'status_dead.Kernel)
43 | (ein:notification-status-set notebook
44 | 'notebook_saving.Notebook)
45 | (should (string-prefix-p
46 | (concat "IP[y]: Saving notebook... | "
47 | (substitute-command-keys "Kernel requires restart \\\\[ein:notebook-restart-session-command-km]"))
48 | (ein:header-line)))))
49 |
50 | (ert-deftest ein:notification-and-events ()
51 | (let* ((notification (ein:notification))
52 | (kernel (oref notification :kernel))
53 | (notebook (oref notification :notebook))
54 | (events (ein:events-new))
55 | (event-symbols
56 | '(notebook_saved.Notebook
57 | notebook_saving.Notebook
58 | notebook_save_failed.Notebook
59 | execution_count.Kernel
60 | status_idle.Kernel
61 | status_busy.Kernel
62 | status_restarting.Kernel
63 | status_restarted.Kernel
64 | status_dead.Kernel
65 | status_reconnecting.Kernel
66 | status_reconnected.Kernel
67 | status_disconnected.Kernel
68 | ))
69 | (callbacks (oref events :callbacks)))
70 | (ein:notification-bind-events notification events)
71 | (mapc (lambda (s) (should (gethash s callbacks))) event-symbols)
72 | (should (= (hash-table-count callbacks) (length event-symbols)))
73 | (should (equal (oref kernel :status) nil))
74 | (cl-loop for et in (mapcar #'car (oref kernel :s2m))
75 | do (ein:events-trigger events et)
76 | do (should (equal (oref kernel :status) et))
77 | do (should (equal (oref notebook :status) nil)))
78 | (cl-loop for et in (mapcar #'car (oref notebook :s2m))
79 | do (ein:events-trigger events et)
80 | do (should (equal (oref kernel :status) 'status_disconnected.Kernel))
81 | do (should (equal (oref notebook :status) et)))))
82 |
--------------------------------------------------------------------------------
/test/test-ein-node.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 |
4 | (require 'ein-node)
5 |
6 | (defun ein:testing-node-dummy-ewco-node (data)
7 | `[nil nil ,data])
8 |
9 | (defun ein:testing-node-ewoc-data (ewoc-node)
10 | (ein:$node-data (ewoc-data ewoc-node)))
11 |
12 | (ert-deftest ein:testing-node-dummy-ewco-node ()
13 | (let* ((obj "some-object")
14 | (ewoc-node (ein:testing-node-dummy-ewco-node obj)))
15 | (should (eq (ewoc-data ewoc-node) obj))))
16 |
17 | (ert-deftest ein:node-filter-is ()
18 | (let ((en-list (mapcar
19 | #'ein:testing-node-dummy-ewco-node
20 | (list (ein:node-new nil "s" '(spam sag))
21 | (ein:node-new nil "p" '(spam))
22 | (ein:node-new nil "e" '(egg))
23 | (ein:node-new nil "a" '(spam sag))
24 | (ein:node-new nil "g" '(egg sag))
25 | (ein:node-new nil "m" '(spam))
26 | (ein:node-new nil "g" '(egg))))))
27 | (should (equal (mapcar #'ein:testing-node-ewoc-data
28 | (ein:node-filter en-list :is 'spam))
29 | '("s" "p" "a" "m")))
30 | (should (equal (mapcar #'ein:testing-node-ewoc-data
31 | (ein:node-filter en-list :is 'egg))
32 | '("e" "g" "g")))
33 | (should (equal (mapcar #'ein:testing-node-ewoc-data
34 | (ein:node-filter en-list :is 'sag))
35 | '("s" "a" "g")))))
36 |
37 | (ert-deftest ein:node-filter-not ()
38 | (let ((en-list (mapcar
39 | #'ein:testing-node-dummy-ewco-node
40 | (list (ein:node-new nil "s" '(spam sag))
41 | (ein:node-new nil "p" '(spam))
42 | (ein:node-new nil "e" '(egg))
43 | (ein:node-new nil "a" '(spam sag))
44 | (ein:node-new nil "g" '(egg sag))
45 | (ein:node-new nil "m" '(spam))
46 | (ein:node-new nil "g" '(egg))))))
47 | (should (equal (mapcar #'ein:testing-node-ewoc-data
48 | (ein:node-filter en-list :not 'spam))
49 | '("e" "g" "g")))
50 | (should (equal (mapcar #'ein:testing-node-ewoc-data
51 | (ein:node-filter en-list :not 'egg))
52 | '("s" "p" "a" "m")))
53 | (should (equal (mapcar #'ein:testing-node-ewoc-data
54 | (ein:node-filter en-list :not 'sag))
55 | '("p" "e" "m" "g")))))
56 |
57 | (ert-deftest ein:node-filter-is-and-not ()
58 | (let ((en-list (mapcar
59 | #'ein:testing-node-dummy-ewco-node
60 | (list (ein:node-new nil "s" '(spam sag))
61 | (ein:node-new nil "p" '(spam))
62 | (ein:node-new nil "e" '(egg))
63 | (ein:node-new nil "a" '(spam sag))
64 | (ein:node-new nil "g" '(egg sag))
65 | (ein:node-new nil "m" '(spam))
66 | (ein:node-new nil "g" '(egg))))))
67 | (should (equal (mapcar #'ein:testing-node-ewoc-data
68 | (ein:node-filter en-list :not 'spam :is 'sag))
69 | '("g")))
70 | (should (equal (mapcar #'ein:testing-node-ewoc-data
71 | (ein:node-filter en-list :is 'sag :not 'spam))
72 | '("g")))
73 | (should (equal (mapcar #'ein:testing-node-ewoc-data
74 | (ein:node-filter en-list :is 'spam :is 'sag))
75 | '("s" "a")))
76 | (should (equal (mapcar #'ein:testing-node-ewoc-data
77 | (ein:node-filter en-list :is 'sag :not 'spam
78 | :not 'not-existing))
79 | '("g")))
80 | (should (equal (mapcar #'ein:testing-node-ewoc-data
81 | (ein:node-filter en-list :is 'sag :is 'spam))
82 | '("s" "a")))))
83 |
--------------------------------------------------------------------------------
/lisp/ein-log.el:
--------------------------------------------------------------------------------
1 | ;;; ein-log.el --- Logging module for ein.el -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012- Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-log.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-log.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-log.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'ein-core)
29 |
30 | (defvar ein:log-all-buffer-name "*ein:log-all*")
31 |
32 | (defvar ein:log-level-def
33 | '(;; debugging
34 | (blather . 60) (trace . 50) (debug . 40)
35 | ;; information
36 | (verbose . 30) (info . 20)
37 | ;; errors
38 | (warn . 10) (error . 0))
39 | "Named logging levels.")
40 | ;; Some names are stolen from supervisord (http://supervisord.org/logging.html)
41 |
42 | (defvar ein:log-level 30)
43 | (defvar ein:log-message-level 20)
44 |
45 | (defvar ein:log-print-length 10 "`print-length' for `ein:log'")
46 | (defvar ein:log-print-level 1 "`print-level' for `ein:log'")
47 | (defvar ein:log-max-string 1000)
48 |
49 | (defun ein:log-set-level (level)
50 | (setq ein:log-level (ein:log-level-name-to-int level)))
51 |
52 | (defun ein:log-set-message-level (level)
53 | (setq ein:log-message-level (ein:log-level-name-to-int level)))
54 |
55 | (defun ein:log-level-int-to-name (int)
56 | (cl-loop for (n . i) in ein:log-level-def
57 | when (>= int i)
58 | return n
59 | finally 'error))
60 |
61 | (defun ein:log-level-name-to-int (name)
62 | (cdr (assq name ein:log-level-def)))
63 |
64 | (defsubst ein:log-strip-timestamp (msg)
65 | (replace-regexp-in-string "^[0-9: ]+" "" msg))
66 |
67 | (defun ein:log-wrapper (level func)
68 | (setq level (ein:log-level-name-to-int level))
69 | (when (<= level ein:log-level)
70 | (let* ((levname (ein:log-level-int-to-name level))
71 | (print-level ein:log-print-level)
72 | (print-length ein:log-print-length)
73 | (msg (format "%s: [%s] %s" (format-time-string "%H:%M:%S:%3N") levname (funcall func)))
74 | (orig-buffer (current-buffer)))
75 | (if (and ein:log-max-string
76 | (> (length msg) ein:log-max-string))
77 | (setq msg (substring msg 0 ein:log-max-string)))
78 | (ein:with-read-only-buffer (get-buffer-create ein:log-all-buffer-name)
79 | (goto-char (point-max))
80 | (insert msg (format " @%S" orig-buffer) "\n"))
81 | (when (<= level ein:log-message-level)
82 | (message "ein: %s" (ein:log-strip-timestamp msg))))))
83 |
84 | (make-obsolete-variable 'ein:debug nil "0.17.0")
85 |
86 | (defmacro ein:log (level string &rest args)
87 | (declare (indent 1))
88 | `(ein:log-wrapper ,level (lambda () (format ,string ,@args))))
89 |
90 | (defsubst ein:debug-p ()
91 | "Set to non-`nil' to raise errors instead of suppressing it.
92 | Change the behavior of `ein:log-ignore-errors'."
93 | (>= ein:log-level (alist-get 'debug ein:log-level-def)))
94 |
95 | (defun ein:log-pop-to-ws-buffer ()
96 | (interactive)
97 | (-if-let* ((kernel (ein:get-kernel--notebook))
98 | (websocket (ein:$kernel-websocket kernel)))
99 | (pop-to-buffer
100 | (websocket-get-debug-buffer-create
101 | (ein:$websocket-ws websocket)))
102 | (message "Must be run from notebook buffer")))
103 |
104 | (defun ein:log-pop-to-request-buffer ()
105 | (interactive)
106 | (aif (get-buffer request-log-buffer-name)
107 | (pop-to-buffer it)
108 | (message "No buffer %s" request-log-buffer-name)))
109 |
110 | (defun ein:log-pop-to-all-buffer ()
111 | (interactive)
112 | (pop-to-buffer (get-buffer-create ein:log-all-buffer-name)))
113 |
114 | (provide 'ein-log)
115 |
116 | ;;; ein-log.el ends here
117 |
--------------------------------------------------------------------------------
/lisp/ein-websocket.el:
--------------------------------------------------------------------------------
1 | ;;; ein-websocket.el --- Wrapper of websocket.el -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012- Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-websocket.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-websocket.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-websocket.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'websocket)
29 | (require 'ein-core)
30 | (require 'ein-classes)
31 | (require 'url-cookie)
32 | (require 'request)
33 |
34 | (defun ein:websocket-store-cookie (c host-port url-filename securep)
35 | (url-cookie-store (car c) (cdr c) nil host-port url-filename securep))
36 |
37 | (defun ein:maybe-get-jhconn-user (url)
38 | (let ((paths (cl-rest (split-string (url-filename (url-generic-parse-url url)) "/"))))
39 | (when (string= (cl-first paths) "user")
40 | (list (format "/%s/%s/" (cl-first paths) (cl-second paths))))))
41 |
42 | (defun ein:websocket--prepare-cookies (url)
43 | "Websocket gets its cookies using the url-cookie API, so we need
44 | to transcribe any cookies stored in `request-cookie-alist' during
45 | earlier calls to `request' (request.el)."
46 | (let* ((parsed-url (url-generic-parse-url url))
47 | (host-port (format "%s:%s" (url-host parsed-url) (url-port parsed-url)))
48 | (base-url (file-name-as-directory (url-filename parsed-url)))
49 | (securep (string-match "^wss://" url))
50 | (read-cookies-func (lambda (path)
51 | (request-cookie-alist
52 | (url-host parsed-url) path securep)))
53 | (cookies (cl-loop
54 | repeat 4
55 | for cand = (cl-mapcan read-cookies-func
56 | `("/"
57 | "/hub/"
58 | ,base-url
59 | ,@(ein:maybe-get-jhconn-user url)))
60 | until (cl-some (lambda (x) (string= "_xsrf" (car x))) cand)
61 | do (ein:log 'info
62 | "ein:websocket--prepare-cookies: no _xsrf among %s, retrying."
63 | cand)
64 | do (sleep-for 0 300)
65 | finally return cand)))
66 | (dolist (c cookies)
67 | (ein:websocket-store-cookie
68 | c host-port (car (url-path-and-query parsed-url)) securep))))
69 |
70 | (defun ein:websocket (url kernel on-message on-close on-open)
71 | (ein:websocket--prepare-cookies (ein:$kernel-ws-url kernel))
72 | (let* ((ws (websocket-open url
73 | :on-open on-open
74 | :on-message on-message
75 | :on-close on-close
76 | :on-error (lambda (ws action err)
77 | (ein:log 'info "WS action [%s] %s (%s)"
78 | err action (websocket-url ws)))))
79 | (websocket (make-ein:$websocket :ws ws :kernel kernel :closed-by-client nil)))
80 | (setf (websocket-client-data ws) websocket)
81 | websocket))
82 |
83 | (defun ein:websocket-open-p (websocket)
84 | (eql (websocket-ready-state (ein:$websocket-ws websocket)) 'open))
85 |
86 |
87 | (defun ein:websocket-send (websocket text)
88 | ;; (ein:log 'info "WS: Sent message %s" text)
89 | (condition-case-unless-debug err
90 | (websocket-send-text (ein:$websocket-ws websocket) text)
91 | (error (message "Error %s on sending websocket message %s." err text))))
92 |
93 |
94 | (defun ein:websocket-close (websocket)
95 | (setf (ein:$websocket-closed-by-client websocket) t)
96 | (websocket-close (ein:$websocket-ws websocket)))
97 |
98 |
99 | (defun ein:websocket-send-shell-channel (kernel msg)
100 | (cond ((= (ein:$kernel-api-version kernel) 2)
101 | (ein:websocket-send
102 | (ein:$kernel-shell-channel kernel)
103 | (ein:json-encode msg)))
104 | ((>= (ein:$kernel-api-version kernel) 3)
105 | (ein:websocket-send
106 | (ein:$kernel-websocket kernel)
107 | (ein:json-encode (plist-put msg :channel "shell"))))))
108 |
109 | (defun ein:websocket-send-stdin-channel (kernel msg)
110 | (cond ((= (ein:$kernel-api-version kernel) 2)
111 | (ein:log 'warn "Stdin messages only supported with IPython 3."))
112 | ((>= (ein:$kernel-api-version kernel) 3)
113 | (ein:websocket-send
114 | (ein:$kernel-websocket kernel)
115 | (ein:json-encode (plist-put msg :channel "stdin"))))))
116 |
117 | (provide 'ein-websocket)
118 |
119 | ;;; ein-websocket.el ends here
120 |
--------------------------------------------------------------------------------
/lisp/ein-ipdb.el:
--------------------------------------------------------------------------------
1 | ;;; ein-ipdb.el --- Support ipython debugger (ipdb) -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2015 - John Miller
4 |
5 | ;; Author: John Miller
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-ipdb.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-ipdb.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-kernel.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'cl-lib)
29 |
30 | (defvar *ein:ipdb-sessions* (make-hash-table)
31 | "Kernel Id to ein:$ipdb-session.")
32 |
33 | (declare-function ein:kernel--get-msg "ein-kernel")
34 |
35 | (cl-defstruct ein:$ipdb-session buffer kernel prompt notebook)
36 |
37 | (defun ein:ipdb-get-session (kernel)
38 | (gethash (ein:$kernel-kernel-id kernel) *ein:ipdb-sessions*))
39 |
40 | (defun ein:ipdb-start-session (kernel prompt notebook)
41 | (let* ((buffer (get-buffer-create
42 | (format "*ipdb: %s*"
43 | (ein:$kernel-kernel-id kernel))))
44 | (session (make-ein:$ipdb-session :buffer buffer
45 | :kernel kernel
46 | :prompt prompt
47 | :notebook notebook)))
48 | (puthash (ein:$kernel-kernel-id kernel) session *ein:ipdb-sessions*)
49 | (with-current-buffer buffer
50 | (kill-all-local-variables)
51 | (add-hook 'kill-buffer-hook
52 | (apply-partially #'ein:ipdb-quit-session session) nil t)
53 | (ein:ipdb-mode)
54 | (setq comint-use-prompt-regexp t)
55 | (setq comint-prompt-regexp (concat "^" (regexp-quote prompt)))
56 | (setq comint-input-sender (apply-partially #'ein:ipdb-input-sender session))
57 | (setq comint-prompt-read-only t)
58 | (set (make-local-variable 'comint-output-filter-functions)
59 | '(ansi-color-process-output))
60 | (let ((proc (start-process "ein:ipdb" buffer "cat"))
61 | (sentinel (lambda (process _event)
62 | (when (memq (process-status process) '(exit signal))
63 | (ein:ipdb-cleanup-session session)))))
64 | (set-process-query-on-exit-flag proc nil)
65 | (set-process-sentinel proc sentinel)
66 | (set-marker (process-mark proc) (point))
67 | (comint-output-filter proc (concat "\n" (ein:$ipdb-session-prompt session)))))
68 | (pop-to-buffer buffer)))
69 |
70 | (defun ein:ipdb-quit-session (session)
71 | (let* ((kernel (ein:$ipdb-session-kernel session))
72 | (msg (ein:kernel--get-msg kernel "input_reply" (list :value "exit"))))
73 | (ein:websocket-send-stdin-channel kernel msg)))
74 |
75 | (defun ein:ipdb-stop-session (session)
76 | (awhen (get-buffer-process (ein:$ipdb-session-buffer session))
77 | (when (process-live-p it)
78 | (kill-process it))))
79 |
80 | (defun ein:ipdb-cleanup-session (session)
81 | (let ((kernel (ein:$ipdb-session-kernel session))
82 | (notebook (ein:$ipdb-session-notebook session))
83 | (buffer (ein:$ipdb-session-buffer session)))
84 | (remhash (ein:$kernel-kernel-id kernel) *ein:ipdb-sessions*)
85 | (when (buffer-live-p buffer)
86 | (with-current-buffer buffer
87 | (insert "\nFinished\n")))
88 | (awhen (ein:notebook-buffer notebook)
89 | (when (buffer-live-p it)
90 | (pop-to-buffer it)))))
91 |
92 | (defun ein:ipdb--handle-iopub-reply (kernel packet)
93 | (cl-destructuring-bind
94 | (&key content &allow-other-keys)
95 | (ein:json-read-from-string packet)
96 | (-when-let* ((session (ein:ipdb-get-session kernel))
97 | (buffer (ein:$ipdb-session-buffer session))
98 | (prompt (ein:$ipdb-session-prompt session))
99 | (proc (get-buffer-process buffer))
100 | (proc-live-p (process-live-p proc)))
101 | (let ((text (plist-get content :text))
102 | (ename (plist-get content :ename)))
103 | (when (stringp text)
104 | (comint-output-filter proc text))
105 | (if (and (stringp ename) (string-match-p "bdbquit" ename))
106 | (ein:ipdb-stop-session session)
107 | (comint-output-filter proc prompt))))))
108 |
109 | (defun ein:ipdb-input-sender (session proc input)
110 | ;; in case of eof, comint-input-sender-no-newline is t
111 | (if comint-input-sender-no-newline
112 | (ein:ipdb-quit-session session)
113 | (when (process-live-p proc)
114 | (with-current-buffer (process-buffer proc)
115 | (let* ((buffer-read-only nil)
116 | (kernel (ein:$ipdb-session-kernel session))
117 | (content (list :value input))
118 | (msg (ein:kernel--get-msg kernel "input_reply" content)))
119 | (ein:websocket-send-stdin-channel kernel msg))))))
120 |
121 | (define-derived-mode ein:ipdb-mode comint-mode "ein:debugger"
122 | "Run an EIN debug session.
123 |
124 | \\")
125 |
126 | (provide 'ein-ipdb)
127 |
--------------------------------------------------------------------------------
/README.in.rst:
--------------------------------------------------------------------------------
1 | ==========================================================
2 | EIN -- Emacs IPython Notebook |build-status| |melpa-dev|
3 | ==========================================================
4 |
5 | .. image:: https://github.com/dickmao/emacs-ipython-notebook/blob/master/thumbnail.png
6 | :target: https://youtu.be/8VzWc9QeOxE
7 | :alt: Kaggle Notebooks in AWS
8 |
9 | .. COMMENTARY (see Makefile)
10 |
11 | .. |build-status|
12 | image:: https://github.com/millejoh/emacs-ipython-notebook/workflows/CI/badge.svg
13 | :target: https://github.com/millejoh/emacs-ipython-notebook/actions
14 | :alt: Build Status
15 | .. |melpa-dev|
16 | image:: https://melpa.org/packages/ein-badge.svg
17 | :target: http://melpa.org/#/ein
18 | :alt: MELPA current version
19 | .. _Jupyter: http://jupyter.org
20 | .. _Babel: https://orgmode.org/worg/org-contrib/babel/intro.html
21 | .. _Org: https://orgmode.org
22 | .. _[tkf]: http://tkf.github.io
23 | .. _[gregsexton]: https://github.com/gregsexton/ob-ipython
24 |
25 | Install
26 | =======
27 | As described in `Getting started`_, ensure melpa's whereabouts in ``init.el`` or ``.emacs``::
28 |
29 | (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
30 |
31 | Then
32 |
33 | ::
34 |
35 | M-x package-install RET ein RET
36 |
37 | Alternatively, directly clone this repo and ``make install``.
38 |
39 | For jupyterlab 3.0+, reconfigure the subcommand from "notebook" to "server".
40 |
41 | ::
42 |
43 | M-x customize-option RET ein:jupyter-server-use-subcommand RET
44 |
45 | Usage
46 | =====
47 | Start EIN using **ONE** of the following:
48 |
49 | * Open an ``.ipynb`` file, press ``C-c C-o``, or,
50 | * ``M-x ein:run`` launches a jupyter process from emacs, or,
51 | * ``M-x ein:login`` to a running jupyter server, or,
52 |
53 | ``M-x ein:stop`` prompts to halt local and remote jupyter services.
54 |
55 | Alternatively, ob-ein_.
56 |
57 | .. _Cask: https://cask.readthedocs.io/en/latest/guide/installation.html
58 | .. _Getting started: http://melpa.org/#/getting-started
59 |
60 | FAQ
61 | ===
62 |
63 | How do I...
64 | -----------
65 |
66 | ... report a bug?
67 | Note EIN is tested only for *released* GNU Emacs versions
68 | .. CI VERSION (see Makefile)
69 | and later. Pre-release versions will not work.
70 |
71 | First try ``emacs -Q -f package-initialize -f ein:dev-start-debug`` and reproduce the bug. The ``-Q`` skips any user configuration that might interfere with EIN.
72 |
73 | Then file an issue using ``M-x ein:dev-bug-report-template``.
74 |
75 | ... display images inline?
76 | We find inserting images into emacs disruptive, and so default to spawning an external viewer. To override this,
77 | ::
78 |
79 | M-x customize-group RET ein
80 | Ein:Output Area Inlined Images
81 |
82 | ... configure the external image viewer?
83 | ::
84 |
85 | M-x customize-group RET mailcap
86 | Mailcap User Mime Data
87 |
88 | On a typical Linux system, one might configure a viewer for MIME Type ``image/png`` as a shell command ``convert %s -background white -alpha remove -alpha off - | display -immutable``.
89 |
90 | ... get IDE-like behavior?
91 | You can't. EIN's architecture is fundamentally incompatible with LSP.
92 |
93 | .. _Issues: https://github.com/millejoh/emacs-ipython-notebook/issues
94 | .. _prevailing documentation: http://millejoh.github.io/emacs-ipython-notebook
95 | .. _spacemacs layer: https://github.com/syl20bnr/spacemacs/tree/master/layers/%2Blang/ipython-notebook
96 | .. _company-mode: https://github.com/company-mode/company-mode
97 | .. _jupyterhub: https://github.com/jupyterhub/jupyterhub
98 | .. _elpy: https://melpa.org/#/elpy
99 | .. _math-preview: https://gitlab.com/matsievskiysv/math-preview
100 | .. _program modes: https://www.gnu.org/software/emacs/manual/html_node/emacs/Program-Modes.html
101 | .. _undo boundaries: https://www.gnu.org/software/emacs/manual/html_node/elisp/Undo.html
102 |
103 | ob-ein
104 | ======
105 | Configuration:
106 |
107 | ::
108 |
109 | M-x customize-group RET org-babel
110 | Org Babel Load Languages:
111 | Insert (ein . t)
112 | For example, '((emacs-lisp . t) (ein . t))
113 |
114 | Snippet:
115 |
116 | ::
117 |
118 | #+BEGIN_SRC ein-python :session localhost
119 | import numpy, math, matplotlib.pyplot as plt
120 | %matplotlib inline
121 | x = numpy.linspace(0, 2*math.pi)
122 | plt.plot(x, numpy.sin(x))
123 | #+END_SRC
124 |
125 | The ``:session`` is the notebook url, e.g., ``http://localhost:8888/my.ipynb``, or simply ``localhost``, in which case org evaluates anonymously. A port may also be specified, e.g., ``localhost:8889``.
126 |
127 | *Language* can be ``ein-python``, ``ein-r``, or ``ein-julia``. **The relevant** `jupyter kernel`_ **must be installed before use**. Additional languages can be configured via::
128 |
129 | M-x customize-group RET ein
130 | Ob Ein Languages
131 |
132 | .. _polymode: https://github.com/polymode/polymode
133 | .. _ob-ipython: https://github.com/gregsexton/ob-ipython
134 | .. _scimax: https://github.com/jkitchin/scimax
135 | .. _jupyter kernel: https://github.com/jupyter/jupyter/wiki/Jupyter-kernels
136 |
137 | .. _gat utility: https://dickmaogat.readthedocs.io/en/latest/install.html
138 | .. _gat usage: https://dickmaogat.readthedocs.io/en/latest/usage.html
139 | .. _batch mode: https://nbconvert.readthedocs.io/en/latest/execute_api.html
140 | .. _dickmao/Kaggler: https://github.com/dickmao/Kaggler/tree/gcspath#importing-datasets
141 |
142 | Keymap (C-h m)
143 | ==============
144 |
145 | ::
146 |
147 | .. KEYS NOTEBOOK (see Makefile)
148 |
--------------------------------------------------------------------------------
/features/notebook.feature:
--------------------------------------------------------------------------------
1 | @paren
2 | Scenario: show-paren-mode should behave
3 | Given new python notebook
4 | And I press "C-c C-b"
5 | And I press "C-c C-p"
6 | And I type "x = np.arange(0"
7 | And I press "C-b"
8 | And I press "C-b"
9 | And eval "(let ((orig (point))) (funcall show-paren-data-function) (cl-assert (equal (point) orig)))"
10 |
11 | @rename
12 | Scenario: rename notebook
13 | Given new python notebook
14 | And I press "C-c C-/"
15 | And I switch to buffer like "Untitled"
16 | And rename notebook to "Renamed" succeeds
17 |
18 | @image
19 | Scenario: in ipython<=7.10, image failed to materialize initially
20 | Given new python notebook
21 | And I type "import numpy, math, matplotlib.pyplot as plt"
22 | And I press "RET"
23 | And I type "x = numpy.linspace(0, 2*math.pi)"
24 | And I press "RET"
25 | And I type "plt.plot(x, numpy.sin(x))"
26 | And I press "RET"
27 | And I clear log expr "ein:log-all-buffer-name"
28 | And I wait for cell to execute
29 | And I switch to log expr "ein:log-all-buffer-name"
30 | Then I should see "msg_type=display_data"
31 |
32 | @switch
33 | Scenario: switch kernel
34 | Given new python notebook
35 | And I type "import itertools"
36 | And I wait for cell to execute
37 | And I switch kernel to "ir"
38 |
39 | @pager
40 | Scenario: so-called paging in ein:kernel--handle-payload
41 | Given new python notebook
42 | And I type "help?"
43 | And I wait for cell to execute
44 | And I switch to buffer like "*ein:pager"
45 | And I should see "help()"
46 | And I press "C-x k"
47 |
48 | @traceback
49 | Scenario: native json parsing broke traceback
50 | Given new python notebook
51 | And I type "def func1():"
52 | And I press "RET"
53 | And I type " func2()"
54 | And I press "RET"
55 | And I type "def func2():"
56 | And I press "RET"
57 | And I type " 1/0"
58 | And I press "RET"
59 | And I type "func1()"
60 | And I press "RET"
61 | And I wait for cell to execute
62 | Then I should see "Traceback"
63 | And I press "C-c C-$"
64 | And I switch to buffer like "*ein:tb"
65 | Then I should see "ZeroDivisionError"
66 |
67 | @reconnect
68 | Scenario: kernel reconnect succeeds
69 | Given new python notebook
70 | When I type "import math"
71 | And I wait for cell to execute
72 | And I kill processes like "websocket"
73 | And I switch to log expr "ein:log-all-buffer-name"
74 | Then I should see "WS closed unexpectedly"
75 | And I switch to buffer like "Untitled"
76 | And header says "Kernel requires reconnect \\[ein:notebook-reconnect-session-command-km]"
77 | And I clear log expr "ein:log-all-buffer-name"
78 | And I press "C-c C-r"
79 | And I wait for the smoke to clear
80 | And header does not say "Kernel requires reconnect \\[ein:notebook-reconnect-session-command-km]"
81 | And I switch to log expr "ein:log-all-buffer-name"
82 | Then I should not see "[warn]"
83 | And I should not see ": [error]"
84 | And I should see "ein:kernel-retrieve-session--complete"
85 | And I switch to buffer like "Untitled"
86 | And I kill processes like "websocket"
87 | And I switch to log expr "ein:log-all-buffer-name"
88 | Then I should see "WS closed unexpectedly"
89 | And I switch to buffer like "Untitled"
90 | And header says "Kernel requires reconnect \\[ein:notebook-reconnect-session-command-km]"
91 | And I clear log expr "ein:log-all-buffer-name"
92 | And I wait for cell to execute
93 | And header does not say "Kernel requires reconnect \\[ein:notebook-reconnect-session-command-km]"
94 | And I switch to log expr "ein:log-all-buffer-name"
95 | Then I should not see "[warn]"
96 | And I should not see ": [error]"
97 | And I should see "ein:kernel-retrieve-session--complete"
98 | And I switch to buffer like "Untitled"
99 | And I clear log expr "ein:log-all-buffer-name"
100 | And I restart kernel
101 | And I switch to log expr "ein:log-all-buffer-name"
102 | Then I should not see "[warn]"
103 | And I should not see ": [error]"
104 | And I should see "ein:kernel-retrieve-session--complete"
105 | And I switch to buffer like "Untitled"
106 | And I kill kernel
107 | And header says "Kernel requires reconnect \\[ein:notebook-reconnect-session-command-km]"
108 | And I clear log expr "ein:log-all-buffer-name"
109 | And my reconnect is questioned
110 |
111 | @exit
112 | Scenario: Saving fails upon quit, need to consult user
113 | Given new python notebook
114 | When I type "import math"
115 | And I wait for cell to execute
116 | And I cannot save upon quit
117 |
118 | @kill
119 | Scenario: Assign variable, save, kill notebook buffer, get it back, check variable
120 | Given new python notebook
121 | When I type "import math"
122 | And I press "RET"
123 | And I type "b = math.pi"
124 | And I press "RET"
125 | And I wait for cell to execute
126 | And I clear log expr "ein:log-all-buffer-name"
127 | And I press "C-x C-s"
128 | And I switch to log expr "ein:log-all-buffer-name"
129 | And I wait for buffer to say "Notebook is saved"
130 | And I switch to buffer like "Untitled"
131 | And I kill buffer and reopen
132 | And I press "C-c C-b"
133 | And I dump buffer
134 | And I type "b"
135 | And I wait for cell to execute
136 | Then I should see "3.1415"
137 |
138 | @julia
139 | Scenario: Smoke test julia
140 | Given new julia notebook
141 | When I type "isapprox(Base.MathConstants.e ^ (pi * im), -1)"
142 | And I wait for cell to execute
143 | Then I should see "true"
144 | And I dump buffer
145 |
--------------------------------------------------------------------------------
/test/ein-testing-notebook.el:
--------------------------------------------------------------------------------
1 | ;;; ein-testing-notebook.el --- Testing utilities for notebook module -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012 Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-testing-notebook.el is free software: you can redistribute it
10 | ;; and/or modify it under the terms of the GNU General Public License
11 | ;; as published by the Free Software Foundation, either version 3 of
12 | ;; the License, or (at your option) any later version.
13 |
14 | ;; ein-testing-notebook.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-testing-notebook.el.
21 | ;; If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | ;;
26 |
27 | ;;; Code:
28 |
29 | (require 'ein-notebook)
30 | (require 'ein-testing-cell)
31 |
32 | (defvar ein:testing-notebook-dummy-name "Dummy Name.ipynb")
33 | (defvar ein:testing-notebook-dummy-url "DUMMY-URL")
34 |
35 | (defun ein:testing-notebook-from-json (json-string)
36 | (let* ((data (ein:with-json-setting (json-read-from-string json-string))) ;; intentionally not ein:json-read-from-string
37 | (path (plist-get data :path))
38 | (kernelspec (make-ein:$kernelspec :name "python" :language "python"))
39 | (content (make-ein:$content :url-or-port ein:testing-notebook-dummy-url
40 | :notebook-api-version "5"
41 | :path path)))
42 | (ignore content)
43 | (cl-letf (((symbol-function 'ein:need-notebook-api-version) (lambda (&rest _ignore) "5"))
44 | ((symbol-function 'ein:kernel-retrieve-session) #'ignore)
45 | ((symbol-function 'ein:notebook-enable-autosaves) #'ignore))
46 | (let ((notebook (ein:notebook-new ein:testing-notebook-dummy-url path kernelspec)))
47 | (setf (ein:$notebook-kernel notebook)
48 | (ein:kernel-new 8888 "" nil "/kernels" (ein:$notebook-events notebook) (ein:need-notebook-api-version (ein:$notebook-url-or-port notebook))))
49 | (setf (ein:$kernel-events (ein:$notebook-kernel notebook))
50 | (ein:events-new))
51 | ; matryoshka: new-content makes a ein:$content using CONTENT as template
52 | ; populating its raw_content field with DATA's content field
53 | (ein:notebook-open--callback
54 | notebook nil
55 | (ein:new-content (ein:$notebook-url-or-port notebook)
56 | (ein:$notebook-notebook-path notebook) data))
57 | (ein:notebook-buffer notebook)))))
58 |
59 | (defun ein:testing-notebook-make-data (name path cells)
60 | (setq cells
61 | (ein:testing-notebook--preprocess-cells-data-for-json-encode cells))
62 | `((path . ,path)
63 | (name . ,name)
64 | (type . "notebook")
65 | (format . "json")
66 | (mimetype . nil)
67 | (writeable . t)
68 | (content (metadata . ())
69 | (nbformat . 4)
70 | (nbformat_minor . 0)
71 | (cells . ,(apply #'vector cells)))))
72 |
73 | (defun ein:testing-notebook--preprocess-cells-data-for-json-encode (cells)
74 | "Preprocess CELLS data to make it work nice with `json-encode'."
75 | (mapcar (lambda (c)
76 | (cond
77 | ((equal (plist-get c :cell_type) "code")
78 | ;; turn `:outputs' into an array.
79 | (plist-put c :outputs (apply #'vector (plist-get c :outputs))))
80 | (t c)))
81 | cells))
82 |
83 | (defun ein:testing-notebook-make-new (&optional name path cells)
84 | "Make new notebook. One empty cell will be inserted
85 | automatically if CELLS is nil."
86 | (ein:testing-notebook-from-json
87 | (ein:json-encode (ein:testing-notebook-make-data
88 | (or name ein:testing-notebook-dummy-name)
89 | (or path name ein:testing-notebook-dummy-name)
90 | cells))))
91 |
92 | (defun ein:testing-notebook-make-empty (&optional name path)
93 | "Make empty notebook and return its buffer.
94 | Automatically inserted cell for new notebook is deleted."
95 | (let ((buffer (ein:testing-notebook-make-new name path)))
96 | (with-current-buffer buffer
97 | (call-interactively #'ein:worksheet-delete-cell))
98 | buffer))
99 |
100 | (defmacro ein:testing-with-one-cell (type-or-cell &rest body)
101 | "Insert new cell of TYPE-OR-CELL in a clean notebook and execute BODY.
102 | The new cell is bound to a variable `cell'."
103 | (declare (indent 1))
104 | `(with-current-buffer (ein:testing-notebook-make-empty)
105 | (let ((cell (ein:worksheet-insert-cell-below ein:%worksheet%
106 | ,type-or-cell nil t)))
107 | ,@body)))
108 |
109 | (defun ein:testing-make-notebook-with-outputs (list-outputs)
110 | "Make a new notebook with cells with output.
111 | LIST-OUTPUTS is a list of list of strings (pyout text). Number
112 | of LIST-OUTPUTS equals to the number cells to be contained in the
113 | notebook."
114 | (ein:testing-notebook-make-new
115 | ein:testing-notebook-dummy-name nil
116 | (mapcar (lambda (outputs)
117 | (ein:testing-codecell-data
118 | nil nil (mapcar #'ein:testing-codecell-pyout-data outputs)))
119 | list-outputs)))
120 |
121 | (provide 'ein-testing-notebook)
122 |
123 | ;;; ein-testing-notebook.el ends here
124 |
--------------------------------------------------------------------------------
/features/notebooklist.feature:
--------------------------------------------------------------------------------
1 | @bread
2 | Scenario: Breadcrumbs
3 | Given I am in notebooklist buffer
4 | When I click on dir "step-definitions" until "ein-steps"
5 | And I click on "Home"
6 | Then I wait for buffer to say "support"
7 |
8 | @kernel
9 | Scenario: Default kernel
10 | And I am in notebooklist buffer
11 | Then I should see "(*) Python"
12 |
13 | Scenario: New Notebook
14 | Given I am in notebooklist buffer
15 | When I clear log expr "ein:log-all-buffer-name"
16 | And I click on "New Notebook"
17 | And no notebooks pending
18 | And I switch to log expr "ein:log-all-buffer-name"
19 | Then I should see "Opened notebook Untitled"
20 |
21 | Scenario: Resync
22 | Given I am in notebooklist buffer
23 | When I clear log expr "ein:log-all-buffer-name"
24 | And I click on "Resync"
25 | And I switch to log expr "ein:log-all-buffer-name"
26 | Then I should see "kernelspecs--complete"
27 |
28 | Scenario: File open
29 | Given I am in notebooklist buffer
30 | And I go to word "notebooklist.feature"
31 | And I go to beginning of line
32 | And I click without going top on file "Open"
33 | And I switch to buffer like "notebooklist.feature"
34 | Then I should see "File open"
35 | And I press "C-x k"
36 |
37 | @stop
38 | Scenario: Stop after closing notebook
39 | Given I am in notebooklist buffer
40 | Given I clear log expr "ein:log-all-buffer-name"
41 | And I click on "New Notebook"
42 | And no notebooks pending
43 | And I switch to buffer like "Untitled"
44 | And I press "C-x k"
45 | And I am in notebooklist buffer
46 | And I clear log expr "ein:log-all-buffer-name"
47 | And I keep clicking "Resync" until "Stop"
48 | And I click on "Stop"
49 | And I switch to log expr "ein:log-all-buffer-name"
50 | Then I wait for buffer to say "kernel-delete-session--success"
51 | And I am in notebooklist buffer
52 | And I go to word "Untitled"
53 | And I go to beginning of line
54 | And I click without going top on "Open"
55 | And no notebooks pending
56 | And I switch to buffer like "Untitled"
57 |
58 | @delete
59 | Scenario: Delete closes buffers and sessions
60 | Given I am in notebooklist buffer
61 | Given I clear log expr "ein:log-all-buffer-name"
62 | And I click on "New Notebook"
63 | And no notebooks pending
64 | And I switch to buffer like "Untitled"
65 | And I am in notebooklist buffer
66 | And I clear log expr "ein:log-all-buffer-name"
67 | And I click on "Delete"
68 | And I wait for buffer to not say "Stop"
69 | Then eval "(cl-assert (not (ein:notebook-opened-notebooks)))"
70 | Then eval "(cl-assert (not (seq-some (lambda (b) (cl-search "Untitled" (buffer-name b))) (buffer-list))))"
71 | And I switch to log expr "ein:log-all-buffer-name"
72 | Then I wait for buffer to say "kernel-delete-session--success"
73 | Then I should see "notebooklist-delete-notebook--complete"
74 |
75 | @content
76 | Scenario: Read a massive directory
77 | Given I create a directory "/var/tmp/fg7Cv8" with depth 4 and width 8
78 | And I get into notebook mode "/var/tmp/fg7Cv8" "8/4/3/bar.ipynb"
79 | And I open notebook "bar.ipynb"
80 | And I open file "foo.txt"
81 | And notebooklist-list-paths does not contain "4/4/4/foo.txt"
82 | And notebooklist-list-paths contains "foo.txt"
83 |
84 | @login
85 | Scenario: No token server
86 | Given I start the server configured "c.NotebookApp.token = u''\n"
87 | And I switch to log expr "ein:log-all-buffer-name"
88 | Then I should not see "[warn]"
89 | And I should not see ": [error]"
90 |
91 | @login
92 | Scenario: With token server, get from server buffer
93 | Given I start the server configured "\n"
94 | And I login disabling crib token
95 | And I switch to log expr "ein:log-all-buffer-name"
96 | Then I should not see "[warn]"
97 | And I should not see ": [error]"
98 |
99 | @login
100 | Scenario: To the cloud with password
101 | Given I start the server configured "c.NotebookApp.password=u'sha1:712118ed6c09:bc02227d84b76b720cc320b855e1006d0b120f98'\n"
102 | And I login forcing ping with password "foo"
103 | And I switch to log expr "ein:log-all-buffer-name"
104 | Then I should not see "[warn]"
105 | And I should not see ": [error]"
106 | And new python notebook
107 | When I type "import math"
108 | And I wait for cell to execute
109 |
110 | @login
111 | Scenario: Nonempty base_url and password
112 | Given I start the server configured "c.NotebookApp.base_url=u'nonempty'\nc.NotebookApp.password=u'sha1:712118ed6c09:bc02227d84b76b720cc320b855e1006d0b120f98'\n"
113 | And I login forcing ping with password "foo"
114 | And I switch to log expr "ein:log-all-buffer-name"
115 | Then I should not see "[warn]"
116 | And I should not see ": [error]"
117 | And new python notebook
118 | When I type "import math"
119 | And I wait for cell to execute
120 |
121 | @login
122 | Scenario: Logging into nowhere
123 | Given I login erroneously to 0
124 | And I switch to log expr "ein:log-all-buffer-name"
125 | Then I should see "[error] Login to http://127.0.0.1:0 failed"
126 |
127 | @login
128 | Scenario: Logging into nowhere again
129 | Given I login erroneously to adfljdsf.org:8432
130 | And I switch to log expr "ein:log-all-buffer-name"
131 | Then I should see "[error] Login to https://adfljdsf.org:8432 failed"
132 |
133 | @login
134 | Scenario: Bad curl invocation produces sensible error message
135 | Given I start the server configured "\n"
136 | And I login with bad curl
137 | And I call "ein:log-pop-to-all-buffer"
138 | And I wait for buffer to say "error-thrown"
139 |
140 | @login
141 | Scenario: jupyter not found
142 | And I start bad jupyter path
143 |
144 | @jupyter-notebook
145 | Scenario: Someone uses jupyter-notebook
146 | Given I customize "ein:jupyter-default-server-command" to "jupyter-notebook"
147 | And I set "ein:jupyter-server-use-subcommand" to eval "nil"
148 | Given I start and login to the server configured "\n"
149 | And I switch to log expr "ein:log-all-buffer-name"
150 | Then I should not see "[warn]"
151 | And I should not see ": [error]"
152 | And I customize "ein:jupyter-default-server-command" to "jupyter"
153 | And I set "ein:jupyter-server-use-subcommand" to "notebook"
154 |
155 | @login
156 | Scenario: With token server
157 | Given I start and login to the server configured "\n"
158 | And I switch to log expr "ein:log-all-buffer-name"
159 | Then I should not see "[warn]"
160 | And I should not see ": [error]"
161 |
162 | @exit
163 | Scenario: Ask user to save unsaved notebooks
164 | Given new python notebook
165 | When I type "import math"
166 | And I press "RET"
167 | Then save on exit
168 |
--------------------------------------------------------------------------------
/lisp/ein-python-send.el:
--------------------------------------------------------------------------------
1 | ;;; ein-python-send.el --- Ad hoc sending of code fragments to kernel -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (C) 2012- The Authors
4 |
5 | ;; This file is NOT part of GNU Emacs.
6 |
7 | ;; ein-python-send.el is free software: you can redistribute it and/or modify
8 | ;; it under the terms of the GNU General Public License as published by
9 | ;; the Free Software Foundation, either version 3 of the License, or
10 | ;; (at your option) any later version.
11 |
12 | ;; ein-python-send.el is distributed in the hope that it will be useful,
13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ;; GNU General Public License for more details.
16 |
17 | ;; You should have received a copy of the GNU General Public License
18 | ;; along with ein-python-send.el. If not, see .
19 |
20 | ;;; Commentary:
21 |
22 | ;; python parsing code by authors of elpy (Schaefer et al)
23 |
24 | ;;; Code:
25 |
26 | (autoload 'ein:get-notebook "ein-notebook")
27 |
28 | (defvar ein:python-send-map)
29 |
30 | (defun ein:python-send--prepare (&optional reset)
31 | (cl-assert (boundp 'ein:python-send-map) nil
32 | "ein:python-send--prepare: %s not called"
33 | "ein:python-send--init")
34 | (unless (and (buffer-live-p (current-buffer))
35 | (eq major-mode 'python-mode))
36 | (error "ein:python-send--prepare: %s is not a python buffer" (buffer-name)))
37 | (when (or (not (ein:get-notebook)) reset)
38 | (aif (ein:notebook-opened-notebooks)
39 | (let ((choice
40 | (ein:completing-read
41 | "Notebook: "
42 | (mapcar (lambda (nb) (ein:$notebook-notebook-name nb)) it))))
43 | (setq ein:%notebook% (seq-find
44 | (lambda (nb)
45 | (string= choice (ein:$notebook-notebook-name nb)))
46 | it)))
47 | (error "ein:python-send--prepare: No open notebooks"))))
48 |
49 | (defun ein:python-send-region-or-buffer (&optional reset)
50 | "Based on `elpy-shell--send-region-or-buffer-internal' by Schaefer et al."
51 | (interactive "P")
52 | (ein:python-send--prepare reset)
53 | (if (use-region-p)
54 | (let ((region (python-shell-buffer-substring
55 | (region-beginning) (region-end))))
56 | (when (string-match "\t" region)
57 | (message "Region contained tabs, this might cause weird errors"))
58 | ;; python-shell-buffer-substring (intentionally?) does not accurately
59 | ;; respect (region-beginning); it always start on the first character
60 | ;; of the respective line even if that's before the region beginning
61 | ;; Here we post-process the output to remove the characters before
62 | ;; (region-beginning) and the start of the line. The end of the region
63 | ;; is handled correctly and needs no special treatment.
64 | (let* ((bounds (save-excursion
65 | (goto-char (region-beginning))
66 | (bounds-of-thing-at-point 'line)))
67 | (used-part (string-trim
68 | (buffer-substring-no-properties
69 | (car bounds)
70 | (min (cdr bounds) (region-end)))))
71 | (relevant-part (string-trim
72 | (buffer-substring-no-properties
73 | (max (car bounds) (region-beginning))
74 | (min (cdr bounds) (region-end))))))
75 | (setq region
76 | ;; replace just first match
77 | (replace-regexp-in-string
78 | (concat "\\(" (regexp-quote used-part) "\\)\\(?:.*\n?\\)*\\'")
79 | relevant-part
80 | region t t 1))
81 | (ein:shared-output-eval-string (ein:get-kernel) region)))
82 | (ein:shared-output-eval-string (ein:get-kernel) (buffer-string)))
83 | (if (use-region-p)
84 | (progn
85 | (goto-char (region-end))
86 | (deactivate-mark))
87 | (goto-char (point-max))))
88 |
89 | (defun ein:python-send-statement (&optional reset)
90 | "Based on `elpy-shell-send-statement' by Schaefer et al."
91 | (interactive "P")
92 | (ein:python-send--prepare reset)
93 | (python-nav-beginning-of-statement)
94 | (unless (looking-at "[[:space:]]*$")
95 | (let ((beg (save-excursion (beginning-of-line) (point)))
96 | (end (progn (ein:python-send--nav-end-of-statement) (point))))
97 | (unless (eq beg end)
98 | (ein:shared-output-eval-string (ein:get-kernel)
99 | (buffer-substring beg end))))))
100 |
101 | (defun ein:python-send--nav-end-of-statement ()
102 | "Based on `elpy-shell--nav-end-of-statement' by Schaefer et al."
103 | (let ((continue t)
104 | p)
105 | (while (and (not (eq p (point))) continue)
106 | ;; is there another block at same indentation level?
107 | (setq p (point))
108 | (ein:python-send--nav-forward-block)
109 | (if (eq p (point))
110 | (progn
111 | ;; nope, go to the end of the block and done
112 | (python-nav-end-of-block)
113 | (setq continue nil))
114 | (unless (eq 0 (string-match-p "\\s-*el\\(?:se:\\|if[^\w]\\)"
115 | (thing-at-point 'line)))
116 | (forward-line -1)
117 | (while (and (or (eq (string-match-p "\\s-*$" (thing-at-point 'line)) 0)
118 | (python-info-current-line-comment-p))
119 | (not (eq (point) (point-min))))
120 | (forward-line -1))
121 | (setq continue nil)))))
122 | (end-of-line))
123 |
124 | (defun ein:python-send--nav-forward-block ()
125 | "Based on `elpy-shell--nav-forward-block' by Schaefer et al.
126 |
127 | Move to the next line indented like point. This will skip over lines and
128 | statements with different indentation levels."
129 | (interactive "^")
130 | (let ((indent (current-column))
131 | (start (point))
132 | (cur nil))
133 | (when (/= (% indent python-indent-offset)
134 | 0)
135 | (setq indent (* (1+ (/ indent python-indent-offset))
136 | python-indent-offset)))
137 | (python-nav-forward-statement)
138 | (while (and (< indent (current-indentation))
139 | (not (eobp)))
140 | (when (equal (point) cur)
141 | (error "Statement does not finish"))
142 | (setq cur (point))
143 | (python-nav-forward-statement))
144 | (when (< (current-indentation)
145 | indent)
146 | (goto-char start))))
147 |
148 | (defun ein:python-send--init ()
149 | (unless (boundp 'ein:python-send-map)
150 | (require 'python)
151 | (setq ein:python-send-map
152 | (let ((map (make-sparse-keymap)))
153 | (define-key map (kbd "e") 'ein:python-send-statement)
154 | (define-key map (kbd "r") 'ein:python-send-region-or-buffer)
155 | map))
156 | (define-key python-mode-map (kbd "C-c C-/") ein:python-send-map)))
157 |
158 | (provide 'ein-python-send)
159 |
160 | ;;; ein-python-send.el ends here
161 |
--------------------------------------------------------------------------------
/features/ob-ein.feature:
--------------------------------------------------------------------------------
1 | @memory
2 | Scenario: R and Julia in the same org file
3 | Given I stop the server
4 | When I open temp file "ecukes.org"
5 | And I call "org-mode"
6 | And I type ""
15 | And I type ""
39 | And I type ""
81 | And I type ""
101 | And I type ""
170 | And I type ""
195 | And I press "RET"
196 | And I press "RET"
197 | And I type "= emacs-major-version 27)
26 | (require 'org-tempo))
27 |
28 | (unless with-editor-emacsclient-executable
29 | (!cons "gat" ecukes-exclude-tags))
30 |
31 | (!cons "timestamp" ecukes-exclude-tags)
32 |
33 | (unless (member "jupyterhub" ecukes-include-tags)
34 | (!cons "jupyterhub" ecukes-exclude-tags))
35 |
36 | (when (getenv "GITHUB_ACTIONS")
37 | (cl-assert (not (eq system-type 'darwin)))
38 | (!cons "memory" ecukes-exclude-tags)
39 | (!cons "julia" ecukes-exclude-tags)
40 | (!cons "content" ecukes-exclude-tags)
41 | (!cons "svg" ecukes-exclude-tags)
42 | (!cons "gat" ecukes-exclude-tags)
43 | (!cons "pass" ecukes-exclude-tags) ;; salt? stopped working around 20210316
44 | (!cons "switch" ecukes-exclude-tags))
45 |
46 | (defalias 'activate-cursor-for-undo #'ignore)
47 | (defalias 'deactivate-cursor-after-undo #'ignore)
48 |
49 | (defvar ein:testing-jupyter-server-root (f-parent (f-dirname load-file-name)))
50 |
51 | (defconst ein:testing-project-path (ecukes-project-path))
52 |
53 | (defun ein:testing-after-scenario ()
54 | (let ((default-directory ein:testing-project-path))
55 | (with-current-buffer (ein:notebooklist-get-buffer (ein:jupyter-my-url-or-port))
56 | (cl-loop for notebook in (ein:notebook-opened-notebooks)
57 | for url-or-port = (ein:$notebook-url-or-port notebook)
58 | for path = (ein:$notebook-notebook-path notebook)
59 | for done-p = nil
60 | do (ein:notebook-kill-kernel-then-close-command
61 | notebook (lambda (_kernel) (setq done-p t)))
62 | do (cl-loop repeat 16
63 | until done-p
64 | do (sleep-for 0 1000)
65 | finally do (unless done-p
66 | (ein:display-warning (format "cannot close %s" path))))
67 | do (when (or (ob-ein-anonymous-p path)
68 | (cl-search "Untitled" path)
69 | (cl-search "Renamed" path))
70 | (ein:notebooklist-delete-notebook ein:%notebooklist% url-or-port path)
71 | (cl-loop with fullpath = (concat (file-name-as-directory ein:testing-jupyter-server-root) path)
72 | repeat 10
73 | for extant = (file-exists-p fullpath)
74 | until (not extant)
75 | do (sleep-for 0 1000)
76 | finally do (when extant
77 | (ein:display-warning (format "cannot delete %s" path))))))))
78 | (awhen (ein:notebook-opened-notebooks)
79 | (cl-loop for nb in it
80 | for path = (ein:$notebook-notebook-path nb)
81 | do (ein:log 'debug "Notebook %s still open" path)
82 | finally do (cl-assert nil)))
83 | (cl-loop repeat 5
84 | for stragglers = (file-name-all-completions "Untitled"
85 | ein:testing-jupyter-server-root)
86 | until (null stragglers)
87 | ;; do (message "ein:testing-after-scenario: fs stale handles: %s"
88 | ;; (mapconcat #'identity stragglers ", "))
89 | do (sleep-for 0 1000))
90 | (mapc #'delete-file
91 | (mapcar (apply-partially #'concat
92 | (file-name-as-directory ein:testing-jupyter-server-root))
93 | (file-name-all-completions "Untitled" ein:testing-jupyter-server-root))))
94 |
95 | (defmacro ein--remove-ecukes-io-advices (function class name)
96 | "The princ advice is known to peg CPU when cl-prin1 of nested objects."
97 | `(when (ad-find-advice ',function ',class ',name)
98 | (ad-remove-advice ',function ',class ',name)
99 | (ad-activate ',function)))
100 |
101 | (Setup
102 | (ein--remove-ecukes-io-advices princ around princ-around)
103 | (ein--remove-ecukes-io-advices print around print-around)
104 | (ein:dev-start-debug)
105 | (setenv "GAT_APPLICATION_CREDENTIALS" "nonempty")
106 | (custom-set-variables '(python-indent-guess-indent-offset-verbose nil)
107 | '(ein:jupyter-use-containers nil)
108 | '(ein:gat-gce-zone "abc")
109 | '(ein:gat-gce-region "abc")
110 | '(ein:gat-aws-region "abc")
111 | '(ein:gat-gce-project "abc")
112 | '(electric-indent-mode nil)
113 | '(ein:gat-machine-types '("abc"))
114 | `(request-storage-directory ,(expand-file-name "test" ein:testing-project-path)))
115 | (setq ein:jupyter-default-kernel
116 | (cl-loop with cand = ""
117 | for (k . spec) in
118 | (alist-get
119 | 'kernelspecs
120 | (let ((json-object-type 'alist))
121 | (json-read-from-string ;; intentionally not ein:json-read-from-string
122 | (shell-command-to-string
123 | (format "%s kernelspec list --json"
124 | ein:jupyter-server-command)))))
125 | if (let ((lang (alist-get 'language (alist-get 'spec spec))))
126 | (and (string= "python" lang)
127 | (string> (symbol-name k) cand)))
128 | do (setq cand (symbol-name k))
129 | end
130 | finally return (intern cand)))
131 | (setq ein:testing-dump-file-log (concat default-directory "log/ecukes.log"))
132 | (setq ein:testing-dump-file-messages (concat default-directory "log/ecukes.messages"))
133 | (setq ein:testing-dump-file-server (concat default-directory "log/ecukes.server"))
134 | (setq ein:testing-dump-file-websocket (concat default-directory "log/ecukes.websocket"))
135 | (setq ein:testing-dump-file-request (concat default-directory "log/ecukes.request"))
136 | (setq org-confirm-babel-evaluate nil)
137 | (setq transient-mark-mode t)
138 | (Given "I start and login to the server configured \"\\n\""))
139 |
140 | (Before
141 | (setq default-directory ein:testing-project-path))
142 |
143 | (After
144 | (ein:testing-after-scenario))
145 |
146 | (Teardown
147 | (Given "I finally stop the server"))
148 |
149 | (Fail
150 | (if noninteractive
151 | (ein:testing-after-scenario)
152 | (keyboard-quit))) ;; useful to prevent emacs from quitting
153 |
--------------------------------------------------------------------------------
/test/ein-testing.el:
--------------------------------------------------------------------------------
1 | ;;; ein-testing.el --- Tools for testing -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012 Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-testing.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-testing.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-testing.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'ein-log)
29 | (require 'ein-jupyter)
30 | (require 'request)
31 | (require 'anaphora)
32 | (require 'f)
33 |
34 | (defmacro ein:setq-if-not (sym val)
35 | `(unless ,sym (setq ,sym ,val)))
36 |
37 | (defvar ein:testing-dump-file-log nil
38 | "File to save buffer specified by `ein:log-all-buffer-name'.")
39 |
40 | (defvar ein:testing-dump-file-messages nil
41 | "File to save the ``*Messages*`` buffer.")
42 |
43 | (defvar ein:testing-dump-file-server nil
44 | "File to save `*ein:jupyter-server-buffer-name*`.")
45 |
46 | (defvar ein:testing-dump-file-request nil
47 | "File to save `request-log-buffer-name`.")
48 |
49 | (defun ein:testing-save-buffer (buffer-or-name file-name)
50 | (when (and buffer-or-name (get-buffer buffer-or-name) file-name)
51 | (let ((dir (file-name-directory file-name)))
52 | (make-directory dir t)
53 | (with-current-buffer buffer-or-name
54 | (let ((coding-system-for-write 'raw-text))
55 | (write-region (point-min) (point-max) file-name))))))
56 |
57 | (defun ein:testing-dump-logs ()
58 | (ein:testing-save-buffer "*Messages*" ein:testing-dump-file-messages)
59 | (ein:testing-save-buffer *ein:jupyter-server-buffer-name* ein:testing-dump-file-server)
60 | (mapc (lambda (b)
61 | (ein:and-let* ((bname (buffer-name b))
62 | (prefix "kernels/")
63 | (is-websocket (cl-search "*websocket" bname))
64 | (kernel-start (cl-search prefix bname))
65 | (sofar (cl-subseq bname (+ kernel-start (length prefix))))
66 | (kernel-end (cl-search "/" sofar)))
67 | (ein:testing-save-buffer
68 | bname
69 | (concat ein:testing-dump-file-websocket "."
70 | (seq-take sofar kernel-end)))))
71 | (buffer-list))
72 | (ein:testing-save-buffer ein:log-all-buffer-name ein:testing-dump-file-log)
73 | (ein:testing-save-buffer request-log-buffer-name ein:testing-dump-file-request))
74 |
75 | (defun ein:testing-flush-queries (&optional ms interval)
76 | "I need the smoke to clear, but just waiting for zero running processes doesn't work
77 | if I call this between links in a deferred chain. Adding a flush-queue."
78 | (deferred:flush-queue!)
79 | (ein:testing-wait-until (lambda ()
80 | (not (seq-some (lambda (proc)
81 | (cl-search "request curl"
82 | (process-name proc)))
83 | (process-list))))
84 | nil ms interval t))
85 |
86 | (defun ein:testing-make-directory-level (parent current-depth width depth)
87 | (let ((write-region-inhibit-fsync nil))
88 | (f-touch (concat (file-name-as-directory parent) "foo.txt"))
89 | (f-touch (concat (file-name-as-directory parent) "bar.ipynb"))
90 | (f-write-text "{
91 | \"cells\": [],
92 | \"metadata\": {},
93 | \"nbformat\": 4,
94 | \"nbformat_minor\": 2
95 | }
96 | " 'utf-8 (concat (file-name-as-directory parent) "bar.ipynb")))
97 | (if (< current-depth depth)
98 | (cl-loop for w from 1 to width
99 | for dir = (concat (file-name-as-directory parent) (number-to-string w))
100 | do (f-mkdir dir)
101 | (ein:testing-make-directory-level dir (1+ current-depth) width depth))))
102 |
103 | (defun ein:testing-wait-until (predicate &optional predargs ms interval continue)
104 | "Wait until PREDICATE function returns non-`nil'.
105 | PREDARGS is argument list for the PREDICATE function.
106 | MS is milliseconds to wait. INTERVAL is polling interval in milliseconds."
107 | (let* ((int (aif interval it (aif ms (max 300 (/ ms 10)) 300)))
108 | (count (max 1 (if ms (truncate (/ ms int)) 25))))
109 | (unless (or (cl-loop repeat count
110 | when (apply predicate predargs)
111 | return t
112 | do (accept-process-output nil (/ (float int) 1000))
113 | finally return nil)
114 | continue)
115 | (cl-assert nil nil "Timeout %s" predicate))))
116 |
117 | (defun ein:testing-new-notebook (url-or-port ks &optional retry subdir)
118 | (condition-case err
119 | (let (notebook)
120 | (ein:testing-wait-until (lambda ()
121 | (ein:notebooklist-list-get url-or-port))
122 | nil 10000 1000)
123 | (ein:notebooklist-new-notebook url-or-port ks
124 | (lambda (nb _created)
125 | (setq notebook nb))
126 | nil nil subdir)
127 | (ein:testing-wait-until (lambda ()
128 | (aand notebook
129 | (ein:$notebook-kernel it)
130 | (ein:kernel-live-p it)))
131 | nil 20000 1000)
132 | notebook)
133 | (error (let ((notice (format "ein:testing-new-notebook: [%s] %s"
134 | url-or-port (error-message-string err))))
135 | (if retry
136 | (progn (ein:log 'error notice) nil)
137 | (ein:log 'info notice)
138 | (sleep-for 0 1500)
139 | (ein:testing-new-notebook url-or-port ks t subdir))))))
140 |
141 | (add-hook 'kill-emacs-hook #'ein:testing-dump-logs)
142 |
143 | (with-eval-after-load "ein-notebook"
144 | ;; if y-or-n-p isn't specially overridden, make it always "no"
145 | (let ((original-y-or-n-p (symbol-function 'y-or-n-p)))
146 | (add-function :around (symbol-function 'ein:notebook-ask-save)
147 | (lambda (f &rest args)
148 | (if (not (eq (symbol-function 'y-or-n-p) original-y-or-n-p))
149 | (apply f args)
150 | (cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest _args) nil)))
151 | (apply f args)))))))
152 |
153 | (provide 'ein-testing)
154 |
155 | ;;; ein-testing.el ends here
156 |
--------------------------------------------------------------------------------
/lisp/ein-core.el:
--------------------------------------------------------------------------------
1 | ;;; ein-core.el --- EIN core -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012 Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-core.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-core.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-core.el.
21 | ;; If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | ;;
26 |
27 | ;;; Code:
28 |
29 | (require 'ein) ; get autoloaded functions into namespace
30 | (require 'ein-utils)
31 | (require 'anaphora)
32 | (require 'request)
33 |
34 | (defgroup ein nil
35 | "IPython notebook client in Emacs"
36 | :group 'applications
37 | :prefix "ein:")
38 |
39 | (define-obsolete-variable-alias 'ein:url-or-port 'ein:urls "0.17.0")
40 | (defcustom ein:urls nil
41 | "List of default urls."
42 | :type '(repeat (choice (string :tag "Remote url")
43 | (integer :tag "Local port" 8888)))
44 | :group 'ein)
45 |
46 | (make-obsolete-variable 'ein:default-url-or-port nil "0.17.0")
47 |
48 | (defconst ein:source-dir (file-name-directory load-file-name)
49 | "Directory in which `ein*.el` files are located.")
50 |
51 | (defun ein:version (&optional interactively copy-to-kill)
52 | "Return a longer version string.
53 | With prefix argument, copy the string to kill ring.
54 | The result contains `ein:version' and either git revision (if
55 | the source is in git repository) or elpa version."
56 | (interactive (list t current-prefix-arg))
57 | (let* ((version
58 | (or (and (ein:git-root-p
59 | (concat (file-name-as-directory ein:source-dir) ".."))
60 | (let ((default-directory ein:source-dir))
61 | (ein:git-revision-dirty)))
62 | (and (string-match "/ein-\\([0-9\\.]*\\)/$" ein:source-dir)
63 | (match-string 1 ein:source-dir)))))
64 | (when interactively
65 | (message "EIN version is %s" version))
66 | (when copy-to-kill
67 | (kill-new version))
68 | version))
69 |
70 | ;;; Server attribute getters. These should be moved to ein-open.el
71 |
72 | (defvar *ein:notebook-api-version* (make-hash-table :test #'equal)
73 | "url-or-port to major notebook version")
74 |
75 | (defvar *ein:kernelspecs* (make-hash-table :test #'equal)
76 | "url-or-port to kernelspecs")
77 |
78 | (defun ein:get-kernelspec (url-or-port name &optional lang)
79 | (let* ((kernelspecs (ein:need-kernelspecs url-or-port))
80 | (name (if (stringp name)
81 | (intern (format ":%s" name))
82 | name))
83 | (ks (or (plist-get kernelspecs name)
84 | (cl-loop for (_key spec) on (ein:plist-exclude kernelspecs '(:default)) by 'cddr
85 | if (string= (ein:$kernelspec-language spec) lang)
86 | return spec
87 | end))))
88 | (cond ((stringp ks)
89 | (ein:get-kernelspec url-or-port ks))
90 | (t ks))))
91 |
92 | (defun ein:need-kernelspecs (url-or-port)
93 | "Callers assume ein:query-kernelspecs succeeded. If not, nil."
94 | (aif (gethash url-or-port *ein:kernelspecs*) it
95 | (ein:log 'warn "No recorded kernelspecs for %s" url-or-port)
96 | nil))
97 |
98 | (defsubst ein:notebook-api-version-numeric (url-or-port)
99 | (truncate (string-to-number (ein:need-notebook-api-version url-or-port))))
100 |
101 | (defun ein:need-notebook-api-version (url-or-port)
102 | "Callers assume `ein:query-notebook-api-version' succeeded.
103 | If not, we hardcode a guess."
104 | (aif (gethash url-or-port *ein:notebook-api-version*) it
105 | (ein:log 'warn "No recorded notebook version for %s" url-or-port)
106 | "5"))
107 |
108 | (defun ein:generic-getter (func-list)
109 | "Internal function for generic getter functions (`ein:get-*').
110 |
111 | FUNC-LIST is a list of function which takes no argument and
112 | return what is desired or nil. Each function in FUNC-LIST is
113 | called one by one and the first non-nil result will be used. The
114 | function is not called when it is not bound. So, it is safe to
115 | give functions defined in lazy-loaded sub-modules.
116 |
117 | This is something similar to dispatching in generic function such
118 | as `defgeneric' in EIEIO, but it takes no argument. Actual
119 | implementation is chosen based on context (buffer, point, etc.).
120 | This helps writing generic commands which requires same object
121 | but can operate in different contexts."
122 | (cl-loop for func in func-list
123 | if (and (functionp func) (funcall func))
124 | return it))
125 |
126 | (defun ein:get-url-or-port ()
127 | (ein:generic-getter '(ein:get-url-or-port--notebooklist
128 | ein:get-url-or-port--notebook
129 | ein:get-url-or-port--worksheet
130 | ein:get-url-or-port--shared-output)))
131 |
132 | (defun ein:get-kernel ()
133 | (ein:generic-getter '(ein:get-kernel--notebook
134 | ein:get-kernel--worksheet
135 | ein:get-kernel--shared-output
136 | ein:get-kernel--connect)))
137 |
138 | (defun ein:get-kernel-or-error ()
139 | (or (ein:get-kernel)
140 | (error "No kernel related to the current buffer.")))
141 |
142 | (defun ein:get-cell-at-point ()
143 | (ein:generic-getter '(ein:get-cell-at-point--worksheet
144 | ein:get-cell-at-point--shared-output)))
145 |
146 | (defun ein:get-traceback-data ()
147 | (append (ein:generic-getter '(ein:get-traceback-data--worksheet
148 | ein:get-traceback-data--shared-output
149 | ein:get-traceback-data--connect))
150 | nil))
151 |
152 | ;;; Emacs utilities
153 |
154 | (defun ein:clean-compiled-files ()
155 | (let* ((files (directory-files ein:source-dir 'full "^ein-.*\\.elc$")))
156 | (mapc #'delete-file files)
157 | (message "Removed %s byte-compiled files." (length files))))
158 |
159 | (defun ein:byte-compile-ein ()
160 | "Byte compile EIN files."
161 | (interactive)
162 | (ein:clean-compiled-files)
163 | (let* ((files (directory-files ein:source-dir 'full "^ein-.*\\.el$"))
164 | (errors (cl-mapcan (lambda (f) (unless (byte-compile-file f) (list f)))
165 | files)))
166 | (aif errors
167 | (error "Got %s errors while compiling these files: %s"
168 | (length errors)
169 | (ein:join-str " " (mapcar #'file-name-nondirectory it))))
170 | (message "Compiled %s files" (length files))))
171 |
172 | (provide 'ein-core)
173 |
174 | ;;; ein-core.el ends here
175 |
--------------------------------------------------------------------------------
/lisp/ein-output-area.el:
--------------------------------------------------------------------------------
1 | ;;; ein-output-area.el --- Output area module
2 |
3 | ;; Copyright (C) 2012 Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-output-area.el is free software: you can redistribute it and/or
10 | ;; modify it under the terms of the GNU General Public License as
11 | ;; published by the Free Software Foundation, either version 3 of the
12 | ;; License, or (at your option) any later version.
13 |
14 | ;; ein-output-area.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-output-area.el.
21 | ;; If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | ;;
26 |
27 | ;;; Code:
28 |
29 | (require 'xml)
30 | (require 'shr)
31 | (require 'ein-core)
32 |
33 |
34 | (defvar ein:output-area-case-types '(:image/svg+xml :image/png :image/jpeg :text/plain :text/html :application/latex :application/tex :application/javascript)
35 | "Prefer :text/plain.
36 | Unless it's a single line \"\" or
37 | \"TemporalData[TimeSeries, <<1>>]\" in which case prefer :text/html.")
38 |
39 | (defcustom ein:output-area-inlined-images nil
40 | "Turn on to insert images into buffer. Default spawns external viewer."
41 | :type 'boolean
42 | :group 'ein)
43 |
44 | (defcustom ein:output-area-inlined-image-properties '(:foreground "black" :background "white")
45 | "Additional properties for inlined images.
46 | This is passed to `create-image' for some supported image types,
47 | such as SVG ones whose foregrounds are taken from the current
48 | frame by default and may appear unreadable."
49 | :type '(plist :value-type color)
50 | :group 'ein)
51 |
52 | (defcustom ein:shr-env
53 | '((shr-table-horizontal-line ?-)
54 | (shr-table-vertical-line ?|)
55 | (shr-table-corner ?+))
56 | "Variables let-bound while calling `shr-insert-document'.
57 |
58 | To use default shr setting:
59 |
60 | (setq ein:shr-env nil)
61 |
62 | Draw boundaries for table (default):
63 |
64 | (setq ein:shr-env
65 | \\='((shr-table-horizontal-line ?-)
66 | (shr-table-vertical-line ?|)
67 | (shr-table-corner ?+)))
68 | "
69 | :type '(sexp)
70 | :group 'ein)
71 |
72 | ;;; XML/HTML utils
73 |
74 | (defun ein:xml-parse-html-string (html-string)
75 | "Parse HTML-STRING and return a dom object which
76 | can be handled by the xml module."
77 | (with-temp-buffer
78 | (insert html-string)
79 | (when (fboundp 'libxml-parse-html-region)
80 | (cl-loop with result
81 | repeat 3
82 | do (setq result
83 | (libxml-parse-html-region (point-min) (point-max)))
84 | until result
85 | finally return result))))
86 |
87 | (defalias 'ein:xml-node-p 'listp)
88 |
89 | (defun ein:xml-tree-apply (dom operation)
90 | "Apply OPERATION on nodes in DOM. Apply the same OPERATION on
91 | the next level children when it returns `nil'."
92 | (cl-loop for child in (xml-node-children dom)
93 | if (and (not (funcall operation child))
94 | (ein:xml-node-p child))
95 | do (ein:xml-tree-apply child operation)))
96 |
97 | (defun ein:xml-replace-attributes (dom tag attr replace-p replacer)
98 | "Replace value of ATTR of TAG in DOM using REPLACER
99 | when REPLACE-P returns non-`nil'."
100 | (ein:xml-tree-apply
101 | dom
102 | (lambda (node)
103 | (ein:and-let* (((ein:xml-node-p node))
104 | ((eq (xml-node-name node) tag))
105 | (attr-cell (assoc attr (xml-node-attributes node)))
106 | (val (cdr attr-cell))
107 | ((funcall replace-p val)))
108 | (setcdr attr-cell (funcall replacer val))
109 | t))))
110 |
111 |
112 | (defun ein:output-area-get-html-renderer ()
113 | (if (fboundp 'libxml-parse-xml-region)
114 | #'ein:insert-html-shr
115 | #'ein:insert-read-only))
116 |
117 | (defun ein:shr-insert-document (dom)
118 | "`shr-insert-document' with EIN setting."
119 | (eval `(let ,ein:shr-env (shr-insert-document dom))))
120 |
121 | (defun ein:insert-html-shr (html-string)
122 | "Render HTML-STRING using `shr-insert-document'.
123 |
124 | Usage::
125 |
126 | (ein:insert-html-shr \"HTML string\")
127 |
128 | "
129 | (let ((dom (ein:xml-parse-html-string html-string))
130 | (start (point))
131 | end
132 | (buffer-undo-list t))
133 | (ein:insert-html--fix-urls dom)
134 | (ein:shr-insert-document dom)
135 | (setq end (point))
136 | (put-text-property start end 'read-only t)
137 | (put-text-property start end 'front-sticky t)))
138 |
139 | (defun ein:insert-html--fix-urls (dom &optional url-or-port)
140 | "Destructively prepend notebook server URL to local URLs in DOM."
141 | (ein:and-let* ((url-or-port (or url-or-port (ein:get-url-or-port)))
142 | (replace-p (lambda (val) (string-match-p "^/?files/" val)))
143 | (replacer (lambda (val) (ein:url url-or-port val))))
144 | (ein:xml-replace-attributes dom 'a 'href replace-p replacer)
145 | (ein:xml-replace-attributes dom 'img 'src replace-p replacer)))
146 |
147 | (defun ein:output-area-type (mime-type)
148 | "Investigate why :image/svg+xml to :svg and :text/plain to :text"
149 | (let* ((mime-str (if (symbolp mime-type) (symbol-name mime-type) mime-type))
150 | (minor-kw (car (nreverse (split-string mime-str "/"))))
151 | (minor (car (nreverse (split-string minor-kw ":")))))
152 | (intern (concat ":"
153 | (cond ((string= minor "plain") "text")
154 | (t (cl-subseq minor 0 (cl-search "+" minor))))))))
155 |
156 | (defun ein:output-area-convert-mime-types (json data)
157 | (let ((known-mimes (cl-remove-if-not
158 | #'identity
159 | (mapcar (lambda (x) (intern-soft (concat ":" x)))
160 | (mailcap-mime-types)))))
161 | (mapc (lambda (x)
162 | (-when-let* ((mime-val (plist-get data x))
163 | (minor-kw (ein:output-area-type x)))
164 | (setq json (plist-put json minor-kw mime-val))))
165 | known-mimes)
166 | json))
167 |
168 | (defmacro ein:output-area-case-type (json &rest case-body)
169 | `(let* ((types (cl-copy-list ein:output-area-case-types))
170 | (heuristic-p (and (memq :text/plain types)
171 | (memq :text/html types)))
172 | (,json (or (plist-get ,json :data) ,json))
173 | (plain (plist-get ,json :text/plain))
174 | (html (plist-get ,json :text/html)))
175 | (when (and heuristic-p
176 | (stringp plain) (< (length plain) 60)
177 | (stringp html) (> (length html) 300))
178 | (delq :text/plain types))
179 | (seq-some (lambda (type)
180 | (when-let ((value (plist-get ,json type)))
181 | ,@case-body
182 | t))
183 | types)))
184 |
185 | (provide 'ein-output-area)
186 |
187 | ;;; ein-output-area.el ends here
188 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | export EMACS ?= $(shell which emacs)
2 | export CASK := $(shell which cask)
3 | TEST_VERSION := $(shell python -c "from yq import cli; import sys; sys.exit(cli())" .jobs.build.strategy.matrix.emacs_version .github/workflows/test.yml | jq .[] | egrep "[0-9]" | sort -n | head -1)
4 | ifeq ($(CASK),)
5 | $(error Please install CASK at https://cask.readthedocs.io/en/latest/guide/installation.html)
6 | endif
7 | CASK_DIR := $(shell EMACS=$(EMACS) $(CASK) package-directory || exit 1)
8 | SRC=$(shell $(CASK) files)
9 | PKBUILD=2.3
10 | ELCFILES = $(SRC:.el=.elc)
11 | ifeq ($(TRAVIS_PULL_REQUEST_SLUG),)
12 | TRAVIS_PULL_REQUEST_SLUG := $(shell git config --global user.name)/$(shell basename `git rev-parse --show-toplevel`)
13 | endif
14 | ifeq ($(TRAVIS_PULL_REQUEST_BRANCH),)
15 | TRAVIS_PULL_REQUEST_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
16 | endif
17 | ifeq ($(TRAVIS),true)
18 | ifeq ($(TRAVIS_PULL_REQUEST_SHA),)
19 | TRAVIS_PULL_REQUEST_SHA := $(shell if git show-ref --quiet --verify origin/$(TRAVIS_PULL_REQUEST_BRANCH) ; then git rev-parse origin/$(TRAVIS_PULL_REQUEST_BRANCH) ; fi))
20 | endif
21 | endif
22 |
23 | .DEFAULT_GOAL := test-compile
24 |
25 | README.rst: cask README.in.rst lisp/ein.el lisp/ein-notebook.el
26 | $(CASK) eval "(progn \
27 | (add-to-list 'load-path \"./lisp\") \
28 | (load \"ein-notebook\") \
29 | (describe-minor-mode \"ein:notebook-mode\") \
30 | (with-current-buffer \"*Help*\" (princ (buffer-string))))" 2>/dev/null \
31 | | tools/readme-sed.sh "KEYS NOTEBOOK" README.in.rst "key.*binding" > README.rst0
32 | perl -ne "s/^((\s*)C-c C-c(\s+).*)$$/\$${1}\n\$${2}C-u C-c C-c \$${3}ein:worksheet-execute-all-cells/; print" README.rst0 > README.rst1
33 | perl -ne "s/^(\s*)\S+.*CI VERSION.*$$/\$${1}$(TEST_VERSION)/; print" README.rst1 > README.rst0
34 | grep ';;' lisp/ein.el \
35 | | gawk '/;;;\s*Commentary/{within=1;next}/;;;\s*/{within=0}within' \
36 | | sed -e 's/^\s*;;*\s*//g' \
37 | | tools/readme-sed.sh "COMMENTARY" README.rst0 > README.rst
38 | rm README.rst0 README.rst1
39 |
40 | .PHONY: autoloads
41 | autoloads:
42 | $(CASK) emacs -Q --batch -l package --eval "(let ((v (format \"%s.%s\" emacs-major-version emacs-minor-version))) (custom-set-variables (backquote (package-user-dir ,(concat \".cask/\" v)))))" -f package-initialize --eval "(package-generate-autoloads \"ein\" \"./lisp\")"
43 |
44 | .PHONY: clean
45 | clean:
46 | $(CASK) clean-elc
47 | rm -rf test/test-install
48 | rm -f test/curl-cookie-jar
49 | rm -rf log
50 | rm -f features/[Uu]ntitled*
51 | rm -f features/Renamed.ipynb
52 | rm -f ert-profile*
53 | rm -rf features/test-repo
54 | rm -f gmon.out
55 |
56 | .PHONY: cask
57 | cask: $(CASK_DIR)
58 |
59 | $(CASK_DIR): Cask
60 | $(CASK) install
61 | touch $(CASK_DIR)
62 |
63 | .PHONY: test-compile
64 | test-compile: clean
65 | ! ($(CASK) eval "(let ((byte-compile-error-on-warn t)) (cask-cli/build))" 2>&1 | egrep -a "(Warning|Error):") ; (ret=$$? ; $(CASK) clean-elc && exit $$ret)
66 |
67 | .PHONY: quick
68 | quick: $(CASK_DIR) autoloads test-compile test-ob-ein-recurse test-unit
69 |
70 | # Last I tried, I can get jupyterhub to launch a user server
71 | # on a random port 58153, but that instance is still-born, i.e.,
72 | # it posts 404 from the browser. SimpleLocalProcessSpawner
73 | # results in Module not found.
74 | .PHONY: test-jupyterhub
75 | test-jupyterhub: test-compile
76 | $(CASK) exec ecukes --tags @jupyterhub --reporter magnars
77 |
78 | .PHONY: test
79 | test: quick test-int
80 |
81 | .PHONY: test-int
82 | test-int: clean
83 | $(CASK) exec ecukes --reporter magnars --debug
84 |
85 | .PHONY: test-unit
86 | test-unit:
87 | $(CASK) exec ert-runner -L ./lisp -L ./test -l test/testein.el test/test-ein*.el
88 |
89 | .PHONY: test-ob-ein-recurse
90 | test-ob-ein-recurse:
91 | $(CASK) eval "(progn (require 'cl) (custom-set-variables (quote (org-babel-load-languages (quote ((emacs-lisp . t) (ein . t)))))) (org-version))"
92 |
93 | .PHONY: travis-install
94 | travis-install:
95 | mkdir -p test/test-install
96 | if [ ! -s "test/test-install/$(PKBUILD).tar.gz" ] ; then \
97 | cd test/test-install ; curl -sLOk https://github.com/melpa/package-build/archive/$(PKBUILD).tar.gz ; fi
98 | cd test/test-install ; tar xfz $(PKBUILD).tar.gz
99 | cd test/test-install ; rm -f $(PKBUILD).tar.gz
100 | cd test/test-install/package-build-$(PKBUILD) ; make -s loaddefs
101 | mkdir -p test/test-install/recipes
102 | cd test/test-install/recipes ; curl -sLOk https://raw.githubusercontent.com/melpa/melpa/master/recipes/ein
103 | $(EMACS) -Q --batch -L test/test-install/package-build-$(PKBUILD) \
104 | --eval "(require 'package-build)" \
105 | --eval "(require 'subr-x)" \
106 | --eval "(package-initialize)" \
107 | --eval "(add-to-list 'package-archives '(\"melpa\" . \"http://melpa.org/packages/\"))" \
108 | --eval "(package-refresh-contents)" \
109 | --eval "(setq rcp (package-recipe-lookup \"ein\"))" \
110 | --eval "(unless (file-exists-p package-build-archive-dir) \
111 | (make-directory package-build-archive-dir))" \
112 | --eval "(let* ((my-repo \"$(TRAVIS_PULL_REQUEST_SLUG)\") \
113 | (my-branch \"$(TRAVIS_PULL_REQUEST_BRANCH)\") \
114 | (my-commit \"$(TRAVIS_PULL_REQUEST_SHA)\")) \
115 | (oset rcp :repo my-repo) \
116 | (oset rcp :branch my-branch) \
117 | (oset rcp :commit my-commit))" \
118 | --eval "(let ((version (package-build--checkout rcp))) \
119 | (delete-directory (expand-file-name (concat \"ein-\" version) package-user-dir) t) \
120 | (package-build--package rcp version))" \
121 | --eval "(package-install-file (car (file-expand-wildcards (concat package-build-archive-dir \"ein*.tar\"))))" 2>&1 | tee /tmp/test-install.out
122 | ! ( egrep -a "Error: " /tmp/test-install.out )
123 |
124 | .PHONY: dist-clean
125 | dist-clean:
126 | rm -rf dist
127 |
128 | .PHONY: dist
129 | dist: dist-clean autoloads
130 | $(CASK) package
131 |
132 | .PHONY: backup-melpa
133 | backup-melpa:
134 | $(EMACS) -Q --batch --eval "(package-initialize)" --eval \
135 | "(with-temp-buffer \
136 | (insert-file-contents-literally (car (file-expand-wildcards \"dist/ein*.tar\"))) \
137 | (tar-mode) \
138 | (let* ((my-desc (package-tar-file-info)) \
139 | (name (package-desc-name my-desc)) \
140 | (other-pkgs (cdr (assq name package-alist)))) \
141 | (mapc (lambda (odesc) \
142 | (let* ((odir (package-desc-dir odesc)) \
143 | (parent (file-name-directory odir)) \
144 | (leaf (file-name-nondirectory odir))) \
145 | (if (equal (package-desc-version my-desc) \
146 | (package-desc-version odesc)) \
147 | (delete-directory odir t) \
148 | (rename-file odir \
149 | (expand-file-name (format \"BACKUP-%s\" leaf) parent) \
150 | t)))) \
151 | other-pkgs)))"
152 |
153 | .PHONY: install
154 | install: dist backup-melpa
155 | $(EMACS) -Q --batch --eval "(package-initialize)" \
156 | --eval "(add-to-list 'package-archives '(\"shmelpa\" . \"https://shmelpa.commandlinesystems.com/packages/\"))" \
157 | --eval "(package-refresh-contents)" \
158 | --eval "(package-install-file (car (file-expand-wildcards \"dist/ein*.tar\")))"
159 |
--------------------------------------------------------------------------------
/lisp/ein-notification.el:
--------------------------------------------------------------------------------
1 | ;;; ein-notification.el --- Notification widget for Notebook -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012- Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-notification.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-notification.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-notification.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 |
29 | (require 'eieio)
30 |
31 | (require 'ein-core)
32 | (require 'ein-classes)
33 | (require 'ein-events)
34 |
35 | (declare-function ein:get-notebook "ein:notebook")
36 | (declare-function ein:notebook-opened-buffer-names "ein:notebook")
37 | (declare-function ein:list-available-kernels "ein:notebook")
38 | (declare-function ein:notebook-switch-kernel "ein:notebook")
39 |
40 | (define-obsolete-variable-alias 'ein:@notification 'ein:%notification% "0.1.2")
41 | (ein:deflocal ein:%notification% nil
42 | "Buffer local variable to hold an instance of `ein:notification'.")
43 |
44 | (defvar ein:header-line-format '(:eval (ein:header-line)))
45 | (defvar ein:header-line-switch-kernel-map (make-sparse-keymap))
46 |
47 | (cl-defmethod ein:notification-status-set ((ns ein:notification-status) status)
48 | (let* ((message (cdr (assoc status (slot-value ns 's2m)))))
49 | (setf (slot-value ns 'status) status)
50 | (setf (slot-value ns 'message) (substitute-command-keys message))
51 | (force-mode-line-update t)))
52 |
53 | (cl-defmethod ein:notification-bind-events ((notification ein:notification) events)
54 | "Bind a callback to events of the event handler EVENTS which
55 | just set the status (= event-type):
56 | (ein:notification-status-set NS EVENT-TYPE)
57 | where NS is `:kernel' or `:notebook' slot of NOTIFICATION."
58 | (cl-loop for ns in (list (slot-value notification 'kernel)
59 | (slot-value notification 'notebook))
60 | for statuses = (mapcar #'car (slot-value ns 's2m))
61 | do (cl-loop for st in statuses
62 | do (ein:events-on events
63 | st ; = event-type
64 | #'ein:notification--callback
65 | (cons ns st))))
66 | (ein:events-on events
67 | 'notebook_saved.Notebook
68 | #'ein:notification--fadeout-callback
69 | (list (slot-value notification 'notebook)
70 | "Notebook is saved"
71 | 'notebook_saved.Notebook
72 | nil))
73 | (ein:events-on events
74 | 'execution_count.Kernel
75 | #'ein:notification--set-execution-count
76 | notification))
77 |
78 | (defun ein:notification--callback (packed _data)
79 | (let ((ns (car packed))
80 | (status (cdr packed)))
81 | (ein:notification-status-set ns status)))
82 |
83 | (defun ein:notification--set-execution-count (notification count)
84 | (setf (oref notification :execution-count) count))
85 |
86 | (defun ein:notification--fadeout-callback (packed _data)
87 | ;; FIXME: I can simplify this.
88 | ;; Do not pass around message, for exmaple.
89 | (cl-destructuring-bind (ns message status &rest) packed
90 | (setf (oref ns :status) status)
91 | (setf (oref ns :message) message)
92 | (apply #'run-at-time
93 | 1 nil
94 | (lambda (ns _message status next)
95 | (when (equal (slot-value ns 'status) status)
96 | (ein:notification-status-set ns next)
97 | ;; (ein:with-live-buffer (slot-value ns :buffer)
98 | ;; (force-mode-line-update))
99 | ))
100 | packed)))
101 |
102 | (defun ein:notification-setup (buffer events &rest tab-slots)
103 | "Setup a new notification widget in the BUFFER.
104 | This function saves the new notification widget instance in the
105 | local variable of the BUFFER.
106 |
107 | Rest of the arguments are for TABs in `header-line'.
108 |
109 | GET-LIST : function
110 | Return a list of worksheets.
111 |
112 | GET-CURRENT : function
113 | Return the current worksheet.
114 |
115 | GET-NAME : function
116 | Return a name of the worksheet given as its argument.
117 |
118 | \(fn buffer events &key get-list get-current)"
119 | (with-current-buffer buffer
120 | (setq ein:%notification%
121 | (make-instance 'ein:notification
122 | :buffer buffer))
123 | (setq header-line-format ein:header-line-format)
124 | (ein:notification-bind-events ein:%notification% events)
125 | (setf (oref ein:%notification% :tab)
126 | (apply #'make-instance 'ein:notification-tab tab-slots))
127 | ein:%notification%))
128 |
129 | (defface ein:notification-tab-normal
130 | '((t :inherit (header-line) :underline t :height 0.8))
131 | "Face for headline selected tab."
132 | :group 'ein)
133 |
134 | (define-key ein:header-line-switch-kernel-map
135 | [header-line mouse-1] 'ein:header-line-switch-kernel)
136 |
137 | (defmacro ein:with-destructuring-bind-key-event (key-event &rest body)
138 | (declare (debug (form &rest form))
139 | (indent 1))
140 | ;; See: (info "(elisp) Click Events")
141 | `(cl-destructuring-bind
142 | (event-type
143 | (window pos-or-area (x . y) timestamp
144 | object text-pos (col . row)
145 | image (dx . dy) (width . height)))
146 | ,key-event
147 | ,@body))
148 |
149 | (defun ein:header-line-switch-kernel (_key-event)
150 | (interactive "e")
151 | (let* ((notebook (or (ein:get-notebook)
152 | (ein:completing-read
153 | "Select notebook: "
154 | (ein:notebook-opened-buffer-names))))
155 | (kernel-name (ein:completing-read
156 | "Select kernel: "
157 | (ein:list-available-kernels (ein:$notebook-url-or-port notebook)))))
158 | (ein:notebook-switch-kernel notebook kernel-name)))
159 |
160 | (defun ein:header-line ()
161 | (format
162 | "IP[%s]: %s"
163 | (slot-value ein:%notification% 'execution-count)
164 | (ein:join-str
165 | " | "
166 | (cl-remove-if-not
167 | #'identity
168 | (list (slot-value (slot-value ein:%notification% 'notebook) 'message)
169 | (slot-value (slot-value ein:%notification% 'kernel) 'message)
170 | (propertize (aif (aand (ein:get-notebook) (ein:$notebook-kernelspec it))
171 | (format "|%s|" (ein:$kernelspec-name it))
172 | "|unknown: please click and select a kernel|")
173 | 'keymap ein:header-line-switch-kernel-map
174 | 'help-echo "Click (mouse-1) to change the running kernel."
175 | 'mouse-face 'highlight
176 | 'face 'ein:notification-tab-normal))))))
177 |
178 | (provide 'ein-notification)
179 |
180 | ;;; ein-notification.el ends here
181 |
--------------------------------------------------------------------------------
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 1.0.{build}
2 |
3 | # you can use {branch} name in version format too
4 | # version: 1.0.{build}-{branch}
5 |
6 | # Do not build on tags (GitHub and BitBucket)
7 | skip_tags: true
8 |
9 | # Skipping commits with particular message or from specific user
10 | skip_commits:
11 | message: /\[skip appveyor\]/ # Regex for matching commit message
12 |
13 | # Do not build feature branch with open Pull Requests
14 | # skip_branch_with_pr: true
15 |
16 | # Maximum number of concurrent jobs for the project
17 | max_jobs: 1
18 |
19 | # Build worker image (VM template)
20 | image: Visual Studio 2017
21 |
22 | # scripts that are called at very beginning, before repo cloning
23 | init:
24 | - git config --global core.autocrlf input
25 |
26 | # clone directory
27 | clone_folder: c:\projects\myproject
28 |
29 | # fetch repository as zip archive
30 | shallow_clone: true # default is "false"
31 |
32 | # set clone depth
33 | clone_depth: 5 # clone entire repository history if not defined
34 |
35 | # setting up etc\hosts file
36 | hosts:
37 | queue-server: 127.0.0.1
38 | db.server.com: 127.0.0.2
39 |
40 | # environment variables
41 | environment:
42 | # this is how to set encrypted variable. Go to "Settings" -> "Encrypt YAML" page in account menu to encrypt data.
43 | my_secure_var1:
44 | secure: FW3tJ3fMncxvs58/ifSP7w==
45 |
46 | # environment:
47 | # global:
48 | # connection_string: server=12;password=13;
49 | # service_url: https://127.0.0.1:8090
50 | #
51 | # matrix:
52 | # - db: mysql
53 | # provider: mysql
54 | #
55 | # - db: mssql
56 | # provider: mssql
57 | # password:
58 | # secure: $#(JFDA)jQ@#$
59 |
60 | # this is how to allow failing jobs in the matrix
61 | matrix:
62 | fast_finish: true # set this flag to immediately finish build once one of the jobs fails.
63 | allow_failures:
64 | - platform: x86
65 | configuration: Debug
66 | - platform: x64
67 | configuration: Release
68 |
69 | # exclude configuration from the matrix. Works similarly to 'allow_failures' but build not even being started for excluded combination.
70 | exclude:
71 | - platform: x86
72 | configuration: Debug
73 |
74 | # build cache to preserve files/folders between builds
75 | cache:
76 | # - packages -> **\packages.config # preserve "packages" directory in the root of build folder but will reset it if packages.config is modified
77 | - C:\python27\
78 | - C:\ProgramData\chocolatey\lib
79 | - C:\ProgramData\chocolatey\bin
80 | # - '%LocalAppData%'
81 | # - node_modules # local npm modules
82 | - '%LocalAppData%\NuGet\Cache' # NuGet < v3
83 | - '%LocalAppData%\NuGet\v3-cache' # NuGet v3
84 |
85 | # enable service required for build/tests
86 | #services:
87 | # - mssql2014 # start SQL Server 2014 Express
88 | # - mssql2014rs # start SQL Server 2014 Express and Reporting Services
89 | # - mssql2012sp1 # start SQL Server 2012 SP1 Express
90 | # - mssql2012sp1rs # start SQL Server 2012 SP1 Express and Reporting Services
91 | # - mssql2008r2sp2 # start SQL Server 2008 R2 SP2 Express
92 | # - mssql2008r2sp2rs # start SQL Server 2008 R2 SP2 Express and Reporting Services
93 | # - mysql # start MySQL 5.6 service
94 | # - postgresql # start PostgreSQL 9.5 service
95 | # - iis # start IIS
96 | # - msmq # start Queuing services
97 | # - mongodb # start MongoDB
98 |
99 | # scripts that run after cloning repository
100 |
101 | # enable patching of AssemblyInfo.* files
102 | # assembly_info:
103 | # patch: true
104 | # file: AssemblyInfo.*
105 | # assembly_version: "2.2.{build}"
106 | # assembly_file_version: "{version}"
107 | # assembly_informational_version: "{version}"
108 |
109 |
110 | # Automatically register private account and/or project AppVeyor NuGet feeds.
111 | #nuget:
112 | # account_feed: true
113 | # project_feed: true
114 | # disable_publish_on_pr: true # disable publishing of .nupkg artifacts to
115 | # # account/project feeds for pull request builds
116 |
117 | #---------------------------------#
118 | # build configuration #
119 | #---------------------------------#
120 |
121 | # build platform, i.e. x86, x64, Any CPU. This setting is optional.
122 | #platform: Any CPU
123 |
124 | # to add several platforms to build matrix:
125 | #platform:
126 | # - x86
127 | # - Any CPU
128 |
129 | # build Configuration, i.e. Debug, Release, etc.
130 | # configuration: Release
131 |
132 | # to add several configurations to build matrix:
133 | #configuration:
134 | # - Debug
135 | # - Release
136 |
137 | # Build settings, not to be confused with "before_build" and "after_build".
138 | # "project" is relative to the original build directory and not influenced by directory changes in "before_build".
139 | #build:
140 | # parallel: true # enable MSBuild parallel builds
141 | # project: MyTestAzureCS.sln # path to Visual Studio solution or project
142 | # publish_wap: true # package Web Application Projects (WAP) for Web Deploy
143 | # publish_wap_xcopy: true # package Web Application Projects (WAP) for XCopy deployment
144 | # publish_azure: true # package Azure Cloud Service projects and push to artifacts
145 | # publish_nuget: true # package projects with .nuspec files and push to artifacts
146 | # publish_nuget_symbols: true # generate and publish NuGet symbol packages
147 | # include_nuget_references: true # add -IncludeReferencedProjects option while packaging NuGet artifacts
148 |
149 | # MSBuild verbosity level
150 | # verbosity: quiet|minimal|normal|detailed
151 |
152 |
153 | # to disable automatic builds
154 | build: off
155 |
156 | #---------------------------------#
157 | # tests configuration #
158 | #---------------------------------#
159 |
160 | # to run tests against only selected assemblies and/or categories
161 | #test:
162 | # assemblies:
163 | # only:
164 | # - asm1.dll
165 | # - asm2.dll
166 | #
167 | # categories:
168 | # only:
169 | # - UI
170 | # - E2E
171 |
172 | # to run tests against all except selected assemblies and/or categories
173 | #test:
174 | # assemblies:
175 | # except:
176 | # - asm1.dll
177 | # - asm2.dll
178 | #
179 | # categories:
180 | # except:
181 | # - UI
182 | # - E2E
183 |
184 | # to run tests from different categories as separate jobs in parallel
185 | #test:
186 | # categories:
187 | # - A # A category common for all jobs
188 | # - [UI] # 1st job
189 | # - [DAL, BL] # 2nd job
190 |
191 | # scripts to run before tests (working directory and environment changes are persisted from the previous steps such as "before_build")
192 | before_test:
193 |
194 | # to run your custom scripts instead of automatic tests
195 | test_script:
196 | - cd c:\projects\myproject
197 | # - make quick
198 |
199 | # scripts to run after tests
200 | after_test:
201 |
202 | # to disable automatic tests
203 | test: off
204 |
205 | # to disable deployment
206 | deploy: off
207 |
208 | #---------------------------------#
209 | # global handlers #
210 | #---------------------------------#
211 |
212 | # on successful build
213 | #on_success:
214 |
215 | # on build failure
216 | #on_failure:
217 | # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
218 |
219 | # after build failure or success
220 | on_finish:
221 | # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
222 |
--------------------------------------------------------------------------------
/test/test-ein-utils.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding:t -*-
2 | (require 'ert)
3 |
4 | (require 'ein)
5 | (require 'ein-utils)
6 |
7 | (ert-deftest ein-url-simple ()
8 | (should (null (ein:url nil)))
9 | (should (equal (ein:url 8888) "http://127.0.0.1:8888"))
10 | (should (equal (ein:url "http://:8000") "http://127.0.0.1:8000"))
11 | (should (equal (ein:url "http://localhost") "http://127.0.0.1"))
12 | (should (equal (ein:url "https://localhost:8888") "https://127.0.0.1:8888"))
13 | (should (equal (ein:url "http://localhost:8000" "" "" "" "Untitled.ipynb") "http://127.0.0.1:8000/Untitled.ipynb"))
14 | (should (equal (ein:url "http://localhost:8000" "") "http://127.0.0.1:8000"))
15 | (should (equal (ein:url "http://localhost:8000") "http://127.0.0.1:8000"))
16 | (should (equal (ein:url "localhost" "foo" "bar") "http://127.0.0.1/foo/bar"))
17 | (should (equal (ein:url "https://localhost" "foo" "bar") "https://127.0.0.1/foo/bar"))
18 | (should (equal (ein:url "datasci-1:8888") "https://datasci-1:8888"))
19 | (should (equal (ein:url "datasci-1" "foo" "bar") "https://datasci-1/foo/bar"))
20 | (should (equal (ein:glom-paths "" nil "Untitled1.ipynb") "Untitled1.ipynb"))
21 | (should (equal (ein:glom-paths) ""))
22 | (cl-loop for url in '("http://127.0.0.1:8888" "http://localhost:8888" "http://127.0.0.1:8888/" "http://localhost:8888/" "8888" 8888)
23 | do (should (equal (ein:url "http://localhost:8888") (ein:url url)))))
24 |
25 | (ert-deftest ein-url-slashes ()
26 | (cl-loop for a in '("a" "a/" "/a")
27 | do (cl-loop for b in '("b" "/b")
28 | do (should (equal (ein:url 8888 a b)
29 | "http://127.0.0.1:8888/a/b")))
30 | do (should (equal (ein:url 8888 a "b/")
31 | "http://127.0.0.1:8888/a/b"))))
32 |
33 | (ert-deftest ein-trim-simple ()
34 | (should (equal (ein:trim "a") "a"))
35 | (should (equal (ein:trim " a ") "a"))
36 | (should (equal (ein:trim "\na\n") "a")))
37 |
38 | (ert-deftest ein-trim-middle-spaces ()
39 | (should (equal (ein:trim "a b") "a b"))
40 | (should (equal (ein:trim " a b ") "a b"))
41 | (should (equal (ein:trim "\na b\n") "a b")))
42 |
43 | (ert-deftest ein-trim-left-simple ()
44 | (should (equal (ein:trim-left "a") "a"))
45 | (should (equal (ein:trim-left " a ") "a "))
46 | (should (equal (ein:trim-left "\na\n") "a\n")))
47 |
48 | (ert-deftest ein-trim-right-simple ()
49 | (should (equal (ein:trim-right "a") "a"))
50 | (should (equal (ein:trim-right " a ") " a"))
51 | (should (equal (ein:trim-right "\na\n") "\na")))
52 |
53 | (ert-deftest ein:trim-indent-empty ()
54 | (should (equal (ein:trim-indent "") "")))
55 |
56 | (ert-deftest ein:trim-indent-one-line ()
57 | (should (equal (ein:trim-indent "one line") "one line")))
58 |
59 | (ert-deftest ein:trim-indent-one-newline ()
60 | (should (equal (ein:trim-indent "one line\n") "one line\n")))
61 |
62 | (ert-deftest ein:trim-indent-multi-lines-no-trim ()
63 | (let ((original "\
64 | def func():
65 | pass
66 | ")
67 | (trimmed "\
68 | def func():
69 | pass
70 | "))
71 | (should (equal (ein:trim-indent original) trimmed))))
72 |
73 | (ert-deftest ein:trim-indent-multi-lines-one-trim ()
74 | (let ((original "\
75 | def func():
76 | pass
77 | ")
78 | (trimmed "\
79 | def func():
80 | pass
81 | "))
82 | (should (equal (ein:trim-indent original) trimmed))))
83 |
84 | (ert-deftest ein:trim-indent-multi-lines-with-empty-lines ()
85 | (let ((original "\
86 |
87 | def func():
88 | pass
89 |
90 | ")
91 | (trimmed "\
92 |
93 | def func():
94 | pass
95 |
96 | "))
97 | (should (equal (ein:trim-indent original) trimmed))))
98 |
99 |
100 | ;;; Text manipulation on buffer
101 |
102 | (ert-deftest ein:find-leftmost-column-simple-cases ()
103 | (cl-loop for (indent text) in
104 | '(;; No indent
105 | (0 "\
106 | def f():
107 | pass")
108 | ;; Indented python code
109 | (4 "\
110 | def f():
111 | pass")
112 | ;; Deeper indent can come first
113 | (4 "\
114 | # indent = 8
115 | # indent 4")
116 | ;; With empty lines
117 | (4 "\
118 |
119 | # indent = 8
120 |
121 | # indent 4
122 |
123 | ")
124 | )
125 | do (with-temp-buffer
126 | (insert text)
127 | (should (= (ein:find-leftmost-column (point-min) (point-max))
128 | indent)))))
129 |
130 |
131 | ;;; Misc
132 |
133 | (ert-deftest ein:list-insert-after ()
134 | (should (equal (ein:list-insert-after '(a) 'a 'X) '(a X)))
135 | (should (equal (ein:list-insert-after '(a b c) 'a 'X) '(a X b c)))
136 | (should (equal (ein:list-insert-after '(a b c) 'b 'X) '(a b X c)))
137 | (should (equal (ein:list-insert-after '(a b c) 'c 'X) '(a b c X)))
138 | (should-error (ein:list-insert-after '(a b c) 'd 'X)))
139 |
140 | (ert-deftest ein:list-insert-before ()
141 | (should (equal (ein:list-insert-before '(a) 'a 'X) '(X a)))
142 | (should (equal (ein:list-insert-before '(a b c) 'a 'X) '(X a b c)))
143 | (should (equal (ein:list-insert-before '(a b c) 'b 'X) '(a X b c)))
144 | (should (equal (ein:list-insert-before '(a b c) 'c 'X) '(a b X c)))
145 | (should-error (ein:list-insert-before '(a b c) 'd 'X)))
146 |
147 | (ert-deftest ein:list-move-left ()
148 | (should (equal (ein:list-move-left '(a) 'a) '(a)))
149 | (should (equal (ein:list-move-left '(a b) 'a) '(b a)))
150 | (should (equal (ein:list-move-left '(a b) 'b) '(b a)))
151 | (should (equal (ein:list-move-left '(a b c d) 'a) '(b c d a)))
152 | (should (equal (ein:list-move-left '(a b c d) 'b) '(b a c d)))
153 | (should (equal (ein:list-move-left '(a b c d) 'c) '(a c b d)))
154 | (should (equal (ein:list-move-left '(a b c d) 'd) '(a b d c)))
155 | (should-error (ein:list-move-left '(a b c d) 'X)))
156 |
157 | (ert-deftest ein:list-move-right ()
158 | (should (equal (ein:list-move-right '(a) 'a) '(a)))
159 | (should (equal (ein:list-move-right '(a b) 'a) '(b a)))
160 | (should (equal (ein:list-move-right '(a b) 'b) '(b a)))
161 | (should (equal (ein:list-move-right '(a b c d) 'a) '(b a c d)))
162 | (should (equal (ein:list-move-right '(a b c d) 'b) '(a c b d)))
163 | (should (equal (ein:list-move-right '(a b c d) 'c) '(a b d c)))
164 | (should (equal (ein:list-move-right '(a b c d) 'd) '(d a b c)))
165 | (should-error (ein:list-move-right '(a b c d) 'X)))
166 |
167 | (defvar test-ein-utils-setting)
168 | (defun ein:testing-choose-setting-should-equal (setting value desired &optional single-p)
169 | (setq test-ein-utils-setting setting)
170 | (should (equal (ein:choose-setting 'test-ein-utils-setting value single-p) desired)))
171 |
172 | (ert-deftest ein:choose-setting-single-string ()
173 | (let ((test 'ein:testing-choose-setting-should-equal))
174 | (funcall test "a" nil "a")
175 | (funcall test "a" 'whatever "a")))
176 |
177 | (ert-deftest ein:choose-setting-single-int ()
178 | (let ((test #'ein:testing-choose-setting-should-equal))
179 | (funcall test 1 nil 1 #'integerp)
180 | (funcall test 1 'whatever 1 #'integerp)))
181 |
182 | (ert-deftest ein:choose-setting-alist ()
183 | (let ((test (lambda (&rest args)
184 | (apply #'ein:testing-choose-setting-should-equal
185 | '(("a" . 1) ("b" . 2) ("c" . 3))
186 | args))))
187 | (funcall test "a" 1)
188 | (funcall test "b" 2)))
189 |
190 | (ert-deftest ein:choose-setting-func ()
191 | (let* ((test (lambda (&rest args)
192 | (apply #'ein:testing-choose-setting-should-equal
193 | (lambda (_x) 1)
194 | args))))
195 | (funcall test nil 1)
196 | (funcall test 'whatever 1)))
197 |
--------------------------------------------------------------------------------
/lisp/ein-traceback.el:
--------------------------------------------------------------------------------
1 | ;;; ein-traceback.el --- Traceback module -*- lexical-binding:t -*-
2 |
3 | ;; Copyright (C) 2012- Takafumi Arakaki
4 |
5 | ;; Author: Takafumi Arakaki
6 |
7 | ;; This file is NOT part of GNU Emacs.
8 |
9 | ;; ein-traceback.el is free software: you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; ein-traceback.el is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with ein-traceback.el. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;;
25 |
26 | ;;; Code:
27 |
28 | (require 'eieio)
29 | (require 'ewoc)
30 | (require 'ansi-color)
31 |
32 | (require 'ein-core)
33 | (require 'ein-shared-output)
34 |
35 | (declare-function ein:get-notebook "ein-notebook")
36 | (declare-function ein:notebook-buffer "ein-notebook")
37 |
38 | (defclass ein:traceback ()
39 | ((tb-data :initarg :tb-data :type list)
40 | (notebook :initarg :source-notebook ;; :type ein:$notebook
41 | :accessor ein:traceback-notebook)
42 | (buffer-name :initarg :buffer-name :type string)
43 | (buffer :initarg :buffer :type buffer)
44 | (ewoc :initarg :ewoc :type ewoc)))
45 |
46 | (ein:deflocal ein:%traceback% nil
47 | "Buffer local variable to store an instance of `ein:traceback'.")
48 |
49 | (defvar ein:tb-buffer-name-template "*ein:tb %s/%s*")
50 |
51 | (defun ein:tb-new (buffer-name notebook)
52 | (make-instance 'ein:traceback
53 | :buffer-name buffer-name
54 | :source-notebook notebook))
55 |
56 | (cl-defmethod ein:tb-get-buffer ((traceback ein:traceback))
57 | (unless (and (slot-boundp traceback :buffer)
58 | (buffer-live-p (slot-value traceback 'buffer)))
59 | (let ((buf (get-buffer-create (slot-value traceback 'buffer-name))))
60 | (setf (slot-value traceback 'buffer) buf)))
61 | (slot-value traceback 'buffer))
62 |
63 | (defun ein:tb-pp (ewoc-data)
64 | (insert (ansi-color-apply ewoc-data)))
65 |
66 | (cl-defmethod ein:tb-render ((traceback ein:traceback) tb-data)
67 | (with-current-buffer (ein:tb-get-buffer traceback)
68 | (setq ein:%traceback% traceback)
69 | (setq buffer-read-only t)
70 | (let ((inhibit-read-only t)
71 | (ewoc (ein:ewoc-create #'ein:tb-pp)))
72 | (erase-buffer)
73 | (setf (slot-value traceback 'ewoc) ewoc)
74 | (setf (slot-value traceback 'tb-data) tb-data)
75 | (mapc (lambda (data) (ewoc-enter-last ewoc data)) tb-data))
76 | (ein:traceback-mode)))
77 |
78 | (cl-defmethod ein:tb-popup ((traceback ein:traceback) tb-data)
79 | (ein:tb-render traceback tb-data)
80 | (pop-to-buffer (ein:tb-get-buffer traceback)))
81 |
82 | ;;;###autoload
83 | (defun ein:tb-show ()
84 | "Show full traceback in traceback viewer."
85 | (interactive)
86 | (unless
87 | (ein:and-let* ((tb-data (ein:get-traceback-data))
88 | (url-or-port (or (ein:get-url-or-port)
89 | (ein:get-url-or-port--shared-output)))
90 | (kernel (or (ein:get-kernel)
91 | (ein:get-kernel--shared-output)))
92 | (kr-id (ein:kernel-id kernel))
93 | (tb-name (format ein:tb-buffer-name-template
94 | url-or-port kr-id)))
95 | (ein:tb-popup (ein:tb-new tb-name (ein:get-notebook)) tb-data)
96 | t)
97 | (error "No traceback is available.")))
98 |
99 | (cl-defmethod ein:tb-range-of-node-at-point ((traceback ein:traceback))
100 | (let* ((ewoc (slot-value traceback 'ewoc))
101 | (ewoc-node (ewoc-locate ewoc))
102 | (beg (ewoc-location ewoc-node))
103 | (end (ein:aand (ewoc-next ewoc ewoc-node) (ewoc-location it))))
104 | (list beg end)))
105 |
106 | (cl-defmethod ein:tb-file-path-at-point ((traceback ein:traceback))
107 | (cl-destructuring-bind (beg end)
108 | (ein:tb-range-of-node-at-point traceback)
109 | (let* ((file-tail
110 | (next-single-property-change beg 'font-lock-face nil end))
111 | (file (when file-tail
112 | (buffer-substring-no-properties beg file-tail))))
113 | (if (string-match "\\.pyc$" file)
114 | (concat (file-name-sans-extension file) ".py")
115 | file))))
116 |
117 | (cl-defmethod ein:tb-file-lineno-at-point ((traceback ein:traceback))
118 | (cl-destructuring-bind (beg end)
119 | (ein:tb-range-of-node-at-point traceback)
120 | (when (save-excursion
121 | (goto-char beg)
122 | (search-forward-regexp "^[-]+> \\([0-9]+\\)" end t))
123 | (string-to-number (match-string 1)))))
124 |
125 | (cl-defmethod ein:tb-jump-to-source-at-point ((traceback ein:traceback)
126 | &optional select)
127 | (let ((file (ein:tb-file-path-at-point traceback))
128 | (lineno (ein:tb-file-lineno-at-point traceback)))
129 | (if (string-match ""
21 | And I press "C-"
22 | And I press "C-c C-k"
23 | And I press "C-"
24 | And I press "C-c C-y"
25 | And I press "C-/"
26 | Then the cursor should be at point "75"
27 |
28 | @undo
29 | Scenario: Collapse doesn't break undo
30 | Given I enable "ein:worksheet-enable-undo"
31 | Given new python notebook
32 | When I type "from time import sleep"
33 | And I press "RET"
34 | And I wait for cell to execute
35 | And I dump buffer
36 | And I press "C-c C-b"
37 | And I type "1 + 1"
38 | And I press "RET"
39 | And I press "C-"
40 | And I press "C-n"
41 | And I type "print("abba\nabba")"
42 | And I press "RET"
43 | And I type "1.618"
44 | And I wait for cell to execute
45 | And I press "C-"
46 | And I press "C-n"
47 | And I type "9"
48 | And I press "C-"
49 | And I dump buffer
50 | And I press "C-c C-e"
51 | And I press "C-/"
52 | And I dump buffer
53 | Then the cursor should be at point "77"
54 | And I undo again
55 | Then the cursor should be at point "53"
56 |
57 | @undo
58 | Scenario: Test the conflagrative commands
59 | Given I enable "ein:worksheet-enable-undo"
60 | Given new python notebook
61 | When I type "import math"
62 | And I press "RET"
63 | And I press "M-RET"
64 | And I type "[i for i in [1,2]]"
65 | And I press "M-RET"
66 | And I type "math.log(math.exp(1.0))"
67 | And I wait for cell to execute
68 | And I press "C-"
69 | And I press "C-"
70 | And I press "C-n"
71 | And I type "print("m")"
72 | And I wait for cell to execute
73 | And I press "C-u C-c C-v"
74 | And I press "C-/"
75 | Then the cursor should be at point "22"
76 | And I undo again
77 | And I dump buffer
78 | And I press "C-c C-v"
79 | And I press "C-/"
80 | And I undo again
81 | Then the cursor should be at point "22"
82 | And I press "C-c C-S-l"
83 | And I press "C-/"
84 | And I undo again
85 | And I undo again
86 | Then the cursor should be at point "22"
87 |
88 | @undo
89 | Scenario: Clear output doesn't break undo, throw in multiple-cursors
90 | Given I enable "ein:worksheet-enable-undo"
91 | Given new python notebook
92 | And I clear log expr "ein:log-all-buffer-name"
93 | When I type "from time import sleep"
94 | And I press "RET"
95 | And I press "C-c C-b"
96 | And I type "1 + 1"
97 | And I press "RET"
98 | And I press "C-"
99 | And I press "C-n"
100 | And I type "print("abba\nabba")"
101 | And I press "RET"
102 | And I type "1.618"
103 | And I add fake cursor to undo list
104 | And I wait for cell to execute
105 | And I press "C-"
106 | And I press "C-n"
107 | And I type "undo meee"
108 | And I press "C-"
109 | And I press "C-c C-l"
110 | And I press "C-/"
111 | Then the cursor should be at point "74"
112 | And I undo again
113 | Then the cursor should be at point "53"
114 | And I switch to log expr "ein:log-all-buffer-name"
115 | Then I should see "multiple-cursors-mode exception"
116 |
117 | @undo
118 | Scenario: Moving cells doesn't break undo
119 | Given I enable "ein:worksheet-enable-undo"
120 | Given new python notebook
121 | When I type "100"
122 | And I press "C-c C-b"
123 | And I type "200"
124 | And I press "C-c C-b"
125 | And I type "print("hello")"
126 | And I wait for cell to execute
127 | And I press "C-"
128 | And I wait for cell to execute
129 | And I press "C-"
130 | And I press "C-c "
131 | And I press "C-/"
132 | Then the cursor should be at point "54"
133 | And I press "C-"
134 | And I press "C-"
135 | And I wait for cell to execute
136 | And I press "C-c "
137 | And I press "C-/"
138 | Then the cursor should be at point "67"
139 |
140 | @undo
141 | Scenario: Split and merge don't break undo
142 | Given I enable "ein:worksheet-enable-undo"
143 | Given new python notebook
144 | When I type "print("hello")"
145 | And I press "C-c C-b"
146 | And I type "1111"
147 | And I press "RET"
148 | And I press "RET"
149 | And I press "RET"
150 | And I type "2222"
151 | And I press "RET"
152 | And I type "3333"
153 | And I press "C-c C-b"
154 | And I type "4444"
155 | And I press "C-"
156 | And I press "C-n"
157 | And I press "C-c C-s"
158 | And I wait for cell to execute
159 | And I press "C-"
160 | And I wait for cell to execute
161 | And I press "C-"
162 | And I wait for cell to execute
163 | And I press "C-/"
164 | And I press "C-"
165 | And I type "5555"
166 | And I press "RET"
167 | And I type "6666"
168 | And I wait for cell to execute
169 | And I press "C-/"
170 | And I undo again
171 | And I undo again
172 | And I undo again
173 | And I undo again
174 | Then the cursor should be at point "70"
175 | And I press "C-c C-m"
176 | And I press "C-c C-m"
177 | And I press "C-/"
178 | And I undo again
179 | And I undo again
180 | And I undo again
181 | Then the cursor should be at point "50"
182 |
183 | @undo
184 | Scenario: Undo needs to at least work for reopened notebooks
185 | Given I enable "ein:worksheet-enable-undo"
186 | Given old notebook "undo.ipynb"
187 | And I type "howdy"
188 | And I press "RET"
189 | And I press "C-"
190 | And I press "C-"
191 | And I type "rowdy"
192 | And I press "RET"
193 | And I press "C-"
194 | And I press "C-"
195 | And I press "C-c C-k"
196 | And I type "bowdy"
197 | And I press "RET"
198 | And I press "C-c C-y"
199 | And I press "C-/"
200 | Then the cursor should be at point "15"
201 | And I press "C-/"
202 | And I press "C-n"
203 | And I press "C-n"
204 | And I press "C-c C-s"
205 | And I press "C-/"
206 | And I undo again
207 | And I undo again
208 | And I undo again
209 | And I undo again
210 | Then the cursor should be at point "20"
211 | And I undo again
212 | Then the cursor should be at point "88"
213 | And I press "C-"
214 | And I press "C-k"
215 | And I press "C-k"
216 | And I press "C-k"
217 | And I type "1.618"
218 | And I wait for cell to execute
219 | And I press "C-"
220 | And I press "C-c C-m"
221 | And I press "C-n"
222 | And I press "C-n"
223 | And I press "C-c C-s"
224 | And I press "C-/"
225 | And I undo again
226 | And I undo again
227 | And I undo again
228 | Then the cursor should be at point "124"
229 |
230 | @undo
231 | Scenario: Execute all cells, mod some cells, get outputs, undo mods
232 | Given I enable "ein:worksheet-enable-undo"
233 | Given new python notebook
234 | When I type "from time import sleep"
235 | And I press "RET"
236 | And I type "sleep(3)"
237 | And I press "C-c C-b"
238 | And I type "sleep(1)"
239 | And I press "RET"
240 | And I type "import math"
241 | And I press "RET"
242 | And I type "2*math.asin(1)"
243 | And I press "C-c C-b"
244 | And I press "C-c C-t"
245 | And I type "mark"
246 | And I call "ein:worksheet-execute-all-cells-above"
247 | And I press "C-c C-b"
248 | And I type "undo"
249 | And I press "C-"
250 | And I press "C-"
251 | And I type "surprise"
252 | And I wait for buffer to say "3.14159"
253 | And I press "C-/"
254 | Then the cursor should be at point "51"
255 | And I undo again
256 | Then the cursor should be at point "139"
257 | And I undo again
258 | And dump diagnostic
259 | Then the cursor should be at point "125"
260 |
261 | @undo
262 | Scenario: Toggling between markdown and codecell does not break undo
263 | Given I enable "ein:worksheet-enable-undo"
264 | Given new python notebook
265 | When I type ""to be markdown""
266 | And I press "C-c C-b"
267 | And I type "200"
268 | And I wait for cell to execute
269 | And I press "C-"
270 | And I press "C-c C-t"
271 | And I press "C-/"
272 | Then the cursor should be at point "38"
273 | And I press "C-"
274 | And I press "C-c C-t"
275 | And I press "C-/"
276 | Then the cursor should be at point "33"
277 | And I press "C-"
278 | And I press "C-c C-t"
279 | And I wait for cell to execute
280 | And I press "C-/"
281 | Then the cursor should be at point "62"
282 |
--------------------------------------------------------------------------------
/test/test-ein-cell-notebook.el:
--------------------------------------------------------------------------------
1 | ;; Tests for cell function that requires notebook buffer -*- lexical-binding:t -*-
2 |
3 | (require 'ert)
4 |
5 | (require 'ein-notebook)
6 | (require 'ein-testing-notebook)
7 |
8 | ;; ein:cell-location
9 |
10 | (ert-deftest ein:cell-location-codecell-prompt-beg ()
11 | (ein:testing-with-one-cell 'code
12 | (should (equal (marker-position (ein:cell-location cell :prompt))
13 | (save-excursion
14 | (goto-char (point-max))
15 | (search-backward "In [ ]:")
16 | (point))))))
17 |
18 | (ert-deftest ein:cell-location-codecell-prompt-end ()
19 | (ein:testing-with-one-cell 'code
20 | (should (equal (marker-position (ein:cell-location cell :prompt t))
21 | (1- (point))))))
22 |
23 | (ert-deftest ein:cell-location-codecell-input-beg ()
24 | (ein:testing-with-one-cell 'code
25 | (insert "some text")
26 | (should (equal (marker-position (ein:cell-location cell :input))
27 | (1- (line-beginning-position))))))
28 |
29 | (ert-deftest ein:cell-location-codecell-input-end ()
30 | (ein:testing-with-one-cell 'code
31 | (insert "some text")
32 | (should (equal (marker-position (ein:cell-location cell :input t))
33 | (1+ (point))))))
34 |
35 | ;; from-json
36 |
37 | (ert-deftest eintest:cell-input-prompt-number ()
38 | (ein:testing-with-one-cell
39 | (ein:cell-from-json
40 | (list :cell_type "code"
41 | :source "some input"
42 | :metadata (list :collapsed json-false :autoscroll json-false)
43 | :execution_count 111)
44 | :ewoc (oref ein:%worksheet% :ewoc))
45 | (goto-char (ein:cell-location cell))
46 | (should (looking-at "\
47 | In \\[111\\]:
48 | some input
49 | "))))
50 |
51 | (ert-deftest eintest:cell-input-prompt-star ()
52 | (ein:testing-with-one-cell
53 | (ein:cell-from-json
54 | (list :cell_type "code"
55 | :source "some input"
56 | :metadata (list :collapsed json-false :autoscroll json-false)
57 | :execution_count "*")
58 | :ewoc (oref ein:%worksheet% :ewoc))
59 | (goto-char (ein:cell-location cell))
60 | (should (looking-at "\
61 | In \\[\\*\\]:
62 | some input
63 | "))))
64 |
65 | (ert-deftest eintest:cell-input-prompt-empty ()
66 | (ein:testing-with-one-cell
67 | (ein:cell-from-json
68 | (list :cell_type "code"
69 | :metadata (list :collapsed json-false :autoscroll json-false)
70 | :source "some input")
71 | :ewoc (oref ein:%worksheet% :ewoc))
72 | (goto-char (ein:cell-location cell))
73 | (should (looking-at "\
74 | In \\[ \\]:
75 | some input
76 | "))))
77 |
78 | ;; Insert pyout/display_data
79 |
80 | (defun eintest:cell-insert-output (outputs regexp)
81 | (let ((shr-width 0))
82 | (ein:testing-with-one-cell
83 | (ein:cell-from-json
84 | (list :cell_type "code"
85 | :outputs outputs
86 | :source "some input"
87 | :metadata (list :collapsed json-false :autoscroll json-false)
88 | :execution_count 111)
89 | :ewoc (oref ein:%worksheet% :ewoc))
90 | (goto-char (ein:cell-location cell))
91 | (should (looking-at (format "\
92 | In \\[111\\]:
93 | some input
94 | %s" regexp))))))
95 |
96 | (defmacro eintest:gene-test-cell-insert-output-pyout-and-display-data (name regexps outputs)
97 | (declare (indent defun))
98 | (let ((test-pyout
99 | (intern (format "ein:cell-insert-output-pyout-%s" name)))
100 | (test-display-data
101 | (intern (format "ein:cell-insert-output-display-data-%s" name)))
102 | (outputs-pyout
103 | (cl-loop for i from 1
104 | for x in outputs
105 | collect
106 | ;; ein:cell--handle-output doesn't get called
107 | ;; so can't use :execution_count here although that is preferable
108 | (append x (list :output_type "execute_result" :prompt_number i :metadata nil))))
109 | (outputs-display-data
110 | (mapcar (lambda (x) (append '(:output_type "display_data" :metadata nil) x))
111 | outputs))
112 | (regexp-pyout
113 | (ein:join-str
114 | ""
115 | (cl-loop for i from 1
116 | for x in regexps
117 | collect (format "Out \\[%s\\]:\n%s\n" i x))))
118 | (regexp-display-data
119 | (concat (ein:join-str "\n" regexps) "\n")))
120 | `(progn
121 | (ert-deftest ,test-pyout ()
122 | (eintest:cell-insert-output ',outputs-pyout
123 | ,regexp-pyout))
124 | (ert-deftest ,test-display-data ()
125 | (eintest:cell-insert-output ',outputs-display-data
126 | ,regexp-display-data)))))
127 |
128 | (eintest:gene-test-cell-insert-output-pyout-and-display-data
129 | text ("some output") ((:data (:text/plain "some output"))))
130 |
131 | (eintest:gene-test-cell-insert-output-pyout-and-display-data
132 | latex
133 | ("some output \\\\LaTeX")
134 | ((:data (:application/latex "some output \\LaTeX"))))
135 |
136 | (when (image-type-available-p 'svg)
137 | (eintest:gene-test-cell-insert-output-pyout-and-display-data
138 | svg
139 | ("\\.")
140 | ((:data (:text/plain "some output text" :image/svg+xml ein:testing-example-svg)))))
141 |
142 | (eintest:gene-test-cell-insert-output-pyout-and-display-data
143 | html
144 | ("some output text")
145 | ((:data (:text/plain "some output text" :text/html "not shown"))))
146 |
147 | (eintest:gene-test-cell-insert-output-pyout-and-display-data
148 | javascript
149 | ("some output text")
150 | ((:data (:text/plain "some output text" :application/javascript "$.do.something()"))))
151 |
152 | (eintest:gene-test-cell-insert-output-pyout-and-display-data
153 | text-two
154 | ("first output text" "second output text")
155 | ((:data (:text/plain "first output text")) (:data (:text/plain "second output text"))))
156 |
157 | (eintest:gene-test-cell-insert-output-pyout-and-display-data
158 | text-javascript
159 | ("first output text" "second output text")
160 | ((:data (:text/plain "first output text"))
161 | (:data (:text/plain "second output text" :application/javascript "$.do.something()"))))
162 |
163 | (when (image-type-available-p 'svg)
164 | (eintest:gene-test-cell-insert-output-pyout-and-display-data
165 | text-latex-svg
166 | ("first output text" "second output \\\\LaTeX")
167 | ((:data (:text/plain "first output text"))
168 | (:data (:application/latex "second output \\LaTeX"))
169 | (:data (:text/plain "some output text" :image/svg+xml ein:testing-example-svg)))))
170 |
171 | ;; Insert pyerr
172 |
173 | (ert-deftest ein:cell-insert-output-pyerr-simple ()
174 | (eintest:cell-insert-output
175 | (list (list :output_type "pyerr"
176 | :traceback '("some traceback 1"
177 | "some traceback 2")))
178 | "\
179 | some traceback 1
180 | some traceback 2
181 | "))
182 |
183 |
184 | ;; Insert stream
185 |
186 | (ert-deftest ein:cell-insert-output-stream-simple-stdout ()
187 | (eintest:cell-insert-output
188 | (list (list :output_type "stream"
189 | :stream "stdout"
190 | :text "some stdout 1"))
191 | "\
192 | some stdout 1
193 | "))
194 |
195 | (ert-deftest ein:cell-insert-output-stream-stdout-stderr ()
196 | (eintest:cell-insert-output
197 | (list (list :output_type "stream"
198 | :stream "stdout"
199 | :text "some stdout 1")
200 | (list :output_type "stream"
201 | :stream "stderr"
202 | :text "some stderr 1"))
203 | "\
204 | some stdout 1
205 | some stderr 1
206 | "))
207 |
208 | (ert-deftest ein:cell-insert-output-stream-flushed-stdout ()
209 | (eintest:cell-insert-output
210 | (list (list :output_type "stream"
211 | :stream "stdout"
212 | :text "some stdout 1")
213 | (list :output_type "stream"
214 | :stream "stdout"
215 | :text "some stdout 2"))
216 | "\
217 | some stdout 1some stdout 2
218 | "))
219 |
220 | (ert-deftest ein:cell-insert-output-stream-flushed-stdout-and-stderr ()
221 | (eintest:cell-insert-output
222 | (list (list :output_type "stream"
223 | :stream "stdout"
224 | :text "some stdout 1")
225 | (list :output_type "stream"
226 | :stream "stderr"
227 | :text "some stderr 1")
228 | (list :output_type "stream"
229 | :stream "stdout"
230 | :text "some stdout 2")
231 | (list :output_type "stream"
232 | :stream "stderr"
233 | :text "some stderr 2"))
234 | "\
235 | some stdout 1
236 | some stderr 1
237 | some stdout 2
238 | some stderr 2
239 | "))
240 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ==========================================================
2 | EIN -- Emacs IPython Notebook |build-status| |melpa-dev|
3 | ==========================================================
4 |
5 | .. image:: https://github.com/dickmao/emacs-ipython-notebook/blob/master/thumbnail.png
6 | :target: https://youtu.be/8VzWc9QeOxE
7 | :alt: Kaggle Notebooks in AWS
8 |
9 | Emacs IPython Notebook (EIN), despite its name, is a jupyter client for all
10 | languages. It does not work under non-WSL Windows environments.
11 |
12 | As of 2023, EIN has been sunset for a number of years having been
13 | unable to keep up with jupyter's web-first ecosystem. Even during
14 | its heyday EIN never fully reconciled emac's monolithic buffer
15 | architecture to the notebook's by-cell discretization, leaving
16 | gaping functional holes like crippled undo.
17 |
18 | **As of 2025, a greenfield notebook implementation resides at,**
19 |
20 | https://github.com/commercial-emacs/xjupyter.git
21 |
22 | It features full-fledged undo and relies on "mode overlays"
23 | instead of the complex and fragile polymode.
24 |
25 | .. |build-status|
26 | image:: https://github.com/millejoh/emacs-ipython-notebook/workflows/CI/badge.svg
27 | :target: https://github.com/millejoh/emacs-ipython-notebook/actions
28 | :alt: Build Status
29 | .. |melpa-dev|
30 | image:: https://melpa.org/packages/ein-badge.svg
31 | :target: http://melpa.org/#/ein
32 | :alt: MELPA current version
33 | .. _Jupyter: http://jupyter.org
34 | .. _Babel: https://orgmode.org/worg/org-contrib/babel/intro.html
35 | .. _Org: https://orgmode.org
36 | .. _[tkf]: http://tkf.github.io
37 | .. _[gregsexton]: https://github.com/gregsexton/ob-ipython
38 |
39 | Install
40 | =======
41 | As described in `Getting started`_, ensure melpa's whereabouts in ``init.el`` or ``.emacs``::
42 |
43 | (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
44 |
45 | Then
46 |
47 | ::
48 |
49 | M-x package-install RET ein RET
50 |
51 | Alternatively, directly clone this repo and ``make install``.
52 |
53 | For jupyterlab 3.0+, reconfigure the subcommand from "notebook" to "server".
54 |
55 | ::
56 |
57 | M-x customize-option RET ein:jupyter-server-use-subcommand RET
58 |
59 | Usage
60 | =====
61 | Start EIN using **ONE** of the following:
62 |
63 | * Open an ``.ipynb`` file, press ``C-c C-o``, or,
64 | * ``M-x ein:run`` launches a jupyter process from emacs, or,
65 | * ``M-x ein:login`` to a running jupyter server, or,
66 |
67 | ``M-x ein:stop`` prompts to halt local and remote jupyter services.
68 |
69 | Alternatively, ob-ein_.
70 |
71 | .. _Cask: https://cask.readthedocs.io/en/latest/guide/installation.html
72 | .. _Getting started: http://melpa.org/#/getting-started
73 |
74 | FAQ
75 | ===
76 |
77 | How do I...
78 | -----------
79 |
80 | ... report a bug?
81 | Note EIN is tested only for *released* GNU Emacs versions
82 | 26.3
83 | and later. Pre-release versions will not work.
84 |
85 | First try ``emacs -Q -f package-initialize -f ein:dev-start-debug`` and reproduce the bug. The ``-Q`` skips any user configuration that might interfere with EIN.
86 |
87 | Then file an issue using ``M-x ein:dev-bug-report-template``.
88 |
89 | ... display images inline?
90 | We find inserting images into emacs disruptive, and so default to spawning an external viewer. To override this,
91 | ::
92 |
93 | M-x customize-group RET ein
94 | Ein:Output Area Inlined Images
95 |
96 | ... configure the external image viewer?
97 | ::
98 |
99 | M-x customize-group RET mailcap
100 | Mailcap User Mime Data
101 |
102 | On a typical Linux system, one might configure a viewer for MIME Type ``image/png`` as a shell command ``convert %s -background white -alpha remove -alpha off - | display -immutable``.
103 |
104 | ... get IDE-like behavior?
105 | You can't. EIN's architecture is fundamentally incompatible with LSP.
106 |
107 | .. _Issues: https://github.com/millejoh/emacs-ipython-notebook/issues
108 | .. _prevailing documentation: http://millejoh.github.io/emacs-ipython-notebook
109 | .. _spacemacs layer: https://github.com/syl20bnr/spacemacs/tree/master/layers/%2Blang/ipython-notebook
110 | .. _company-mode: https://github.com/company-mode/company-mode
111 | .. _jupyterhub: https://github.com/jupyterhub/jupyterhub
112 | .. _elpy: https://melpa.org/#/elpy
113 | .. _math-preview: https://gitlab.com/matsievskiysv/math-preview
114 | .. _program modes: https://www.gnu.org/software/emacs/manual/html_node/emacs/Program-Modes.html
115 | .. _undo boundaries: https://www.gnu.org/software/emacs/manual/html_node/elisp/Undo.html
116 |
117 | ob-ein
118 | ======
119 | Configuration:
120 |
121 | ::
122 |
123 | M-x customize-group RET org-babel
124 | Org Babel Load Languages:
125 | Insert (ein . t)
126 | For example, '((emacs-lisp . t) (ein . t))
127 |
128 | Snippet:
129 |
130 | ::
131 |
132 | #+BEGIN_SRC ein-python :session localhost
133 | import numpy, math, matplotlib.pyplot as plt
134 | %matplotlib inline
135 | x = numpy.linspace(0, 2*math.pi)
136 | plt.plot(x, numpy.sin(x))
137 | #+END_SRC
138 |
139 | The ``:session`` is the notebook url, e.g., ``http://localhost:8888/my.ipynb``, or simply ``localhost``, in which case org evaluates anonymously. A port may also be specified, e.g., ``localhost:8889``.
140 |
141 | *Language* can be ``ein-python``, ``ein-r``, or ``ein-julia``. **The relevant** `jupyter kernel`_ **must be installed before use**. Additional languages can be configured via::
142 |
143 | M-x customize-group RET ein
144 | Ob Ein Languages
145 |
146 | .. _polymode: https://github.com/polymode/polymode
147 | .. _ob-ipython: https://github.com/gregsexton/ob-ipython
148 | .. _scimax: https://github.com/jkitchin/scimax
149 | .. _jupyter kernel: https://github.com/jupyter/jupyter/wiki/Jupyter-kernels
150 |
151 | .. _gat utility: https://dickmaogat.readthedocs.io/en/latest/install.html
152 | .. _gat usage: https://dickmaogat.readthedocs.io/en/latest/usage.html
153 | .. _batch mode: https://nbconvert.readthedocs.io/en/latest/execute_api.html
154 | .. _dickmao/Kaggler: https://github.com/dickmao/Kaggler/tree/gcspath#importing-datasets
155 |
156 | Keymap (C-h m)
157 | ==============
158 |
159 | ::
160 |
161 | Key Binding
162 | -------------------------------------------------------------------------------
163 | C- ein:worksheet-goto-next-input-km
164 | C- ein:worksheet-goto-prev-input-km
165 | M-S- ein:worksheet-execute-cell-and-insert-below-km
166 | M- ein:worksheet-not-move-cell-down-km
167 | M- ein:worksheet-not-move-cell-up-km
168 |
169 | C-x C-s ein:notebook-save-notebook-command-km
170 | C-x C-w ein:notebook-rename-command-km
171 |
172 | M-RET ein:worksheet-execute-cell-and-goto-next-km
173 | M-, ein:pytools-jump-back-command
174 | M-. ein:pytools-jump-to-source-command
175 |
176 | C-c C-a ein:worksheet-insert-cell-above-km
177 | C-c C-b ein:worksheet-insert-cell-below-km
178 | C-c C-c ein:worksheet-execute-cell-km
179 | C-u C-c C-c ein:worksheet-execute-all-cells
180 | C-c C-e ein:worksheet-toggle-output-km
181 | C-c C-f ein:file-open-km
182 | C-c C-k ein:worksheet-kill-cell-km
183 | C-c C-l ein:worksheet-clear-output-km
184 | C-c RET ein:worksheet-merge-cell-km
185 | C-c C-n ein:worksheet-goto-next-input-km
186 | C-c C-o ein:notebook-open-km
187 | C-c C-p ein:worksheet-goto-prev-input-km
188 | C-c C-q ein:notebook-kill-kernel-then-close-command-km
189 | C-c C-r ein:notebook-reconnect-session-command-km
190 | C-c C-s ein:worksheet-split-cell-at-point-km
191 | C-c C-t ein:worksheet-toggle-cell-type-km
192 | C-c C-u ein:worksheet-change-cell-type-km
193 | C-c C-v ein:worksheet-set-output-visibility-all-km
194 | C-c C-w ein:worksheet-copy-cell-km
195 | C-c C-y ein:worksheet-yank-cell-km
196 | C-c C-z ein:notebook-kernel-interrupt-command-km
197 | C-c C-S-l ein:worksheet-clear-all-output-km
198 | C-c C-# ein:notebook-close-km
199 | C-c C-$ ein:tb-show-km
200 | C-c C-/ ein:notebook-scratchsheet-open-km
201 | C-c C-; ein:shared-output-show-code-cell-at-point-km
202 | C-c ein:worksheet-move-cell-down-km
203 | C-c ein:worksheet-move-cell-up-km
204 |
205 | C-c C-x C-r ein:notebook-restart-session-command-km
206 |
207 | C-c M-w ein:worksheet-copy-cell-km
208 |
209 |
210 | This is a minor mode. If called interactively, toggle the ‘Ein:Notebook
211 | mode’ mode. If the prefix argument is positive, enable the mode, and if
212 | it is zero or negative, disable the mode.
213 |
214 | If called from Lisp, toggle the mode if ARG is ‘toggle’. Enable the
215 | mode if ARG is nil, omitted, or is a positive number. Disable the mode
216 | if ARG is a negative number.
217 |
218 | To check whether the minor mode is enabled in the current buffer,
219 | evaluate the variable ‘ein:notebook-mode’.
220 |
221 | The mode’s hook is called both when the mode is enabled and when it is
222 | disabled.
223 |
--------------------------------------------------------------------------------