Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1822 lines
77 KiB

  1. ;;; ein-notebook.el --- Notebook module -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2012- Takafumi Arakaki
  3. ;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
  4. ;; This file is NOT part of GNU Emacs.
  5. ;; ein-notebook.el is free software: you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; ein-notebook.el is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with ein-notebook.el. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; * Coding rule about current buffer.
  17. ;; A lot of notebook and cell functions touches to current buffer and
  18. ;; it is not ideal to wrap all these functions by `with-current-buffer'.
  19. ;; Therefore, when the function takes `notebook' to the first argument
  20. ;; ("method" function), it is always assumed that the current buffer
  21. ;; is the notebook buffer. **However**, functions called as callback
  22. ;; (via `url-retrieve', for example) must protect themselves by
  23. ;; calling from unknown buffer.
  24. ;;; Code:
  25. (eval-when-compile (require 'auto-complete))
  26. (require 'ewoc)
  27. (require 'mumamo nil t)
  28. (require 'company nil t)
  29. (require 'px nil t)
  30. (require 'eldoc nil t)
  31. (require 'ein-core)
  32. (require 'ein-classes)
  33. (require 'ein-console)
  34. (require 'ein-log)
  35. (require 'ein-node)
  36. (require 'ein-contents-api)
  37. (require 'ein-kernel)
  38. (require 'ein-kernelinfo)
  39. (require 'ein-cell)
  40. (require 'ein-cell-edit)
  41. (require 'ein-cell-output)
  42. (require 'ein-worksheet)
  43. (require 'ein-iexec)
  44. (require 'ein-scratchsheet)
  45. (require 'ein-notification)
  46. (require 'ein-completer)
  47. (require 'ein-pager)
  48. (require 'ein-pseudo-console)
  49. (require 'ein-events)
  50. (require 'ein-notification)
  51. (require 'ein-kill-ring)
  52. (require 'ein-query)
  53. (require 'ein-pytools)
  54. (require 'ein-traceback)
  55. (require 'ein-inspector)
  56. (require 'ein-shared-output)
  57. (require 'ein-notebooklist)
  58. (require 'ein-multilang)
  59. (require 'ob-ein)
  60. (require 'poly-ein)
  61. ;;; Configuration
  62. (make-obsolete-variable 'ein:notebook-discard-output-on-save nil "0.2.0")
  63. (declare-function ein:smartrep-config "ein-smartrep")
  64. (defcustom ein:use-smartrep nil
  65. "Set to `t' to use preset smartrep configuration.
  66. .. warning:: When used with MuMaMo (see `ein:notebook-modes'),
  67. keyboard macro which manipulates cell (add, remove, move,
  68. etc.) may start infinite loop (you need to stop it with
  69. ``C-g``). Please be careful using this option if you are a
  70. heavy keyboard macro user. Using keyboard macro for other
  71. commands is fine.
  72. .. (Comment) I guess this infinite loop happens because the three
  73. modules (kmacro.el, mumamo.el and smartrep.el) touches to
  74. `unread-command-events' in somehow inconsistent ways."
  75. :type 'boolean
  76. :group 'ein)
  77. (defvar *ein:notebook--pending-query* (make-hash-table :test 'equal)
  78. "A map: (URL-OR-PORT . PATH) => t/nil")
  79. (defcustom ein:notebook-autosave-frequency 300
  80. "Sets the frequency (in seconds) at which the notebook is
  81. automatically saved, per IPEP15. Set to 0 to disable this feature.
  82. Autosaves are automatically enabled when a notebook is opened,
  83. but can be controlled manually via `ein:notebook-enable-autosave'
  84. and `ein:notebook-disable-autosave'.
  85. If you wish to change the autosave frequency for the current
  86. notebook call `ein:notebook-update-autosave-freqency'.
  87. "
  88. :type 'number
  89. :group 'ein)
  90. (defcustom ein:notebook-create-checkpoint-on-save t
  91. "If non-nil a checkpoint will be created every time the
  92. notebook is saved. Otherwise checkpoints must be created manually
  93. via `ein:notebook-create-checkpoint'."
  94. :type 'boolean
  95. :group 'ein)
  96. (defcustom ein:notebook-discard-output-on-save 'no
  97. "Configure if the output part of the cell should be saved or not.
  98. .. warning:: This configuration is obsolete now.
  99. Use nbconvert (https://github.com/ipython/nbconvert) to
  100. strip output.
  101. `no' : symbol
  102. Save output. This is the default.
  103. `yes' : symbol
  104. Always discard output.
  105. a function
  106. This function takes two arguments, notebook and cell. Return
  107. `t' to discard output and return `nil' to save. For example,
  108. if you don't want to save image output but other kind of
  109. output, use `ein:notebook-cell-has-image-output-p'.
  110. "
  111. :type '(choice (const :tag "No" 'no)
  112. (const :tag "Yes" 'yes)
  113. )
  114. :group 'ein)
  115. (defun ein:notebook-cell-has-image-output-p (_ignore cell)
  116. (ein:cell-has-image-ouput-p cell))
  117. (defun ein:notebook-discard-output-p (notebook cell)
  118. "Return non-`nil' if the output must be discarded, otherwise save."
  119. (cl-case ein:notebook-discard-output-on-save
  120. (no nil)
  121. (yes t)
  122. (t (funcall ein:notebook-discard-output-on-save notebook cell))))
  123. ;; As opening/saving notebook treats possibly huge data, define these
  124. ;; timeouts separately:
  125. (defcustom ein:notebook-querty-timeout-save (* 60 1000) ; 1 min
  126. "Query timeout for saving notebook.
  127. Similar to `ein:notebook-querty-timeout-open', but for saving
  128. notebook. For global setting and more information, see
  129. `ein:query-timeout'."
  130. :type '(choice (integer :tag "Timeout [ms]" 5000)
  131. (const :tag "Use global setting" nil))
  132. :group 'ein)
  133. (defcustom ein:helm-kernel-history-search-key nil
  134. "Bind `helm-ein-kernel-history' to this key in notebook mode.
  135. Example::
  136. (setq ein:helm-kernel-history-search-key \"\\M-r\")
  137. This key will be installed in the `ein:notebook-mode-map'."
  138. :type 'boolean
  139. :group 'ein)
  140. (defcustom ein:anything-kernel-history-search-key nil
  141. "Bind `anything-ein-kernel-history' to this key in notebook mode.
  142. Example::
  143. (setq ein:anything-kernel-history-search-key \"\\M-r\")
  144. This key will be installed in the `ein:notebook-mode-map'."
  145. :type 'boolean
  146. :group 'ein)
  147. (defcustom ein:notebook-set-buffer-file-name nil
  148. "[DEPRECATED] Set `buffer-file-name' of notebook buffer. Currently does nothing."
  149. :type 'boolean
  150. :group 'ein)
  151. (defvar ein:notebook-after-rename-hook nil
  152. "Hooks to run after notebook is renamed successfully.
  153. Current buffer for these functions is set to the notebook buffer.")
  154. ;;; Class and variable
  155. (defvar ein:base-kernel-url "/api/")
  156. (defvar ein:create-session-url "/api/sessions")
  157. ;; Currently there is no way to know this setting. Maybe I should ask IPython
  158. ;; developers for an API to get this from notebook server.
  159. ;;
  160. ;; 10April2014 (JMM) - The most recent documentation for the RESTful interface
  161. ;; is at:
  162. ;; https://github.com/ipython/ipython/wiki/IPEP-16%3A-Notebook-multi-directory-dashboard-and-URL-mapping
  163. (defvar ein:notebook-pager-buffer-name-template "*ein:pager %s/%s*")
  164. (defvar ein:notebook-buffer-name-template "*ein: %s/%s*")
  165. (ein:deflocal ein:%notebook% nil
  166. "Buffer local variable to store an instance of `ein:$notebook'.")
  167. (ein:deflocal ein:%notebook-latex-p% nil
  168. "Is latex preview toggled")
  169. (define-obsolete-variable-alias 'ein:notebook 'ein:%notebook% "0.1.2")
  170. ;;; Constructor
  171. (defun ein:notebook-new (url-or-port notebook-path pre-kernelspec &rest args)
  172. (let ((kernelspec
  173. (cond ((ein:$kernelspec-p pre-kernelspec) pre-kernelspec)
  174. ((consp pre-kernelspec)
  175. (cl-loop for (_name ks) on (ein:need-kernelspecs url-or-port) by 'cddr
  176. when (and (ein:$kernelspec-p ks)
  177. (string= (cdr pre-kernelspec)
  178. (cl-struct-slot-value
  179. 'ein:$kernelspec (car pre-kernelspec) ks)))
  180. return ks))
  181. (t (ein:get-kernelspec url-or-port pre-kernelspec)))))
  182. (apply #'make-ein:$notebook
  183. :url-or-port url-or-port
  184. :kernelspec kernelspec
  185. :notebook-path notebook-path
  186. args)))
  187. ;;; Destructor
  188. (defun ein:notebook-del (notebook)
  189. "Destructor for `ein:$notebook'."
  190. (ein:log-ignore-errors
  191. (ein:kernel-del (ein:$notebook-kernel notebook))))
  192. (defun ein:notebook-close-worksheet (notebook ws)
  193. "Close worksheet WS in NOTEBOOK. If WS is the last worksheet,
  194. call notebook destructor `ein:notebook-del'."
  195. (cl-symbol-macrolet ((worksheets (ein:$notebook-worksheets notebook))
  196. (scratchsheets (ein:$notebook-scratchsheets notebook)))
  197. (cond
  198. ((ein:worksheet-p ws) (ein:worksheet-save-cells ws t))
  199. (t (setq scratchsheets (delq ws scratchsheets))))
  200. (unless (or (seq-filter (lambda (x)
  201. (and (not (eq x ws))
  202. (ein:worksheet-has-buffer-p x)))
  203. worksheets)
  204. scratchsheets)
  205. (ein:notebook-del notebook))))
  206. ;;; Notebook utility functions
  207. (defun ein:notebook-update-url-or-port (new-url-or-port notebook)
  208. "Change the url and port the notebook is saved to. Calling
  209. this will propagate the change to the kernel, trying to restart
  210. the kernel in the process. Use case for this command is when
  211. the jupyter server dies and restarted on a different port.
  212. If you have enabled token or password security on server running
  213. at the new url/port, then please be aware that this new url-port
  214. combo must match exactly these url/port you used format
  215. `ein:notebooklist-login'."
  216. (interactive (list
  217. (ein:notebooklist-ask-url-or-port)
  218. (ein:notebook--get-nb-or-error)))
  219. (message "Updating server info and restarting kernel for notebooklist %s"
  220. (ein:$notebook-notebook-name notebook))
  221. (setf (ein:$notebook-url-or-port notebook) new-url-or-port)
  222. (with-current-buffer (ein:notebook-buffer notebook)
  223. (ein:kernel-retrieve-session (ein:$notebook-kernel notebook))
  224. (rename-buffer (format ein:notebook-buffer-name-template
  225. (ein:$notebook-url-or-port notebook)
  226. (ein:$notebook-notebook-name notebook)))))
  227. (defun ein:notebook-buffer (notebook)
  228. "Return first buffer in NOTEBOOK's worksheets."
  229. (cl-loop for ws in (append (ein:$notebook-worksheets notebook)
  230. (ein:$notebook-scratchsheets notebook))
  231. if (ein:worksheet-buffer ws)
  232. return it))
  233. (defun ein:notebook-buffer-list (notebook)
  234. "Return the buffers associated with NOTEBOOK's kernel.
  235. The buffer local variable `default-directory' of these buffers
  236. will be updated with kernel's cwd."
  237. (delete nil
  238. (mapcar #'ein:worksheet-buffer
  239. (append (ein:$notebook-worksheets notebook)
  240. (ein:$notebook-scratchsheets notebook)))))
  241. (defun ein:notebook--get-nb-or-error ()
  242. (or ein:%notebook% (error "Not in notebook buffer.")))
  243. ;;;###autoload
  244. (defalias 'ein:notebook-name 'ein:$notebook-notebook-name)
  245. (defun ein:notebook-name-getter (notebook)
  246. (cons #'ein:notebook-name notebook))
  247. ;;; Open notebook
  248. (defun ein:notebook-url (notebook)
  249. (ein:notebook-url-from-url-and-id (ein:$notebook-url-or-port notebook)
  250. (ein:$notebook-api-version notebook)
  251. (ein:$notebook-notebook-path notebook)))
  252. (defun ein:notebook-url-from-url-and-id (url-or-port api-version path)
  253. (cond ((= api-version 2)
  254. (ein:url url-or-port "api/notebooks" path))
  255. ((>= api-version 3)
  256. (ein:url url-or-port "api/contents" path))))
  257. (defun ein:notebook-open--decorate-callback (notebook existing pending-clear callback no-pop)
  258. "In addition to CALLBACK, also clear the pending semaphore, pop-to-buffer the new notebook, save to disk the kernelspec metadata, and put last warning in minibuffer."
  259. (apply-partially
  260. (lambda (notebook* created callback* pending-clear* no-pop*)
  261. (funcall pending-clear*)
  262. (with-current-buffer (ein:notebook-buffer notebook*)
  263. (ein:worksheet-focus-cell))
  264. (unless no-pop*
  265. (with-current-buffer (ein:notebook-buffer notebook*)
  266. (if ein:polymode
  267. (progn
  268. (pm-select-buffer (pm-innermost-span))
  269. (pop-to-buffer (pm-span-buffer (pm-innermost-span))))
  270. (pop-to-buffer (ein:notebook-buffer notebook*)))))
  271. (when (and (not noninteractive)
  272. (null (plist-member (ein:$notebook-metadata notebook*) :kernelspec)))
  273. (ein:aif (ein:$notebook-kernelspec notebook*)
  274. (progn
  275. (setf (ein:$notebook-metadata notebook*)
  276. (plist-put (ein:$notebook-metadata notebook*)
  277. :kernelspec (ein:notebook--spec-insert-name
  278. (ein:$kernelspec-name it) (ein:$kernelspec-spec it))))
  279. (ein:notebook-save-notebook notebook*))))
  280. (when callback*
  281. (funcall callback* notebook* created))
  282. (ein:and-let* ((created)
  283. (buffer (get-buffer "*Warnings*"))
  284. (last-warning (with-current-buffer buffer
  285. (thing-at-point 'line t))))
  286. (message "%s" last-warning)))
  287. notebook (not existing) callback pending-clear no-pop))
  288. (defun ein:notebook-open-or-create (url-or-port path &optional kernelspec callback no-pop)
  289. "Same as `ein:notebook-open' but create PATH if not found."
  290. (let ((if-not-found (lambda (_contents _status-code) )))
  291. (ein:notebook-open url-or-port path kernelspec callback if-not-found no-pop)))
  292. ;;;###autoload
  293. (defun ein:notebook-jump-to-opened-notebook (notebook)
  294. "List all opened notebook buffers and switch to one that the user selects."
  295. (interactive
  296. (list (completing-read "Jump to notebook:" (ein:notebook-opened-buffer-names) nil t)))
  297. (switch-to-buffer notebook))
  298. ;;;###autoload
  299. (defun ein:notebook-open (url-or-port path &optional kernelspec callback errback no-pop)
  300. "Returns notebook at URL-OR-PORT/PATH.
  301. Note that notebook sends for its contents and won't have them right away.
  302. After the notebook is opened, CALLBACK is called as::
  303. \(funcall CALLBACK notebook created)
  304. where `created' indicates a new notebook or an existing one.
  305. "
  306. (interactive
  307. (ein:notebooklist-parse-nbpath (ein:notebooklist-ask-path "notebook")))
  308. (unless errback (setq errback #'ignore))
  309. (let* ((pending-key (cons url-or-port path))
  310. (pending-p (gethash pending-key *ein:notebook--pending-query*))
  311. (pending-clear (apply-partially (lambda (pending-key* &rest _args)
  312. (remhash pending-key*
  313. *ein:notebook--pending-query*))
  314. pending-key))
  315. (existing (ein:notebook-get-opened-notebook url-or-port path))
  316. (notebook (ein:aif existing it
  317. (ein:notebook-new url-or-port path kernelspec)))
  318. (callback0 (ein:notebook-open--decorate-callback notebook existing pending-clear
  319. callback no-pop)))
  320. (if existing
  321. (progn
  322. (ein:log 'info "Notebook %s is already open"
  323. (ein:$notebook-notebook-name notebook))
  324. (funcall callback0))
  325. (if (and pending-p noninteractive)
  326. (ein:log 'error "Notebook %s pending open!" path)
  327. (when (or (not pending-p)
  328. (y-or-n-p (format "Notebook %s pending open! Retry? " path)))
  329. (setf (gethash pending-key *ein:notebook--pending-query*) t)
  330. (add-function :before (var errback) pending-clear)
  331. (ein:content-query-contents url-or-port path
  332. (apply-partially #'ein:notebook-open--callback
  333. notebook callback0 (not no-pop))
  334. errback))))
  335. notebook))
  336. (defun ein:notebook-open--callback (notebook callback0 q-checkpoints content)
  337. (ein:log 'verbose "Opened notebook %s" (ein:$notebook-notebook-path notebook))
  338. (let ((_notebook-path (ein:$notebook-notebook-path notebook)))
  339. (ein:gc-prepare-operation)
  340. (setf (ein:$notebook-api-version notebook) (ein:$content-notebook-version content)
  341. (ein:$notebook-notebook-name notebook) (ein:$content-name content))
  342. (ein:notebook-bind-events notebook (ein:events-new))
  343. (ein:notebook-maybe-set-kernelspec notebook (plist-get (ein:$content-raw-content content) :metadata))
  344. (ein:notebook-install-kernel notebook)
  345. (ein:notebook-from-json notebook (ein:$content-raw-content content))
  346. (if (not (with-current-buffer (ein:notebook-buffer notebook)
  347. (ein:get-notebook)))
  348. (error "ein:notebook-open--callback: notebook instantiation failed")
  349. ;; Start websocket only after worksheet is rendered
  350. ;; because ein:notification-bind-events only gets called after worksheet's
  351. ;; buffer local notification widget is instantiated
  352. (ein:kernel-retrieve-session (ein:$notebook-kernel notebook) nil
  353. (apply-partially (lambda (callback0* name* _kernel)
  354. (funcall callback0*)
  355. (ein:log 'info "Notebook %s is ready" name*))
  356. callback0
  357. (ein:$notebook-notebook-name notebook)))
  358. (setf (ein:$notebook-kernelinfo notebook)
  359. (ein:kernelinfo-new (ein:$notebook-kernel notebook)
  360. (cons #'ein:notebook-buffer-list notebook)
  361. (symbol-name (ein:get-mode-for-kernel (ein:$notebook-kernelspec notebook)))))
  362. (ein:notebook-put-opened-notebook notebook)
  363. (ein:notebook--check-nbformat (ein:$content-raw-content content))
  364. (setf (ein:$notebook-q-checkpoints notebook) q-checkpoints)
  365. (ein:notebook-enable-autosaves notebook)
  366. (ein:gc-complete-operation))))
  367. (defun ein:notebook-maybe-set-kernelspec (notebook content-metadata)
  368. (ein:aif (plist-get content-metadata :kernelspec)
  369. (let ((kernelspec (ein:get-kernelspec (ein:$notebook-url-or-port notebook)
  370. (plist-get it :name))))
  371. (setf (ein:$notebook-kernelspec notebook) kernelspec))))
  372. (defun ein:notebook--different-number (n1 n2)
  373. (and (numberp n1) (numberp n2) (not (= n1 n2))))
  374. (defun ein:notebook--check-nbformat (data)
  375. "Warn user when nbformat is changed on server side.
  376. See https://github.com/ipython/ipython/pull/1934 for the purpose
  377. of minor mode."
  378. ;; See `Notebook.prototype.load_notebook_success'
  379. ;; at IPython/frontend/html/notebook/static/js/notebook.js
  380. (cl-destructuring-bind (&key nbformat orig_nbformat
  381. nbformat_minor orig_nbformat_minor
  382. &allow-other-keys)
  383. data
  384. (cond
  385. ((ein:notebook--different-number nbformat orig_nbformat)
  386. (ein:display-warning
  387. (format "Notebook major version updated (v%d -> v%d).
  388. To not update version, do not save this notebook."
  389. orig_nbformat nbformat)))
  390. ((ein:notebook--different-number nbformat_minor orig_nbformat_minor)
  391. (ein:display-warning
  392. (format "This notebook is version v%s.%s, but IPython
  393. server you are using only fully support up to v%s.%s.
  394. Some features may not be available."
  395. orig_nbformat orig_nbformat_minor
  396. nbformat nbformat_minor))))))
  397. ;;; Initialization.
  398. (defun ein:notebook-enable-autosaves (notebook)
  399. "Enable automatic, periodic saving for notebook."
  400. (interactive
  401. (list (or (ein:get-notebook)
  402. (ein:aand (ein:notebook-opened-buffer-names)
  403. (with-current-buffer (ein:completing-read
  404. "Notebook: " it nil t)
  405. (ein:get-notebook))))))
  406. (when (and (ein:$notebook-q-checkpoints notebook)
  407. (> ein:notebook-autosave-frequency 0))
  408. (setf (ein:$notebook-autosave-timer notebook)
  409. (run-at-time ein:notebook-autosave-frequency
  410. ein:notebook-autosave-frequency
  411. #'ein:notebook-maybe-save-notebook
  412. notebook))
  413. (ein:log 'verbose "Enabling autosaves for %s with frequency %s seconds."
  414. (ein:$notebook-notebook-name notebook)
  415. ein:notebook-autosave-frequency)))
  416. (defun ein:notebook-disable-autosaves (notebook)
  417. "Disable automatic, periodic saving for current notebook."
  418. (interactive
  419. (list (or (ein:get-notebook)
  420. (ein:aand (ein:notebook-opened-buffer-names)
  421. (with-current-buffer (ein:completing-read
  422. "Notebook: " it nil t)
  423. (ein:get-notebook))))))
  424. (if (and notebook (ein:$notebook-autosave-timer notebook))
  425. (progn
  426. (ein:log 'verbose "Disabling auto checkpoints for notebook %s" (ein:$notebook-notebook-name notebook))
  427. (cancel-timer (ein:$notebook-autosave-timer notebook)))))
  428. (defun ein:notebook-update-autosave-frequency (new-frequency notebook)
  429. "Change the autosaves frequency for the current notebook, or
  430. for a notebook selected by the user if not currently inside a
  431. notebook buffer."
  432. (interactive
  433. (list (read-number "New autosaves frequency (0 to disable): ")
  434. (or (ein:get-notebook)
  435. (ein:aand (ein:notebook-opened-buffer-names)
  436. (with-current-buffer (ein:completing-read
  437. "Notebook: " it nil t)
  438. (ein:get-notebook))))))
  439. (if notebook
  440. (progn
  441. (setq ein:notebook-autosave-frequency new-frequency)
  442. (ein:notebook-disable-autosaves notebook)
  443. (ein:notebook-enable-autosaves notebook))
  444. (message "Open notebook first")))
  445. (defun ein:notebook-bind-events (notebook events)
  446. "Bind events related to PAGER to the event handler EVENTS."
  447. (setf (ein:$notebook-events notebook) events)
  448. (ein:worksheet-class-bind-events events)
  449. ;; Bind events for sub components:
  450. (setf (ein:$notebook-pager notebook)
  451. (ein:pager-new
  452. (format ein:notebook-pager-buffer-name-template
  453. (ein:$notebook-url-or-port notebook)
  454. (ein:$notebook-notebook-name notebook))
  455. (ein:$notebook-events notebook))))
  456. (defalias 'ein:notebook-reconnect-kernel 'ein:notebook-reconnect-session-command "The distinction between kernel and session is a bit mysterious, all the action is now occurring in `ein:notebook-reconnect-session-command' these days, for which this function is now an alias.")
  457. (define-obsolete-function-alias
  458. 'ein:notebook-show-in-shared-output
  459. 'ein:shared-output-show-code-cell-at-point "0.1.2")
  460. (eval-when-compile (defvar outline-regexp))
  461. (declare-function px-preview "px" ())
  462. (defsubst ein:notebook-toggle-latex-fragment ()
  463. (interactive)
  464. (cond (ein:polymode (ein:display-warning "ein:notebook-toggle-latex-fragment: delegate to markdown-mode"))
  465. ((featurep 'px)
  466. (let ((outline-regexp "$a")
  467. (kill-buffer-hook nil)) ;; outline-regexp never matches to avoid headline
  468. (cl-letf (((symbol-function 'px-remove) #'ignore))
  469. (if ein:%notebook-latex-p%
  470. (progn
  471. (ein:worksheet-render (ein:worksheet--get-ws-or-error))
  472. (setq ein:%notebook-latex-p% nil))
  473. (px-preview)
  474. (setq ein:%notebook-latex-p% t)))))
  475. (t (ein:display-warning "px package not found"))))
  476. ;;; Kernel related things
  477. (defun ein:list-available-kernels (url-or-port)
  478. (let ((kernelspecs (ein:need-kernelspecs url-or-port)))
  479. (if kernelspecs
  480. (cl-sort (cl-loop for (_key spec) on (ein:plist-exclude kernelspecs '(:default)) by 'cddr
  481. collecting (cons (ein:$kernelspec-name spec)
  482. (ein:$kernelspec-display-name spec)))
  483. #'string< :key #'cdr))))
  484. (defun ein:notebook-switch-kernel (notebook kernel-name)
  485. "Change the kernel for a running notebook. If not called from a
  486. notebook buffer then the user will be prompted to select an opened notebook."
  487. (interactive
  488. (let* ((notebook (or (ein:get-notebook)
  489. (ein:completing-read
  490. "Select notebook: "
  491. (ein:notebook-opened-buffer-names))))
  492. (kernel-name (ein:completing-read
  493. "Select kernel: "
  494. (ein:list-available-kernels (ein:$notebook-url-or-port notebook)))))
  495. (list notebook kernel-name)))
  496. (let* ((kernelspec (ein:get-kernelspec
  497. (ein:$notebook-url-or-port notebook) kernel-name)))
  498. (setf (ein:$notebook-kernelspec notebook) kernelspec)
  499. (setf (ein:$notebook-metadata notebook)
  500. (plist-put (ein:$notebook-metadata notebook)
  501. :kernelspec (ein:notebook--spec-insert-name
  502. (ein:$kernelspec-name kernelspec) (ein:$kernelspec-spec kernelspec))))
  503. (ein:notebook-save-notebook notebook #'ein:notebook-kill-kernel-then-close-command
  504. (list notebook))
  505. (cl-loop repeat 10
  506. until (null (ein:$kernel-websocket (ein:$notebook-kernel notebook)))
  507. do (sleep-for 0 500)
  508. finally return (ein:notebook-open (ein:$notebook-url-or-port notebook)
  509. (ein:$notebook-notebook-path notebook)))))
  510. (defun ein:notebook-install-kernel (notebook)
  511. (let* ((base-url (concat ein:base-kernel-url "kernels"))
  512. (kernelspec (ein:$notebook-kernelspec notebook))
  513. (kernel (ein:kernel-new (ein:$notebook-url-or-port notebook)
  514. (ein:$notebook-notebook-path notebook)
  515. kernelspec
  516. base-url
  517. (ein:$notebook-events notebook)
  518. (ein:$notebook-api-version notebook))))
  519. (setf (ein:$notebook-kernel notebook) kernel)
  520. (when (eq (ein:get-mode-for-kernel (ein:$notebook-kernelspec notebook)) 'python)
  521. (ein:pytools-setup-hooks kernel notebook))))
  522. (defun ein:notebook-reconnect-session-command ()
  523. "It seems convenient but undisciplined to blithely create a new session if the original one no longer exists."
  524. (interactive)
  525. (ein:kernel-reconnect-session (ein:$notebook-kernel ein:%notebook%)))
  526. (defun ein:notebook-restart-session-command ()
  527. "Delete session on server side. Start new session."
  528. (interactive)
  529. (ein:aif ein:%notebook%
  530. (when (y-or-n-p "Are you sure you want to restart this session? ")
  531. (ein:kernel-restart-session (ein:$notebook-kernel it)))
  532. (message "Not in notebook buffer!")))
  533. (define-obsolete-function-alias
  534. 'ein:notebook-request-tool-tip-or-help-command
  535. 'ein:pytools-request-tooltip-or-help "0.1.2")
  536. (defun ein:notebook-ac-dot-complete ()
  537. "Insert dot and request completion."
  538. (interactive)
  539. (if (and (ein:get-notebook)
  540. (ein:codecell-p (ein:get-cell-at-point)))
  541. (call-interactively #'ein:ac-dot-complete)
  542. (insert ".")))
  543. (defun ein:notebook-kernel-interrupt-command ()
  544. "Interrupt the kernel.
  545. This is equivalent to do ``C-c`` in the console program."
  546. (interactive)
  547. (ein:kernel-interrupt (ein:$notebook-kernel ein:%notebook%)))
  548. ;; autoexec
  549. (defun ein:notebook-execute-autoexec-cells (notebook)
  550. "Execute cells of which auto-execution flag is on."
  551. (interactive (list (or ein:%notebook% (error "Not in notebook buffer!"))))
  552. (mapc #'ein:worksheet-execute-autoexec-cells
  553. (ein:$notebook-worksheets notebook)))
  554. (define-obsolete-function-alias
  555. 'ein:notebook-eval-string
  556. 'ein:shared-output-eval-string "0.1.2")
  557. ;;; Persistence and loading
  558. (defun ein:notebook-set-notebook-name (notebook name)
  559. "Check NAME and change the name of NOTEBOOK to it."
  560. (if (ein:notebook-test-notebook-name name)
  561. (setf (ein:$notebook-notebook-name notebook) name
  562. (ein:$notebook-notebook-id notebook) name)
  563. (ein:log 'error "%S is not a good notebook name." name)
  564. (error "%S is not a good notebook name." name)))
  565. (defun ein:notebook-test-notebook-name (name)
  566. (and (stringp name)
  567. (> (length name) 0)
  568. (not (string-match "[\\/\\\\:]" name))))
  569. (cl-defun ein:notebook--worksheet-new (notebook &optional (func #'ein:worksheet-new))
  570. (funcall func
  571. (ein:$notebook-nbformat notebook)
  572. (ein:notebook-name-getter notebook)
  573. (cons (lambda (notebook cell)
  574. (ein:notebook-discard-output-p notebook cell))
  575. notebook)
  576. (ein:$notebook-kernel notebook)
  577. (ein:$notebook-events notebook)))
  578. (defun ein:notebook--worksheet-render (notebook ws)
  579. (ein:worksheet-render ws)
  580. (with-current-buffer (ein:worksheet-buffer ws)
  581. (let (multilang-failed)
  582. (if ein:polymode
  583. (poly-ein-mode)
  584. ;; Changing major mode here is super dangerous as it
  585. ;; kill-all-local-variables.
  586. ;; Our saviour has been `ein:deflocal' which applies 'permanent-local
  587. ;; to variables assigned up to this point, but we ought not rely on it
  588. (funcall (ein:notebook-choose-mode))
  589. (ein:worksheet-reinstall-undo-hooks ws)
  590. (condition-case err
  591. (ein:aif (ein:$notebook-kernelspec notebook)
  592. (ein:ml-lang-setup it))
  593. (error (ein:log 'error (error-message-string err))
  594. (setq multilang-failed t))))
  595. (unless multilang-failed
  596. (ein:notebook-mode)
  597. (ein:notebook--notification-setup notebook)
  598. (ein:notebook-setup-kill-buffer-hook)
  599. (setq ein:%notebook% notebook)
  600. (when ein:polymode
  601. (poly-ein-fontify-buffer (ein:notebook-buffer notebook)))))))
  602. (defun ein:notebook--notification-setup (notebook)
  603. (ein:notification-setup
  604. (current-buffer)
  605. (ein:$notebook-events notebook)
  606. :get-list
  607. (lambda () (ein:$notebook-worksheets ein:%notebook%))
  608. :get-current
  609. (lambda () ein:%worksheet%)
  610. :get-name
  611. #'ein:worksheet-name
  612. :get-buffer
  613. (lambda (ws)
  614. (ein:notebook-worksheet--render-maybe ein:%notebook% ws "clicked")
  615. (ein:worksheet-buffer ws))
  616. :delete
  617. (lambda (ws)
  618. (ein:notebook-worksheet-delete ein:%notebook% ws t))
  619. :insert-prev
  620. (lambda (ws) (ein:notebook-worksheet-insert-prev ein:%notebook% ws))
  621. :insert-next
  622. (lambda (ws) (ein:notebook-worksheet-insert-next ein:%notebook% ws))
  623. :move-prev
  624. (lambda (ws) (ein:notebook-worksheet-move-prev ein:%notebook% ws))
  625. :move-next
  626. (lambda (ws) (ein:notebook-worksheet-move-next ein:%notebook% ws))
  627. ))
  628. (defun ein:notebook-set-buffer-file-name-maybe (_notebook)
  629. (ein:log 'warn "This function is deprecated. Who could be calling me?"))
  630. ;; (defun ein:notebook-set-buffer-file-name-maybe (notebook)
  631. ;; "Set `buffer-file-name' of the current buffer to ipynb file
  632. ;; of NOTEBOOK."
  633. ;; (when ein:notebook-set-buffer-file-name
  634. ;; (ein:notebook-fetch-data
  635. ;; notebook
  636. ;; (lambda (data notebook buffer)
  637. ;; (with-current-buffer buffer
  638. ;; (cl-destructuring-bind (&key project &allow-other-keys)
  639. ;; data
  640. ;; (setq buffer-file-name
  641. ;; (expand-file-name
  642. ;; (format "%s.ipynb"
  643. ;; (ein:$notebook-notebook-name notebook))
  644. ;; project)))))
  645. ;; (list notebook (current-buffer)))))
  646. (defun ein:notebook-from-json (notebook data)
  647. (cl-destructuring-bind (&key metadata nbformat nbformat_minor
  648. &allow-other-keys)
  649. data
  650. (setf (ein:$notebook-metadata notebook) metadata)
  651. (setf (ein:$notebook-nbformat notebook) nbformat)
  652. (setf (ein:$notebook-nbformat-minor notebook) nbformat_minor))
  653. (setf (ein:$notebook-worksheets notebook)
  654. (cl-case (ein:$notebook-nbformat notebook)
  655. (3 (ein:read-nbformat3-worksheets notebook data))
  656. (4 (ein:read-nbformat4-worksheets notebook data))
  657. (t (ein:log 'error "nbformat version %s unsupported"
  658. (ein:$notebook-nbformat notebook)))))
  659. (ein:notebook--worksheet-render notebook (car (ein:$notebook-worksheets notebook)))
  660. notebook)
  661. (defun ein:read-nbformat3-worksheets (notebook data)
  662. (mapcar (lambda (ws-data)
  663. (ein:worksheet-from-json
  664. (ein:notebook--worksheet-new notebook)
  665. ws-data))
  666. (or (plist-get data :worksheets)
  667. (list nil))))
  668. ;; nbformat4 gets rid of the concept of worksheets. That means, for the moment,
  669. ;; ein will no longer support worksheets. There may be a path forward for
  670. ;; reimplementing this feature, however. The nbformat 4 json definition says
  671. ;; that cells are allowed to have tags. Clever use of this feature may lead to
  672. ;; good things.
  673. (defun ein:read-nbformat4-worksheets (notebook data)
  674. "Convert a notebook in nbformat4 to a list of worksheet-like
  675. objects suitable for processing in ein:notebook-from-json."
  676. (let* ((cells (plist-get data :cells))
  677. (ws-cells (mapcar (lambda (data) (ein:cell-from-json data)) cells))
  678. (worksheet (ein:notebook--worksheet-new notebook)))
  679. (oset worksheet :saved-cells ws-cells)
  680. ;(mapcar (lambda (data) (message "test %s" (slot-value data 'metadata))) ws-cells)
  681. (list worksheet)))
  682. (defun ein:notebook-to-json (notebook)
  683. "Return json-ready alist."
  684. (let ((data
  685. (cl-case (ein:$notebook-nbformat notebook)
  686. (3 (ein:write-nbformat3-worksheets notebook))
  687. (4 (ein:write-nbformat4-worksheets notebook))
  688. (t (ein:log 'error "nbformat version %s unsupported"
  689. (ein:$notebook-nbformat notebook))))))
  690. ;; Apparently metadata can be either a hashtable or a plist...
  691. (let ((metadata (cdr (assq 'metadata data))))
  692. (if (hash-table-p metadata)
  693. (setf (gethash 'name metadata) (ein:$notebook-notebook-name notebook))
  694. (plist-put metadata
  695. :name (ein:$notebook-notebook-name notebook)))
  696. (ein:aif (ein:$notebook-nbformat-minor notebook)
  697. ;; Do not set nbformat when it is not given from server.
  698. (push `(nbformat_minor . ,it) data))
  699. (push `(nbformat . ,(ein:$notebook-nbformat notebook)) data)
  700. data)))
  701. (defun ein:write-nbformat3-worksheets (notebook)
  702. (let ((worksheets (mapcar #'ein:worksheet-to-json
  703. (ein:$notebook-worksheets notebook))))
  704. `((worksheets . ,(apply #'vector worksheets))
  705. (metadata . ,(ein:$notebook-metadata notebook))
  706. )))
  707. (defsubst ein:notebook--spec-insert-name (name spec)
  708. "Add kernel NAME, e.g., 'python2', to the kernelspec member of ipynb metadata."
  709. (if (plist-member spec :name)
  710. spec
  711. (plist-put spec :name name)))
  712. (defun ein:write-nbformat4-worksheets (notebook)
  713. (let ((all-cells (cl-loop for ws in (ein:$notebook-worksheets notebook)
  714. for i from 0
  715. append (ein:worksheet-to-nb4-json ws i))))
  716. ;; should be in notebook constructor, not here
  717. (ein:aif (ein:$notebook-kernelspec notebook)
  718. (setf (ein:$notebook-metadata notebook)
  719. (plist-put (ein:$notebook-metadata notebook)
  720. :kernelspec (ein:notebook--spec-insert-name
  721. (ein:$kernelspec-name it) (ein:$kernelspec-spec it)))))
  722. `((metadata . ,(ein:aif (ein:$notebook-metadata notebook)
  723. it
  724. (make-hash-table)))
  725. (cells . ,(apply #'vector all-cells)))))
  726. (defun ein:notebook-maybe-save-notebook (notebook &optional callback cbargs)
  727. (if (cl-some #'(lambda (ws)
  728. (buffer-modified-p
  729. (ein:worksheet-buffer ws)))
  730. (ein:$notebook-worksheets notebook))
  731. (ein:notebook-save-notebook notebook callback cbargs)))
  732. (defun ein:notebook-save-notebook (notebook &optional callback cbargs errback)
  733. (unless (ein:notebook-buffer notebook)
  734. (let ((buf (format ein:notebook-buffer-name-template
  735. (ein:$notebook-url-or-port notebook)
  736. (ein:$notebook-notebook-name notebook))))
  737. (ein:log 'error "ein:notebook-save-notebook: notebook %s has no buffer!" buf)
  738. (setf (ewoc--buffer (ein:worksheet--ewoc
  739. (car (ein:$notebook-worksheets notebook))))
  740. (get-buffer buf))))
  741. (condition-case err
  742. (with-current-buffer (ein:notebook-buffer notebook)
  743. (cl-letf (((symbol-function 'delete-trailing-whitespace) #'ignore))
  744. (run-hooks 'before-save-hook)))
  745. (error (ein:log 'warn "ein:notebook-save-notebook: Saving despite '%s'."
  746. (error-message-string err))))
  747. (let ((content (ein:content-from-notebook notebook)))
  748. (ein:events-trigger (ein:$notebook-events notebook)
  749. 'notebook_saving.Notebook)
  750. (ein:content-save content
  751. #'ein:notebook-save-notebook-success
  752. (list notebook callback cbargs)
  753. #'ein:notebook-save-notebook-error
  754. (list notebook errback))))
  755. (defun ein:notebook-save-notebook-command ()
  756. "Save the notebook."
  757. (interactive)
  758. (ein:notebook-save-notebook ein:%notebook%))
  759. (defun ein:notebook-save-notebook-success (notebook &optional callback cbargs)
  760. (ein:log 'verbose "Notebook is saved.")
  761. (setf (ein:$notebook-dirty notebook) nil)
  762. (mapc (lambda (ws)
  763. (ein:worksheet-save-cells ws) ; [#]_
  764. (ein:worksheet-set-modified-p ws nil))
  765. (ein:$notebook-worksheets notebook))
  766. (ein:events-trigger (ein:$notebook-events notebook)
  767. 'notebook_saved.Notebook)
  768. (when ein:notebook-create-checkpoint-on-save
  769. (ein:notebook-create-checkpoint notebook))
  770. (when callback
  771. (apply callback cbargs)))
  772. ;; .. [#] Consider the following case.
  773. ;; (1) Open worksheet WS0 and other worksheets.
  774. ;; (2) Edit worksheet WS0 then save the notebook.
  775. ;; (3) Edit worksheet WS0.
  776. ;; (4) Kill WS0 buffer by discarding the edit.
  777. ;; (5) Save the notebook.
  778. ;; This should save the latest WS0. To do so, WS0 at the point (2)
  779. ;; must be cached in the worksheet slot `:saved-cells'.
  780. (cl-defun ein:notebook-save-notebook-error (notebook &key symbol-status &allow-other-keys)
  781. (if (eq symbol-status 'user-cancel)
  782. (ein:log 'info "Cancel saving notebook.")
  783. (ein:log 'info "Failed to save notebook!")
  784. (ein:events-trigger (ein:$notebook-events notebook)
  785. 'notebook_save_failed.Notebook)))
  786. (defun ein:notebook-rename-command (path)
  787. "Rename current notebook and save it immediately.
  788. NAME is any non-empty string that does not contain '/' or '\\'."
  789. (interactive
  790. (list (read-string "Rename to: "
  791. (ein:$notebook-notebook-path ein:%notebook%))))
  792. (unless (and (string-match "\\.ipynb" path) (= (match-end 0) (length path)))
  793. (setq path (format "%s.ipynb" path)))
  794. (let* ((notebook (ein:notebook--get-nb-or-error))
  795. (content (ein:content-from-notebook notebook)))
  796. (ein:log 'verbose "Renaming notebook %s to '%s'" (ein:notebook-url notebook) path)
  797. (ein:content-rename content path #'ein:notebook-rename-success
  798. (list notebook content))))
  799. (defun ein:notebook-save-to-command (path)
  800. "Make a copy of the notebook and save it to a new path specified by NAME.
  801. NAME is any non-empty string that does not contain '/' or '\\'.
  802. "
  803. (interactive
  804. (list (read-string "Save copy to: " (ein:$notebook-notebook-path ein:%notebook%))))
  805. (unless (and (string-match ".ipynb" path) (= (match-end 0) (length path)))
  806. (setq path (format "%s.ipynb" path)))
  807. (let* ((content (ein:content-from-notebook ein:%notebook%))
  808. (name (substring path (or (cl-position ?/ path :from-end t) 0))))
  809. (setf (ein:$content-path content) path
  810. (ein:$content-name content) name)
  811. (ein:content-save content #'ein:notebook-open
  812. (list (ein:$notebook-url-or-port ein:%notebook%)
  813. path))))
  814. (cl-defun ein:notebook-rename-success (notebook content)
  815. (ein:notebook-remove-opened-notebook notebook)
  816. (ein:notebook-set-notebook-name notebook (ein:$content-name content))
  817. (setf (ein:$notebook-notebook-path notebook) (ein:$content-path content))
  818. (ein:notebook-put-opened-notebook notebook)
  819. (mapc #'ein:worksheet-set-buffer-name
  820. (append (ein:$notebook-worksheets notebook)
  821. (ein:$notebook-scratchsheets notebook)))
  822. (ein:and-let* ((kernel (ein:$notebook-kernel notebook)))
  823. (ein:session-rename (ein:$kernel-url-or-port kernel)
  824. (ein:$kernel-session-id kernel)
  825. (ein:$content-path content))
  826. (setf (ein:$kernel-path kernel) (ein:$content-path content)))
  827. (ein:log 'info "Notebook renamed to %s." (ein:$content-name content)))
  828. (defmacro ein:notebook-avoid-recursion (&rest body)
  829. `(let ((kill-buffer-query-functions
  830. (remove 'ein:notebook-kill-buffer-query kill-buffer-query-functions)))
  831. ,@body))
  832. (defun ein:notebook-kill-notebook-buffers (notebook)
  833. "Kill all of NOTEBOOK's buffers"
  834. (mapc #'ein:notebook-kill-current-buffer (ein:notebook-buffer-list notebook)))
  835. (defun ein:notebook-kill-current-buffer (buf)
  836. "Kill BUF and avoid recursion in kill-buffer-query-functions"
  837. (ein:notebook-avoid-recursion
  838. (let ((notebook (buffer-local-value 'ein:%notebook% buf)))
  839. (when (kill-buffer buf)
  840. (ein:notebook-tidy-opened-notebooks notebook)))))
  841. (defsubst ein:notebook-kill-buffer-query ()
  842. (ein:aif (ein:get-notebook--notebook)
  843. (let ((buf (or (buffer-base-buffer (current-buffer))
  844. (current-buffer))))
  845. (ein:notebook-ask-save it (apply-partially
  846. #'ein:notebook-kill-current-buffer
  847. buf))
  848. ;; don't kill buffer!
  849. nil)
  850. ;; kill buffer!
  851. t))
  852. (defun ein:notebook-ask-save (notebook callback0)
  853. (unless callback0
  854. (setq callback0 #'ignore))
  855. (if (and (ein:notebook-modified-p notebook)
  856. (not (ob-ein-anonymous-p (ein:$notebook-notebook-path notebook))))
  857. (if (y-or-n-p (format "Save %s?" (ein:$notebook-notebook-name notebook)))
  858. (let ((success-positive 0))
  859. (add-function :before (var callback0) (lambda () (setq success-positive 1)))
  860. (ein:notebook-save-notebook notebook callback0 nil
  861. (lambda () (setq success-positive -1)))
  862. (cl-loop repeat 10
  863. until (not (zerop success-positive))
  864. do (sleep-for 0 200)
  865. finally return (> success-positive 0)))
  866. (when (ein:worksheet-p ein:%worksheet%)
  867. (ein:worksheet-dont-save-cells ein:%worksheet%)) ;; TODO de-obfuscate
  868. (funcall callback0)
  869. t)
  870. (funcall callback0)
  871. t))
  872. (defun ein:notebook-close (notebook &optional callback &rest cbargs)
  873. (interactive (list (ein:notebook--get-nb-or-error)))
  874. (let* ((notebook (or notebook (ein:notebook--get-nb-or-error)))
  875. (callback0 (apply-partially #'ein:notebook-kill-notebook-buffers notebook)))
  876. (when callback
  877. (add-function :after (var callback0)
  878. (apply #'apply-partially callback cbargs)))
  879. (ein:notebook-ask-save notebook callback0)))
  880. (defun ein:notebook-kill-kernel-then-close-command (notebook)
  881. "Kill kernel and then kill notebook buffer.
  882. To close notebook without killing kernel, just close the buffer
  883. as usual."
  884. (interactive (list (ein:notebook--get-nb-or-error)))
  885. (let ((kernel (ein:$notebook-kernel notebook))
  886. (callback1 (apply-partially
  887. (lambda (notebook* _kernel)
  888. (ein:notebook-close notebook*))
  889. notebook)))
  890. (if (ein:kernel-live-p kernel)
  891. (ein:message-whir "Ending session"
  892. (add-function :before (var callback1) done-callback)
  893. (ein:kernel-delete-session kernel callback1))
  894. (funcall callback1 nil))))
  895. (defun ein:fast-content-from-notebook (notebook)
  896. "Quickly generate a basic content structure from notebook. This
  897. function does not generate the full json representation of the
  898. notebook worksheets."
  899. (make-ein:$content :name (ein:$notebook-notebook-name notebook)
  900. :path (ein:$notebook-notebook-path notebook)
  901. :url-or-port (ein:$notebook-url-or-port notebook)
  902. :type "notebook"
  903. :notebook-version (ein:$notebook-api-version notebook)))
  904. (defun ein:notebook-create-checkpoint (notebook)
  905. "Create checkpoint for current notebook based on most recent save."
  906. (interactive (list (ein:get-notebook)))
  907. (if (ein:$notebook-q-checkpoints notebook)
  908. (ein:content-create-checkpoint
  909. (ein:fast-content-from-notebook notebook)
  910. (let ((notebook notebook))
  911. #'(lambda (content)
  912. (ein:log 'verbose "Checkpoint %s for %s generated."
  913. (plist-get (car (ein:$content-checkpoints content)) :id)
  914. (ein:$notebook-notebook-name notebook))
  915. (setf (ein:$notebook-checkpoints notebook)
  916. (ein:$content-checkpoints content)))))))
  917. (defun ein:notebook-list-checkpoint-ids (notebook)
  918. (unless (ein:$notebook-checkpoints notebook)
  919. (ein:content-query-checkpoints (ein:fast-content-from-notebook notebook)
  920. (let ((notebook notebook))
  921. #'(lambda (content)
  922. (setf (ein:$notebook-checkpoints notebook)
  923. (ein:$content-checkpoints content)))))
  924. (sleep-for 0.5))
  925. (cl-loop for cp in (ein:$notebook-checkpoints notebook)
  926. collecting (plist-get cp :last_modified)))
  927. (defun ein:notebook-restore-to-checkpoint (notebook checkpoint)
  928. "Restore notebook to previous checkpoint saved on the Jupyter
  929. server. Note that if there are multiple checkpoints the user will
  930. be prompted on which one to use."
  931. (interactive
  932. (let* ((notebook (ein:get-notebook))
  933. (checkpoint (ein:completing-read
  934. "Select checkpoint: "
  935. (ein:notebook-list-checkpoint-ids notebook))))
  936. (list notebook checkpoint)))
  937. (ein:content-restore-checkpoint (ein:fast-content-from-notebook notebook)
  938. checkpoint)
  939. (ein:notebook-close notebook)
  940. (ein:notebook-open (ein:$notebook-url-or-port notebook)
  941. (ein:$notebook-notebook-path notebook)))
  942. ;;; Worksheet
  943. (defmacro ein:notebook--worksheet-render-new (notebook type)
  944. "Create new worksheet of TYPE in NOTEBOOK."
  945. (let ((func (intern (format "ein:%s-new" type)))
  946. (slot (list (intern (format "ein:$notebook-%ss" type)) notebook)))
  947. `(let ((ws (ein:notebook--worksheet-new ,notebook #',func)))
  948. (setf ,slot (append ,slot (list ws)))
  949. (ein:notebook--worksheet-render ,notebook ws)
  950. ws)))
  951. (defun ein:notebook-worksheet-render-new (notebook)
  952. "Create new worksheet in NOTEBOOK."
  953. (ein:notebook--worksheet-render-new notebook worksheet))
  954. (defun ein:notebook-worksheet-open-next-or-new (notebook ws &optional show)
  955. "Open next worksheet. Create new if none.
  956. Try to open the worksheet to the worksheet WS using the function
  957. `ein:notebook-worksheet-open-next', open a new worksheet if not
  958. found.
  959. SHOW is a function to be called with the worksheet buffer if
  960. given."
  961. (interactive (list (ein:notebook--get-nb-or-error)
  962. (ein:worksheet--get-ws-or-error)
  963. #'switch-to-buffer))
  964. (let ((next (ein:notebook-worksheet-open-next notebook ws)))
  965. (unless next
  966. (ein:log 'info "Creating new worksheet...")
  967. (setq next (ein:notebook-worksheet-render-new notebook))
  968. (ein:log 'info "Creating new worksheet... Done."))
  969. (when show
  970. (funcall show (ein:worksheet-buffer next)))))
  971. (defun ein:notebook-worksheet-open-next-or-first (notebook ws &optional show)
  972. "Open next or first worksheet.
  973. Try to open the worksheet to the worksheet WS using the function
  974. `ein:notebook-worksheet-open-next', open the first worksheet if
  975. not found.
  976. SHOW is a function to be called with the worksheet buffer if
  977. given."
  978. (interactive (list (ein:notebook--get-nb-or-error)
  979. (ein:worksheet--get-ws-or-error)
  980. #'switch-to-buffer))
  981. (let ((next (ein:notebook-worksheet-open-next notebook ws)))
  982. (unless next
  983. (setq next (car (ein:$notebook-worksheets notebook))))
  984. (when show
  985. (funcall show (ein:worksheet-buffer next)))))
  986. (defun ein:notebook-worksheet-open-prev-or-last (notebook ws &optional show)
  987. "Open previous or last worksheet.
  988. See also `ein:notebook-worksheet-open-next-or-first' and
  989. `ein:notebook-worksheet-open-prev'."
  990. (interactive (list (ein:notebook--get-nb-or-error)
  991. (ein:worksheet--get-ws-or-error)
  992. #'switch-to-buffer))
  993. (let ((prev (ein:notebook-worksheet-open-prev notebook ws)))
  994. (unless prev
  995. (setq prev (car (last (ein:$notebook-worksheets notebook)))))
  996. (when show
  997. (funcall show (ein:worksheet-buffer prev)))))
  998. (cl-defun ein:notebook-worksheet--render-maybe
  999. (notebook ws &optional (adj "next"))
  1000. "Render worksheet WS of NOTEBOOK if it does not have buffer.
  1001. ADJ is a adjective to describe worksheet to be rendered."
  1002. (if (ein:worksheet-has-buffer-p ws)
  1003. (ein:log 'verbose "The worksheet already has a buffer.")
  1004. (ein:log 'verbose "Rendering %s worksheet..." adj)
  1005. (ein:notebook--worksheet-render notebook ws)
  1006. (ein:log 'verbose "Rendering %s worksheet... Done." adj)))
  1007. (cl-defun ein:notebook-worksheet--open-new
  1008. (notebook new &optional (adj "next") show)
  1009. "Open (possibly new) worksheet NEW of NOTEBOOK with SHOW function.
  1010. ADJ is a adjective to describe worksheet to be opened.
  1011. SHOW is a function to be called with worksheet buffer if given."
  1012. (when new
  1013. (ein:notebook-worksheet--render-maybe notebook new adj))
  1014. (when show
  1015. (cl-assert (ein:worksheet-p new) nil "No %s worksheet." adj)
  1016. (funcall show (ein:worksheet-buffer new))))
  1017. (defun ein:notebook-worksheet-open-next (notebook ws &optional show)
  1018. "Open next worksheet.
  1019. Search the worksheet after the worksheet WS, render it if it is
  1020. not yet, then return the worksheet. If there is no such
  1021. worksheet, return nil. Open the first worksheet if the worksheet
  1022. WS is an instance of `ein:scratchsheet'.
  1023. SHOW is a function to be called with the worksheet buffer if
  1024. given."
  1025. (interactive (list (ein:notebook--get-nb-or-error)
  1026. (ein:worksheet--get-ws-or-error)
  1027. #'switch-to-buffer))
  1028. (let ((next (if (ein:scratchsheet-p ws)
  1029. (car (ein:$notebook-worksheets notebook))
  1030. (cl-loop with worksheets = (ein:$notebook-worksheets notebook)
  1031. for current in worksheets
  1032. for next in (cdr worksheets)
  1033. when (eq current ws) return next))))
  1034. (ein:notebook-worksheet--open-new notebook next "next" show)
  1035. next))
  1036. (defun ein:notebook-worksheet-open-prev (notebook ws &optional show)
  1037. "Open previous worksheet.
  1038. See also `ein:notebook-worksheet-open-next'."
  1039. (interactive (list (ein:notebook--get-nb-or-error)
  1040. (ein:worksheet--get-ws-or-error)
  1041. #'switch-to-buffer))
  1042. (let ((prev (if (ein:scratchsheet-p ws)
  1043. (car (last (ein:$notebook-worksheets notebook)))
  1044. (cl-loop for (prev current) on (ein:$notebook-worksheets notebook)
  1045. when (eq current ws) return prev))))
  1046. (ein:notebook-worksheet--open-new notebook prev "previous" show)
  1047. prev))
  1048. (defun ein:notebook-worksheet-open-ith (notebook i &optional show)
  1049. "Open I-th (zero-origin) worksheet."
  1050. (let ((ws (nth i (ein:$notebook-worksheets notebook))))
  1051. (unless ws (error "No %s-th worksheet" (1+ i)))
  1052. (ein:notebook-worksheet--open-new notebook ws (format "%s-th" i) show)))
  1053. (defmacro ein:notebook-worksheet--defun-open-nth (n)
  1054. "Define a command to open N-th (one-origin) worksheet."
  1055. (cl-assert (and (integerp n) (> n 0)) t "Bad nth worksheet n=%s" n)
  1056. (let ((func (intern (format "ein:notebook-worksheet-open-%sth" n))))
  1057. `(defun ,func (notebook &optional show)
  1058. ,(format "Open %d-th worksheet." n)
  1059. (interactive (list (ein:notebook--get-nb-or-error)
  1060. #'switch-to-buffer))
  1061. (ein:notebook-worksheet-open-ith notebook ,(1- n) show))))
  1062. (defmacro ein:notebook-worksheet--defun-all-open-nth (min max)
  1063. `(progn
  1064. ,@(cl-loop for n from min to max
  1065. collect `(ein:notebook-worksheet--defun-open-nth ,n))))
  1066. (ein:notebook-worksheet--defun-all-open-nth 1 8)
  1067. (defun ein:notebook-worksheet-open-last (notebook &optional show)
  1068. "Open the last worksheet."
  1069. (interactive (list (ein:notebook--get-nb-or-error)
  1070. #'switch-to-buffer))
  1071. (let ((last (car (last (ein:$notebook-worksheets notebook)))))
  1072. (ein:notebook-worksheet--open-new notebook last "last" show)
  1073. last))
  1074. (defun ein:notebook-worksheet-insert-new (notebook ws &optional render show
  1075. inserter)
  1076. (let ((new (ein:notebook--worksheet-new notebook)))
  1077. (setf (ein:$notebook-worksheets notebook)
  1078. (funcall inserter (ein:$notebook-worksheets notebook) ws new))
  1079. (when (or render show)
  1080. (ein:notebook--worksheet-render notebook new))
  1081. (when show
  1082. (funcall show (ein:worksheet-buffer new)))
  1083. new))
  1084. (cl-defun ein:notebook-worksheet-insert-next
  1085. (notebook ws &optional (render t) (show #'switch-to-buffer))
  1086. "Insert a new worksheet after this worksheet and open it.
  1087. See also `ein:notebook-worksheet-insert-prev'.
  1088. .. The worksheet WS is searched in the worksheets slot of
  1089. NOTEBOOK and a newly created worksheet is inserted after WS.
  1090. Worksheet buffer is created when RENDER or SHOW is non-`nil'.
  1091. SHOW is a function which take a buffer."
  1092. (interactive (list (ein:notebook--get-nb-or-error)
  1093. (ein:worksheet--get-ws-or-error)))
  1094. (ein:notebook-worksheet-insert-new notebook ws render show
  1095. #'ein:list-insert-after))
  1096. (cl-defun ein:notebook-worksheet-insert-prev
  1097. (notebook ws &optional (render t) (show #'switch-to-buffer))
  1098. "Insert a new worksheet before this worksheet and open it.
  1099. See also `ein:notebook-worksheet-insert-next'."
  1100. (interactive (list (ein:notebook--get-nb-or-error)
  1101. (ein:worksheet--get-ws-or-error)))
  1102. (ein:notebook-worksheet-insert-new notebook ws render show
  1103. #'ein:list-insert-before))
  1104. (defun ein:notebook-worksheet-delete (notebook ws &optional confirm)
  1105. "Delete the current worksheet.
  1106. When used as a lisp function, delete worksheet WS from NOTEBOOk."
  1107. (interactive (list (ein:notebook--get-nb-or-error)
  1108. (ein:worksheet--get-ws-or-error)
  1109. t))
  1110. (when (and confirm
  1111. (not (y-or-n-p "Really remove this worksheet? There is no undo.")))
  1112. (error "Quit deleting the current worksheet."))
  1113. (setf (ein:$notebook-worksheets notebook)
  1114. (delq ws (ein:$notebook-worksheets notebook)))
  1115. (setf (ein:$notebook-dirty notebook) t)
  1116. (kill-buffer (ein:worksheet-buffer ws)))
  1117. (defun ein:notebook-worksheet-move-prev (notebook ws)
  1118. "Switch the current worksheet with the previous one."
  1119. (interactive (list (ein:notebook--get-nb-or-error)
  1120. (ein:worksheet--get-ws-or-error)))
  1121. (cl-assert (ein:worksheet-p ws) nil "Not worksheet.")
  1122. (setf (ein:$notebook-worksheets notebook)
  1123. (ein:list-move-left (ein:$notebook-worksheets notebook) ws)))
  1124. (defun ein:notebook-worksheet-move-next (notebook ws)
  1125. "Switch the current worksheet with the previous one."
  1126. (interactive (list (ein:notebook--get-nb-or-error)
  1127. (ein:worksheet--get-ws-or-error)))
  1128. (cl-assert (ein:worksheet-p ws) nil "Not worksheet.")
  1129. (setf (ein:$notebook-worksheets notebook)
  1130. (ein:list-move-right (ein:$notebook-worksheets notebook) ws)))
  1131. (cl-defun ein:notebook-worksheet-index
  1132. (&optional (notebook ein:%notebook%)
  1133. (ws ein:%worksheet%))
  1134. "Return an index of the worksheet WS in NOTEBOOK."
  1135. (cl-loop for i from 0
  1136. for ith-ws in (ein:$notebook-worksheets notebook)
  1137. when (eq ith-ws ws)
  1138. return i))
  1139. ;;; Scratch sheet
  1140. (defun ein:notebook-scratchsheet-render-new (notebook)
  1141. "Create new scratchsheet in NOTEBOOK."
  1142. (ein:notebook--worksheet-render-new notebook scratchsheet))
  1143. (defun ein:notebook-scratchsheet-open (notebook &optional new popup)
  1144. "Open \"scratch sheet\".
  1145. Open a new one when prefix argument is given.
  1146. Scratch sheet is almost identical to worksheet. However, EIN
  1147. will not save the buffer. Use this buffer like of normal IPython
  1148. console. Note that you can always copy cells into the normal
  1149. worksheet to save result."
  1150. (interactive (list (ein:notebook--get-nb-or-error)
  1151. current-prefix-arg
  1152. t))
  1153. (let ((ss (or (unless new
  1154. (car (ein:$notebook-scratchsheets notebook)))
  1155. (ein:notebook-scratchsheet-render-new notebook))))
  1156. (when popup
  1157. (pop-to-buffer (ein:worksheet-buffer ss)))
  1158. ss))
  1159. ;;; Opened notebooks
  1160. (defvar ein:notebook--opened-map (make-hash-table :test 'equal)
  1161. "A map: (URL-OR-PORT NOTEBOOK-ID) => notebook instance.")
  1162. (defun ein:notebook-get-opened-notebook (url-or-port path)
  1163. (gethash (list url-or-port path) ein:notebook--opened-map))
  1164. (defun ein:notebook-get-opened-buffer (url-or-port path)
  1165. (ein:aand (ein:notebook-get-opened-notebook url-or-port path)
  1166. (ein:notebook-buffer it)))
  1167. (defun ein:notebook-put-opened-notebook (notebook)
  1168. (puthash (list (ein:$notebook-url-or-port notebook)
  1169. (ein:$notebook-notebook-path notebook))
  1170. notebook
  1171. ein:notebook--opened-map))
  1172. (defun ein:notebook-tidy-opened-notebooks (notebook)
  1173. "Remove NOTEBOOK from ein:notebook--opened-map if it's not ein:notebook-live-p"
  1174. (unless (ein:notebook-live-p notebook)
  1175. (ein:notebook-remove-opened-notebook notebook)))
  1176. (defun ein:notebook-remove-opened-notebook (notebook)
  1177. (remhash (list (ein:$notebook-url-or-port notebook)
  1178. (ein:$notebook-notebook-path notebook))
  1179. ein:notebook--opened-map))
  1180. (defun ein:notebook-opened-notebooks (&optional predicate)
  1181. "Return list of opened notebook instances.
  1182. If PREDICATE is given, notebooks are filtered by PREDICATE.
  1183. PREDICATE is called with each notebook and notebook is included
  1184. in the returned list only when PREDICATE returns non-nil value."
  1185. (let ((notebooks (hash-table-values ein:notebook--opened-map)))
  1186. (if predicate
  1187. (seq-filter predicate notebooks)
  1188. notebooks)))
  1189. (defun ein:notebook-opened-buffers (&optional predicate)
  1190. "Return list of opened notebook buffers."
  1191. (mapcar #'ein:notebook-buffer (ein:notebook-opened-notebooks predicate)))
  1192. (defun ein:notebook-opened-buffer-names (&optional predicate)
  1193. "Return list of opened notebook buffer names.
  1194. If PREDICATE is given, the list is filtered by PREDICATE.
  1195. PREDICATE is called with the buffer name for each opened notebook."
  1196. (let ((notebooks (mapcar #'buffer-name (ein:notebook-opened-buffers))))
  1197. (if predicate
  1198. (seq-filter predicate notebooks)
  1199. notebooks)))
  1200. ;;; Generic getter
  1201. (defun ein:get-url-or-port--notebook ()
  1202. (when ein:%notebook% (ein:$notebook-url-or-port ein:%notebook%)))
  1203. (defun ein:get-notebook--notebook ()
  1204. ein:%notebook%)
  1205. (defun ein:get-kernel--notebook ()
  1206. (when (ein:$notebook-p ein:%notebook%)
  1207. (ein:$notebook-kernel ein:%notebook%)))
  1208. ;;; Predicate
  1209. (defun ein:notebook-live-p (notebook)
  1210. "Return non-`nil' if NOTEBOOK has live buffer."
  1211. (buffer-live-p (ein:notebook-buffer notebook)))
  1212. (defun ein:notebook-modified-p (&optional notebook)
  1213. "Return non-nil if NOTEBOOK is modified.
  1214. If NOTEBOOK is not given or nil then consider the notebook
  1215. associated with current buffer (if any)."
  1216. (unless notebook (setq notebook ein:%notebook%))
  1217. (and (ein:$notebook-p notebook)
  1218. (ein:notebook-live-p notebook)
  1219. (or (ein:$notebook-dirty notebook)
  1220. (cl-loop for ws in (ein:$notebook-worksheets notebook)
  1221. when (ein:worksheet-modified-p ws)
  1222. return t))))
  1223. ;;; Notebook mode
  1224. (defcustom ein:notebook-modes
  1225. '(ein:notebook-multilang-mode)
  1226. "Notebook modes to use \(in order of preference).
  1227. When the notebook is opened, mode in this value is checked one by one
  1228. and the first usable mode is used.
  1229. Available modes:
  1230. * `ein:notebook-multilang-mode'
  1231. * `ein:notebook-mumamo-mode'
  1232. * `ein:notebook-python-mode'
  1233. * `ein:notebook-plain-mode'
  1234. Examples:
  1235. Use MuMaMo if it is installed. Otherwise, use plain mode.
  1236. This is the old default setting::
  1237. (setq ein:notebook-modes '(ein:notebook-mumamo-mode ein:notebook-plain-mode))
  1238. Avoid using MuMaMo even when it is installed::
  1239. (setq ein:notebook-modes '(ein:notebook-plain-mode))
  1240. Use simple `python-mode' based notebook mode when MuMaMo is not installed::
  1241. (setq ein:notebook-modes '(ein:notebook-mumamo-mode ein:notebook-python-mode))
  1242. "
  1243. :type '(repeat (choice (const :tag "Multi-lang" ein:notebook-multilang-mode)
  1244. (const :tag "MuMaMo" ein:notebook-mumamo-mode)
  1245. (const :tag "Only Python" ein:notebook-python-mode)
  1246. (const :tag "Plain" ein:notebook-plain-mode)))
  1247. :group 'ein)
  1248. (defun ein:notebook-choose-mode ()
  1249. "Return usable (defined) notebook mode."
  1250. (autoload 'ein:notebook-multilang-mode "ein-multilang")
  1251. (cl-loop for mode in ein:notebook-modes
  1252. if (functionp mode)
  1253. return mode))
  1254. (defvar ein:notebook-mode-map (make-sparse-keymap))
  1255. (with-eval-after-load "ein-smartrep"
  1256. (ein:smartrep-config ein:notebook-mode-map))
  1257. (defmacro ein:notebook--define-key (keymap key defn)
  1258. "Ideally we could override just the keymap binding with a (string . wrapped) cons pair (as opposed to messing with the DEFN itself), but then describe-minor-mode unhelpfully shows ?? for the keymap commands."
  1259. `(progn
  1260. (when (functionp ,defn)
  1261. (add-function :around (symbol-function ,defn)
  1262. (lambda (f &rest args)
  1263. (poly-ein-base (apply f args)))))
  1264. (define-key ,keymap ,key ,defn)))
  1265. (let ((map ein:notebook-mode-map))
  1266. (ein:notebook--define-key map "\C-ci" 'ein:inspect-object)
  1267. (ein:notebook--define-key map "\C-c'" 'ein:edit-cell-contents)
  1268. (ein:notebook--define-key map "\C-c\C-c" 'ein:worksheet-execute-cell)
  1269. (ein:notebook--define-key map (kbd "M-RET") 'ein:worksheet-execute-cell-and-goto-next)
  1270. (ein:notebook--define-key map (kbd "<M-S-return>")
  1271. 'ein:worksheet-execute-cell-and-insert-below)
  1272. (ein:notebook--define-key map (kbd "C-c C-'") 'ein:worksheet-turn-on-autoexec)
  1273. (ein:notebook--define-key map "\C-c\C-e" 'ein:worksheet-toggle-output)
  1274. (ein:notebook--define-key map "\C-c\C-v" 'ein:worksheet-set-output-visibility-all)
  1275. (ein:notebook--define-key map "\C-c\C-l" 'ein:worksheet-clear-output)
  1276. (ein:notebook--define-key map (kbd "C-c C-S-l") 'ein:worksheet-clear-all-output)
  1277. (ein:notebook--define-key map (kbd "C-c C-;") 'ein:shared-output-show-code-cell-at-point)
  1278. (ein:notebook--define-key map "\C-c\C-k" 'ein:worksheet-kill-cell)
  1279. (ein:notebook--define-key map "\C-c\M-w" 'ein:worksheet-copy-cell)
  1280. (ein:notebook--define-key map "\C-c\C-w" 'ein:worksheet-copy-cell)
  1281. (ein:notebook--define-key map "\C-c\C-y" 'ein:worksheet-yank-cell)
  1282. (ein:notebook--define-key map "\C-c\C-a" 'ein:worksheet-insert-cell-above)
  1283. (ein:notebook--define-key map "\C-c\C-b" 'ein:worksheet-insert-cell-below)
  1284. (ein:notebook--define-key map "\C-c\C-t" 'ein:worksheet-toggle-cell-type)
  1285. (ein:notebook--define-key map "\C-cS" 'ein:worksheet-toggle-slide-type)
  1286. (ein:notebook--define-key map "\C-c\C-u" 'ein:worksheet-change-cell-type)
  1287. (ein:notebook--define-key map "\C-c\C-s" 'ein:worksheet-split-cell-at-point)
  1288. (ein:notebook--define-key map "\C-c\C-m" 'ein:worksheet-merge-cell)
  1289. (ein:notebook--define-key map "\C-c\C-n" 'ein:worksheet-goto-next-input)
  1290. (ein:notebook--define-key map "\C-c\C-p" 'ein:worksheet-goto-prev-input)
  1291. (ein:notebook--define-key map (kbd "C-<up>") 'ein:worksheet-goto-prev-input)
  1292. (ein:notebook--define-key map (kbd "C-<down>") 'ein:worksheet-goto-next-input)
  1293. (ein:notebook--define-key map (kbd "C-c <up>") 'ein:worksheet-move-cell-up)
  1294. (ein:notebook--define-key map (kbd "C-c <down>") 'ein:worksheet-move-cell-down)
  1295. (ein:notebook--define-key map (kbd "M-<up>") 'ein:worksheet-move-cell-up)
  1296. (ein:notebook--define-key map (kbd "M-<down>") 'ein:worksheet-move-cell-down)
  1297. (ein:notebook--define-key map "\C-c\C-h" 'ein:pytools-request-tooltip-or-help)
  1298. (ein:notebook--define-key map "\C-c\C-i" 'ein:completer-complete)
  1299. (ein:notebook--define-key map (kbd "C-c C-$") 'ein:tb-show)
  1300. (ein:notebook--define-key map "\C-c\C-x" nil)
  1301. (ein:notebook--define-key map "\C-c\C-x\C-l" 'ein:notebook-toggle-latex-fragment)
  1302. (ein:notebook--define-key map "\C-c\C-x\C-r" 'ein:notebook-restart-session-command)
  1303. (ein:notebook--define-key map "\C-c\C-r" 'ein:notebook-reconnect-session-command)
  1304. (ein:notebook--define-key map "\C-c\C-z" 'ein:notebook-kernel-interrupt-command)
  1305. (ein:notebook--define-key map "\C-c\C-q" 'ein:notebook-kill-kernel-then-close-command)
  1306. (ein:notebook--define-key map (kbd "C-c C-#") 'ein:notebook-close)
  1307. (ein:notebook--define-key map (kbd "C-:") 'ein:shared-output-eval-string)
  1308. (ein:notebook--define-key map "\C-c\C-f" 'ein:file-open)
  1309. (ein:notebook--define-key map "\C-c\C-o" 'ein:notebook-open)
  1310. (ein:notebook--define-key map "\C-x\C-s" 'ein:notebook-save-notebook-command)
  1311. (ein:notebook--define-key map "\C-x\C-w" 'ein:notebook-rename-command)
  1312. (define-key map "\M-." 'ein:pytools-jump-to-source-command)
  1313. (define-key map (kbd "C-c C-.") 'ein:pytools-jump-to-source-command)
  1314. (define-key map "\M-," 'ein:pytools-jump-back-command)
  1315. (define-key map (kbd "C-c C-,") 'ein:pytools-jump-back-command)
  1316. (ein:notebook--define-key map "\M-p" 'ein:worksheet-previous-input-history)
  1317. (ein:notebook--define-key map "\M-n" 'ein:worksheet-next-input-history)
  1318. (ein:notebook--define-key map (kbd "C-c C-/") 'ein:notebook-scratchsheet-open)
  1319. ;; Worksheets
  1320. (ein:notebook--define-key map (kbd "C-c !") 'ein:worksheet-rename-sheet)
  1321. (ein:notebook--define-key map (kbd "C-c {") 'ein:notebook-worksheet-open-prev-or-last)
  1322. (ein:notebook--define-key map (kbd "C-c }") 'ein:notebook-worksheet-open-next-or-first)
  1323. (ein:notebook--define-key map (kbd "C-c M-{") 'ein:notebook-worksheet-move-prev)
  1324. (ein:notebook--define-key map (kbd "C-c M-}") 'ein:notebook-worksheet-move-next)
  1325. (ein:notebook--define-key map (kbd "C-c +") 'ein:notebook-worksheet-insert-next)
  1326. (ein:notebook--define-key map (kbd "C-c M-+") 'ein:notebook-worksheet-insert-prev)
  1327. (ein:notebook--define-key map (kbd "C-c -") 'ein:notebook-worksheet-delete)
  1328. (ein:notebook--define-key map "\C-c1" 'ein:notebook-worksheet-open-1th)
  1329. (ein:notebook--define-key map "\C-c2" 'ein:notebook-worksheet-open-2th)
  1330. (ein:notebook--define-key map "\C-c3" 'ein:notebook-worksheet-open-3th)
  1331. (ein:notebook--define-key map "\C-c4" 'ein:notebook-worksheet-open-4th)
  1332. (ein:notebook--define-key map "\C-c5" 'ein:notebook-worksheet-open-5th)
  1333. (ein:notebook--define-key map "\C-c6" 'ein:notebook-worksheet-open-6th)
  1334. (ein:notebook--define-key map "\C-c7" 'ein:notebook-worksheet-open-7th)
  1335. (ein:notebook--define-key map "\C-c8" 'ein:notebook-worksheet-open-8th)
  1336. (ein:notebook--define-key map "\C-c9" 'ein:notebook-worksheet-open-last)
  1337. ;; Menu
  1338. (easy-menu-define ein:notebook-menu map "EIN Notebook Mode Menu"
  1339. `("EIN Notebook"
  1340. ("File"
  1341. ,@(ein:generate-menu
  1342. '(("Save notebook" ein:notebook-save-notebook-command)
  1343. ("Copy and rename notebook" ein:notebook-save-to-command)
  1344. ("Rename notebook" ein:notebook-rename-command)
  1345. ("Close notebook" ein:notebook-close)
  1346. ("Kill kernel then close notebook"
  1347. ein:notebook-kill-kernel-then-close-command))))
  1348. ("Edit"
  1349. ,@(ein:generate-menu
  1350. '(("Kill cell" ein:worksheet-kill-cell)
  1351. ("Copy cell" ein:worksheet-copy-cell)
  1352. ("Yank cell" ein:worksheet-yank-cell)
  1353. ("Insert cell above" ein:worksheet-insert-cell-above)
  1354. ("Insert cell below" ein:worksheet-insert-cell-below)
  1355. ("Toggle cell type" ein:worksheet-toggle-cell-type)
  1356. ("Toggle slide type" ein:worksheet-toggle-slide-type)
  1357. ("Change cell type" ein:worksheet-change-cell-type)
  1358. ("Split cell at point" ein:worksheet-split-cell-at-point)
  1359. ("Merge cell" ein:worksheet-merge-cell)
  1360. ("Go to next cell" ein:worksheet-goto-next-input)
  1361. ("Go to previous cell" ein:worksheet-goto-prev-input)
  1362. ("Move cell up" ein:worksheet-move-cell-up)
  1363. ("Move cell down" ein:worksheet-move-cell-down)
  1364. ("Dedent text in CELL" ein:worksheet-dedent-cell-text)
  1365. )))
  1366. ("Cell/Code"
  1367. ,@(ein:generate-menu
  1368. '(("Edit cell contents in dedicated buffer" ein:edit-cell-contents)
  1369. ("Execute cell" ein:worksheet-execute-cell
  1370. :active (ein:worksheet-at-codecell-p))
  1371. ("Execute cell and go to next"
  1372. ein:worksheet-execute-cell-and-goto-next
  1373. :active (ein:worksheet-at-codecell-p))
  1374. ("Execute cell and insert below"
  1375. ein:worksheet-execute-cell-and-insert-below
  1376. :active (ein:worksheet-at-codecell-p))
  1377. ("Execute all cells"
  1378. ein:worksheet-execute-all-cells)
  1379. ("Execute all cells above"
  1380. ein:worksheet-execute-all-cells-above)
  1381. ("Execute all cells below"
  1382. ein:worksheet-execute-all-cells-below)
  1383. ("Turn on auto execution flag" ein:worksheet-turn-on-autoexec
  1384. :active (ein:worksheet-at-codecell-p))
  1385. ("Evaluate code in minibuffer" ein:shared-output-eval-string)
  1386. ("Toggle instant cell execution mode" ein:iexec-mode)
  1387. ))
  1388. "---"
  1389. ,@(ein:generate-menu
  1390. '(("Toggle output visibility" ein:worksheet-toggle-output
  1391. :active (ein:worksheet-at-codecell-p))
  1392. ("Show all output"
  1393. ein:worksheet-set-output-visibility-all)
  1394. ("Discard output" ein:worksheet-clear-output
  1395. :active (ein:worksheet-at-codecell-p))
  1396. ("Discard all output" ein:worksheet-clear-all-output)
  1397. ("Show full output" ein:shared-output-show-code-cell-at-point
  1398. :active (ein:worksheet-at-codecell-p))
  1399. ("Traceback viewer" ein:tb-show)
  1400. ))
  1401. "---"
  1402. ,@(ein:generate-menu
  1403. '(("Show object help"
  1404. ein:pytools-request-tooltip-or-help)
  1405. ("Complete code" ein:completer-complete
  1406. :active (ein:worksheet-at-codecell-p))
  1407. ("Jump to definition" ein:pytools-jump-to-source-command)
  1408. ("Go back to the previous jump point"
  1409. ein:pytools-jump-back-command)
  1410. ("Previous input history"
  1411. ein:worksheet-previous-input-history)
  1412. ("Next input history"
  1413. ein:worksheet-next-input-history))))
  1414. ("Kernel"
  1415. ,@(ein:generate-menu
  1416. '(("Restart session" ein:notebook-restart-session-command)
  1417. ("Reconnect session" ein:notebook-reconnect-session-command)
  1418. ("Switch kernel" ein:notebook-switch-kernel)
  1419. ("Interrupt kernel" ein:notebook-kernel-interrupt-command))))
  1420. ("Worksheets [Experimental]"
  1421. ,@(ein:generate-menu
  1422. '(("Rename worksheet" ein:worksheet-rename-sheet)
  1423. ("Insert next worksheet"
  1424. ein:notebook-worksheet-insert-next)
  1425. ("Insert previous worksheet"
  1426. ein:notebook-worksheet-insert-prev)
  1427. ("Delete worksheet" ein:notebook-worksheet-delete)
  1428. ("Move worksheet left" ein:notebook-worksheet-move-prev)
  1429. ("Move worksheet right" ein:notebook-worksheet-move-next)
  1430. ))
  1431. "---"
  1432. ,@(ein:generate-menu
  1433. '(("Open previous worksheet"
  1434. ein:notebook-worksheet-open-prev)
  1435. ("Open previous or last worksheet"
  1436. ein:notebook-worksheet-open-prev-or-last)
  1437. ("Open next worksheet"
  1438. ein:notebook-worksheet-open-next)
  1439. ("Open next or first worksheet"
  1440. ein:notebook-worksheet-open-next-or-first)
  1441. ("Open next or new worksheet"
  1442. ein:notebook-worksheet-open-next-or-new)
  1443. ))
  1444. "---"
  1445. ,@(ein:generate-menu
  1446. (append
  1447. (cl-loop for n from 1 to 8
  1448. collect
  1449. (list
  1450. (format "Open %d-th worksheet" n)
  1451. (intern (format "ein:notebook-worksheet-open-%sth" n))))
  1452. '(("Open last worksheet" ein:notebook-worksheet-open-last)))))
  1453. ;; Misc:
  1454. ,@(ein:generate-menu
  1455. '(("Open regular IPython console" ein:console-open)
  1456. ("Open scratch sheet" ein:notebook-scratchsheet-open)
  1457. ("Toggle pseudo console mode" ein:pseudo-console-mode)
  1458. ))
  1459. ))
  1460. map)
  1461. (defcustom ein:enable-eldoc-support nil
  1462. "Enable experimental support for eldoc in notebook buffers.
  1463. Disabled by default, but if you want to help debug this feature set it to T and
  1464. watch the fireworks!"
  1465. :type 'boolean
  1466. :group 'ein)
  1467. (define-minor-mode ein:notebook-mode
  1468. "A mode for jupyter notebooks.
  1469. \\{ein:notebook-mode-map}
  1470. "
  1471. :init-value nil
  1472. :lighter " Notebook"
  1473. :keymap ein:notebook-mode-map
  1474. :group 'ein
  1475. ;; BODY contains code to execute each time the mode is enabled or disabled.
  1476. ;; It is executed after toggling the mode, and before running MODE-hook.
  1477. (when ein:notebook-mode
  1478. (cl-case ein:completion-backend
  1479. (ein:use-ac-backend
  1480. (ein:ac-install-backend)
  1481. (ein:notebook--define-key ein:notebook-mode-map "." 'ein:notebook-ac-dot-complete)
  1482. (auto-complete-mode))
  1483. (ein:use-company-backend
  1484. (add-to-list 'company-backends 'ein:company-backend)
  1485. (ein:notebook--define-key ein:notebook-mode-map "." nil)
  1486. (company-mode)))
  1487. (ein:aif ein:helm-kernel-history-search-key
  1488. (ein:notebook--define-key ein:notebook-mode-map it 'helm-ein-kernel-history))
  1489. (ein:aif ein:anything-kernel-history-search-key
  1490. (ein:notebook--define-key ein:notebook-mode-map it 'anything-ein-kernel-history))
  1491. (setq indent-tabs-mode nil) ;; Being T causes problems with Python code.
  1492. (when (and (featurep 'eldoc) ein:enable-eldoc-support)
  1493. (add-function :before-until (local 'eldoc-documentation-function)
  1494. #'ein:completer--get-eldoc-signature)
  1495. (eldoc-mode))
  1496. (ein:worksheet-imenu-setup)
  1497. (when ein:use-smartrep
  1498. (require 'ein-smartrep))))
  1499. ;; To avoid MuMaMo to discard `ein:notebook-mode', make it
  1500. ;; permanent local.
  1501. (put 'ein:notebook-mode 'permanent-local t)
  1502. (define-derived-mode ein:notebook-plain-mode fundamental-mode "EIN[plain]"
  1503. "IPython notebook mode without fancy coloring."
  1504. (font-lock-mode))
  1505. (define-derived-mode ein:notebook-python-mode python-mode "EIN[python]"
  1506. "Use `python-mode' for whole notebook buffer.")
  1507. (defun ein:notebook-open-in-browser (&optional print)
  1508. "Open current notebook in web browser.
  1509. When the prefix argument (``C-u``) is given, print page is opened.
  1510. Note that print page is not supported in IPython 0.12.1."
  1511. (interactive "P")
  1512. (let ((url (apply #'ein:url
  1513. (ein:$notebook-url-or-port ein:%notebook%)
  1514. (if (>= (ein:$notebook-api-version ein:%notebook%) 3)
  1515. "notebooks")
  1516. (ein:$notebook-notebook-path ein:%notebook%)
  1517. (if print (list "print")))))
  1518. (message "Opening %s in browser" url)
  1519. (browse-url url)))
  1520. (defun ein:notebook-fetch-data (notebook callback &optional cbargs)
  1521. "Fetch data in body tag of NOTEBOOK html page.
  1522. CALLBACK is called with a plist with data in the body tag as
  1523. the first argument and CBARGS as the rest of arguments."
  1524. (let ((url-or-port (ein:$notebook-url-or-port notebook))
  1525. (notebook-id (ein:$notebook-notebook-id notebook)))
  1526. (ein:query-singleton-ajax
  1527. (list 'notebook-fetch-data url-or-port notebook-id)
  1528. (ein:url url-or-port notebook-id)
  1529. :parser
  1530. (lambda ()
  1531. (list
  1532. :project
  1533. (ein:html-get-data-in-body-tag "data-project")
  1534. :base-project-url
  1535. (ein:html-get-data-in-body-tag "data-base-project-url")
  1536. :base-kernel-url
  1537. (ein:html-get-data-in-body-tag "data-base-kernel-url")
  1538. :read-only
  1539. (ein:html-get-data-in-body-tag "data-read-only")
  1540. :notebook-id
  1541. (ein:html-get-data-in-body-tag "data-notebook-id")))
  1542. :success
  1543. (apply-partially (cl-function
  1544. (lambda (callback cbargs &key data &allow-other-keys)
  1545. (apply callback data cbargs)))
  1546. callback cbargs))))
  1547. ;;; Buffer and kill hooks
  1548. (add-hook 'kill-buffer-query-functions 'ein:notebook-kill-buffer-query)
  1549. (defun ein:notebook-close-notebooks (&optional blithely)
  1550. "Used in `ein:jupyter-server-stop' and `kill-emacs-query-functions' hook."
  1551. (ein:aif (ein:notebook-opened-notebooks)
  1552. (if (and (cl-notevery #'identity (mapcar #'ein:notebook-close it))
  1553. (not blithely))
  1554. (y-or-n-p "Some notebooks could not be saved. Exit anyway?")
  1555. t)
  1556. t))
  1557. (add-hook 'kill-emacs-query-functions 'ein:notebook-close-notebooks t)
  1558. ;;;###autoload
  1559. (defalias 'ein:exit 'ein:quit)
  1560. ;;;###autoload
  1561. (defun ein:quit (&optional force)
  1562. "Close all notebooks and servers."
  1563. (interactive "P")
  1564. (ein:notebook-close-notebooks force)
  1565. (when (featurep 'ein-jupyter)
  1566. (ein:jupyter-server-stop force))) ; autoloaded
  1567. (defun ein:notebook-kill-buffer-callback ()
  1568. "Call notebook destructor. This function is called via `kill-buffer-hook'."
  1569. ;; TODO - it remains a bug that neither `ein:notebook-kill-buffer-callback'
  1570. ;; nor `ein:notebook-close' updates ein:notebook--opened-map
  1571. (ein:log 'debug "ein:notebook-kill-buffer-callback called")
  1572. (when (ein:$notebook-p ein:%notebook%)
  1573. (ein:notebook-disable-autosaves ein:%notebook%)
  1574. (ein:notebook-close-worksheet ein:%notebook% ein:%worksheet%)))
  1575. (defun ein:notebook-setup-kill-buffer-hook ()
  1576. "Add \"notebook destructor\" to `kill-buffer-hook'."
  1577. (add-hook 'kill-buffer-hook 'ein:notebook-kill-buffer-callback nil t))
  1578. (let* ((the-mode (ein:notebook-choose-mode))
  1579. (incompatible-func (lambda ()
  1580. (when (boundp 'undo-tree-incompatible-major-modes)
  1581. (nconc undo-tree-incompatible-major-modes
  1582. (list the-mode))))))
  1583. (unless (funcall incompatible-func)
  1584. (with-eval-after-load 'undo-tree
  1585. (funcall incompatible-func))))
  1586. (provide 'ein-notebook)
  1587. ;;; ein-notebook.el ends here