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.

1370 lines
56 KiB

  1. ;;; ein-worksheet.el --- Worksheet module
  2. ;; Copyright (C) 2012 Takafumi Arakaki
  3. ;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
  4. ;; Author: John M. Miller <millejoh at mac.com>
  5. ;; This file is NOT part of GNU Emacs.
  6. ;; ein-worksheet.el is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; ein-worksheet.el is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with ein-worksheet.el. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;;
  18. ;;; Code:
  19. (require 'eieio)
  20. (require 'ewoc)
  21. (require 'ein-core)
  22. (require 'ein-utils)
  23. (require 'ein-cell)
  24. (require 'ein-cell-edit) ; for `ein:src--ws'
  25. (require 'ein-kill-ring)
  26. (require 'poly-ein)
  27. ;;; Configuration
  28. ;; (define-obsolete-variable-alias
  29. ;; 'ein:notebook-enable-undo 'ein:worksheet-enable-undo "0.2.0")
  30. (defcustom ein:worksheet-enable-undo t
  31. "When non-`nil', allow undo of cell inputs only (as opposed to
  32. whole-cell operations such as killing, moving, executing cells).
  33. Changes to this variable only take effect for newly opened worksheets."
  34. :type 'boolean
  35. :group 'ein)
  36. (ein:deflocal buffer-local-enable-undo t
  37. "Buffer local variable activating undo accounting. Should not modify.")
  38. (ein:deflocal ein:%cell-lengths% '()
  39. "Buffer local variable with buffer-undo-list's current knowledge of cell lengths.")
  40. (ein:deflocal ein:%which-cell% '()
  41. "Buffer local variable one-to-one buffer-undo-list item to cell id.")
  42. (defsubst ein:worksheet--unique-enough-cell-id (cell)
  43. "Gets the first five characters of an md5sum. How far I can get without r collisions follow a negative binomial with p=1e-6 (should go pretty far)."
  44. (intern (substring (slot-value cell 'cell-id) 0 5)))
  45. (defun ein:worksheet--which-cell-hook (change-beg change-end prev-len)
  46. "Hook important for undo thats runs for everything we type (an after-change-functions hook).
  47. Normalize `buffer-undo-list' by removing extraneous details, and update the ein:%which-cell% ledger that associates changes in `buffer-undo-list' with individual cells."
  48. (when (and buffer-undo-list (listp buffer-undo-list))
  49. (setq buffer-undo-list (cl-delete-if (lambda (u) (or (numberp u) (and (consp u) (markerp (car u))))) buffer-undo-list))
  50. (let ((fill (- (length buffer-undo-list) (length ein:%which-cell%))))
  51. (if (< fill 0)
  52. (progn
  53. (ein:log 'debug "truncating %s to %s: %S -> %S" (length ein:%which-cell%) (length buffer-undo-list) ein:%which-cell% (nthcdr (- fill) ein:%which-cell%))
  54. (setq ein:%which-cell% (nthcdr (- fill) ein:%which-cell%)))
  55. (when (> fill 0)
  56. (let ((cell-id (ein:aif (ein:worksheet-get-current-cell :noerror t)
  57. (ein:worksheet--unique-enough-cell-id it) nil)))
  58. (let ((ein:log-print-level 5))
  59. (ein:log 'debug "which-cell (%s . %s) %s %S fill=%s" change-beg change-end cell-id (cl-subseq buffer-undo-list 0 fill) fill))
  60. (setq ein:%which-cell%
  61. (nconc (make-list fill cell-id) ein:%which-cell%))))))))
  62. (defun ein:worksheet--next-cell-start (cell)
  63. (let ((cell1 (ein:cell-next cell)))
  64. (if cell1
  65. (ein:worksheet--element-start cell1 :prompt)
  66. (ein:with-live-buffer (ein:cell-buffer cell) (point-max)))))
  67. (defun ein:worksheet--element-start (cell key &optional cached)
  68. (if cached
  69. (plist-get (nth 4 (plist-get ein:%cell-lengths% (slot-value cell 'cell-id))) key)
  70. (let ((node (ein:cell-element-get cell key (if (eq key :output) 0))))
  71. (if node
  72. (marker-position (ewoc-location node))
  73. (if (eq key :output)
  74. (ein:worksheet--element-start cell :footer))))))
  75. (defsubst ein:worksheet--saved-input-length (cell)
  76. (or (cl-fourth (plist-get ein:%cell-lengths% (slot-value cell 'cell-id))) 0))
  77. (defsubst ein:worksheet--prompt-length (cell &optional cached)
  78. (if cached
  79. (or (car (plist-get ein:%cell-lengths% (slot-value cell 'cell-id))) 0)
  80. ;; 1+ for newline
  81. (1+ (- (ein:worksheet--element-start cell :input)
  82. (ein:worksheet--element-start cell :prompt)))))
  83. (defsubst ein:worksheet--output-length (cell &optional cached)
  84. (if cached
  85. ;; 1 for when cell un-executed, there is still a newline
  86. (or (cadr (plist-get ein:%cell-lengths% (slot-value cell 'cell-id))) 1)
  87. (if (string= (slot-value cell 'cell-type) "code")
  88. (- (ein:worksheet--next-cell-start cell)
  89. (ein:worksheet--element-start cell :output))
  90. 0)))
  91. (defsubst ein:worksheet--total-length (cell &optional cached)
  92. (if cached
  93. (or (cl-third (plist-get ein:%cell-lengths% (slot-value cell 'cell-id))) 0)
  94. (- (ein:worksheet--next-cell-start cell)
  95. (ein:worksheet--element-start cell :prompt))))
  96. (defun ein:worksheet--update-cell-lengths (cell &optional saved-input-length)
  97. (setq ein:%cell-lengths% (plist-put ein:%cell-lengths% (slot-value cell 'cell-id)
  98. (list (ein:worksheet--prompt-length cell)
  99. (ein:worksheet--output-length cell)
  100. (ein:worksheet--total-length cell)
  101. (or saved-input-length (ein:worksheet--saved-input-length cell))))))
  102. ;; can use apply-partially instead here
  103. (defmacro hof-add (distance)
  104. "Return function that adds signed DISTANCE those undo elements. 'hof' refers to higher-order function,"
  105. `(lambda (u)
  106. (cond ((numberp u)
  107. (+ u ,distance))
  108. ((and (consp u) (numberp (car u)) (numberp (cdr u)))
  109. (cons (+ ,distance (car u))
  110. (+ ,distance (cdr u))))
  111. ((and (consp u) (stringp (car u)) (numberp (cdr u)))
  112. (cons (car u)
  113. (* (if (< (cdr u) 0) -1 1) (+ ,distance (abs (cdr u))))))
  114. ((and (consp u) (markerp (car u)))
  115. (let ((mp (marker-position (car u))))
  116. (if (not (null mp))
  117. (let* ((m (set-marker (make-marker) (+ ,distance mp) (marker-buffer (car u)))))
  118. (cons m (cdr u))) u)))
  119. ((and (consp u) (null (car u))
  120. (numberp (car (last u))) (numberp (cdr (last u))))
  121. (append (cl-subseq u 0 3)
  122. (cons (+ ,distance (car (last u)))
  123. (+ ,distance (cdr (last u))))))
  124. ((and (consp u) (eq (car u) 'apply)
  125. (numberp (nth 2 u)) (numberp (nth 3 u)))
  126. (append (cl-subseq u 0 2)
  127. (list (+ ,distance (nth 2 u)))
  128. (list (+ ,distance (nth 3 u)))
  129. (nthcdr 4 u)))
  130. (t u))))
  131. (defun ein:worksheet--get-ids-after (cell)
  132. (let ((cell0 cell) result)
  133. (while (ein:cell-next cell0)
  134. (setq result (plist-put result (ein:worksheet--unique-enough-cell-id (ein:cell-next cell0)) t))
  135. (setq cell0 (ein:cell-next cell0)))
  136. result))
  137. (defun ein:worksheet--jigger-undo-list (&optional change-cell-id)
  138. (if (/= (safe-length buffer-undo-list) (length ein:%which-cell%))
  139. (ein:log 'debug "jig %s to %s: %S %S" (length ein:%which-cell%) (length buffer-undo-list) buffer-undo-list ein:%which-cell%))
  140. (ein:and-let* ((old-cell-id (car change-cell-id))
  141. (new-cell-id (cdr change-cell-id))
  142. (changed-p (not (eq old-cell-id new-cell-id))))
  143. (when changed-p
  144. (setq ein:%which-cell% (-replace old-cell-id new-cell-id ein:%which-cell%))))
  145. (let ((fill (- (safe-length buffer-undo-list) (length ein:%which-cell%))))
  146. (if (> (abs fill) 1)
  147. (progn
  148. (let ((msg (format "Undo failure diagnostic %s %s | %s"
  149. buffer-undo-list ein:%which-cell% fill))
  150. (pm-allow-post-command-hook nil))
  151. (setq ein:worksheet-enable-undo nil)
  152. (ein:worksheet-undo-setup ein:%worksheet%)
  153. (when pm/polymode
  154. (dolist (b (eieio-oref pm/polymode '-buffers))
  155. (when (buffer-live-p b)
  156. (poly-ein-copy-state (ein:worksheet--get-buffer ein:%worksheet%) b))))
  157. (ein:display-warning msg :error)
  158. (when ein:debug
  159. (error "ein:worksheet--jigger-undo-list: aborting"))))
  160. (if (< fill 0)
  161. (setq ein:%which-cell% (nthcdr (- fill) ein:%which-cell%))
  162. (if (> fill 0)
  163. (setq ein:%which-cell%
  164. (nconc (make-list fill (car ein:%which-cell%))
  165. ein:%which-cell%))))))
  166. (cl-assert (= (safe-length buffer-undo-list) (length ein:%which-cell%))
  167. t
  168. "ein:worksheet--jigger-undo-list %d != %d"
  169. (safe-length buffer-undo-list) (length ein:%which-cell%)))
  170. (defun ein:worksheet--unshift-undo-list (cell &optional exogenous-input old-cell)
  171. "Adjust `buffer-undo-list' for adding CELL. Unshift in list parlance means prepending to list."
  172. (unless old-cell
  173. (setq old-cell cell))
  174. (when buffer-local-enable-undo
  175. (ein:with-live-buffer (ein:cell-buffer cell)
  176. (let* ((opl (ein:worksheet--prompt-length old-cell t))
  177. (ool (ein:worksheet--output-length old-cell t))
  178. (otl (ein:worksheet--total-length old-cell t))
  179. (npl (ein:worksheet--prompt-length cell))
  180. (nol (ein:worksheet--output-length cell))
  181. (ntl (ein:worksheet--total-length cell))
  182. (pdist (- npl opl))
  183. (odist (- nol ool))
  184. (has-output (memq :output (slot-value cell 'element-names)))
  185. (old-has-output (memq :output (slot-value old-cell 'element-names)))
  186. (converted-newline (if (eq has-output old-has-output) 0
  187. (if has-output -1 1)))
  188. (after-ids (ein:worksheet--get-ids-after cell))
  189. (func-same-cell (hof-add pdist))
  190. (func-after-cell (hof-add (if (zerop otl)
  191. ntl (+ pdist odist converted-newline))))
  192. lst)
  193. (ein:log 'debug "unsh trig=%s pdist=%s odist=%s otl=%s ntl=%s conv=%s"
  194. (ein:worksheet--unique-enough-cell-id cell) pdist odist otl ntl converted-newline)
  195. (ein:worksheet--jigger-undo-list
  196. (cons (ein:worksheet--unique-enough-cell-id old-cell)
  197. (ein:worksheet--unique-enough-cell-id cell)))
  198. (dolist (uc (cl-mapcar 'cons buffer-undo-list ein:%which-cell%))
  199. (let ((u (car uc))
  200. (cell-id (or (cdr uc) "")))
  201. (if (string= (ein:worksheet--unique-enough-cell-id cell) cell-id)
  202. (setq lst (nconc lst (list (funcall func-same-cell u))))
  203. (if (plist-member after-ids cell-id)
  204. (progn
  205. (ein:log 'debug "unsh adj %s %s" u cell-id)
  206. (setq lst (nconc lst (list (funcall func-after-cell u)))))
  207. (setq lst (nconc lst (list u)))))))
  208. (cl-assert (= (safe-length buffer-undo-list) (length lst)) t
  209. "ein:worksheet--unshift-undo-list %d != %d"
  210. (safe-length buffer-undo-list) (length lst))
  211. (setq buffer-undo-list lst)
  212. (ein:worksheet--update-cell-lengths cell exogenous-input)))))
  213. (defun ein:worksheet--calc-offset (u)
  214. "Return length of inserted (or uninserted) text corresponding to undo entry U."
  215. (cond ((and (consp u) (numberp (car u)) (numberp (cdr u)))
  216. (- (cdr u) (car u)))
  217. ((and (consp u) (stringp (car u)) (numberp (cdr u)))
  218. (- (length (car u))))
  219. (t 0)))
  220. (defun ein:worksheet--shift-undo-list (cell)
  221. "Adjust `buffer-undo-list' for deleting CELL. Shift in list parlance means removing the front."
  222. (when buffer-local-enable-undo
  223. (ein:with-live-buffer (ein:cell-buffer cell)
  224. (let* ((pdist (ein:worksheet--prompt-length cell))
  225. (odist (ein:worksheet--output-length cell))
  226. (sdist (ein:worksheet--saved-input-length cell))
  227. (after-ids (ein:worksheet--get-ids-after cell))
  228. (offset 0)
  229. lst wc)
  230. (ein:log 'debug "shft trig=%s pdist=%s odist=%s sdist=%s" (ein:worksheet--unique-enough-cell-id cell) pdist odist sdist)
  231. (ein:worksheet--jigger-undo-list)
  232. ;; Deletion of a less recent undo affects a more recent undo (arrow of time)
  233. ;; Since buffer-undo-list is ordered most to least recent, we must
  234. ;; reverse.
  235. (dolist (uc (nreverse (cl-mapcar 'cons buffer-undo-list ein:%which-cell%)))
  236. (let ((u (car uc))
  237. (cell-id (or (cdr uc) "")))
  238. (if (string= (ein:worksheet--unique-enough-cell-id cell) cell-id)
  239. (progn
  240. (setq offset (+ offset (ein:worksheet--calc-offset u)))
  241. (ein:log 'debug "shft del %s (%s) %s" u (ein:worksheet--calc-offset u) cell-id))
  242. (setq wc (nconc wc (list (cdr uc))))
  243. (if (plist-member after-ids cell-id)
  244. (progn
  245. (ein:log 'debug "shft adj %s %s" u cell-id)
  246. ;; 1 for when cell un-executed, there is still a newline
  247. (setq lst (nconc lst (list (funcall (hof-add (- (+ 1 offset pdist odist sdist))) u)))))
  248. (setq lst (nconc lst (list u)))))))
  249. (setq buffer-undo-list (nreverse lst))
  250. (setq ein:%which-cell% (nreverse wc))
  251. (ein:worksheet--jigger-undo-list)
  252. (cl-remprop 'ein:%cell-lengths% (slot-value cell 'cell-id))))))
  253. ;;; Class and variable
  254. (defvar ein:worksheet-buffer-name-template "*ein: %s/%s*")
  255. (ein:deflocal ein:%worksheet% nil
  256. "Buffer local variable to store an instance of `ein:worksheet'.")
  257. ;;; Initialization of object and buffer
  258. (defun ein:worksheet-new (nbformat get-notebook-name discard-output-p
  259. kernel events &rest args)
  260. (apply #'make-instance 'ein:worksheet
  261. :nbformat nbformat :get-notebook-name get-notebook-name
  262. :discard-output-p discard-output-p :kernel kernel :events events
  263. args))
  264. (cl-defmethod ein:worksheet-bind-events ((ws ein:worksheet))
  265. (with-slots (events) ws
  266. ;; Bind events for sub components:
  267. (mapc (lambda (cell) (setf (slot-value cell 'events) events))
  268. (ein:worksheet-get-cells ws))))
  269. (defun ein:worksheet-class-bind-events (events)
  270. "Binds event handlers which are not needed to be bound per instance."
  271. (ein:events-on events
  272. 'maybe_reset_undo.Worksheet
  273. (lambda (-ignore- cell)
  274. (ein:worksheet--unshift-undo-list cell)))
  275. (ein:events-on events 'set_next_input.Worksheet
  276. #'ein:worksheet--set-next-input)
  277. (ein:events-on events 'set_dirty.Worksheet #'ein:worksheet--set-dirty))
  278. (defun ein:worksheet--set-next-input (-ignore- data)
  279. (cl-destructuring-bind (&key cell text) data
  280. (ein:with-live-buffer (ein:cell-buffer cell)
  281. (ein:and-let* ((ws ein:%worksheet%)
  282. (new-cell
  283. (ein:worksheet-insert-cell-below ws 'code cell)))
  284. (ein:cell-set-text new-cell text)
  285. (setf (slot-value ws 'dirty) t)))))
  286. (defun ein:worksheet--set-dirty (-ignore- data)
  287. "Set dirty flag of worksheet in which CELL in DATA locates."
  288. (cl-destructuring-bind (&key value cell) data
  289. (ein:with-live-buffer (ein:cell-buffer cell)
  290. (ein:worksheet-set-modified-p ein:%worksheet% value))))
  291. (cl-defmethod ein:worksheet-notebook-name ((ws ein:worksheet))
  292. (ein:funcall-packed (ein:worksheet--notebook-name ws)))
  293. (cl-defmethod ein:worksheet-url-or-port ((ws ein:worksheet))
  294. (ein:kernel-url-or-port (ein:worksheet--kernel ws)))
  295. (cl-defmethod ein:worksheet-name ((ws ein:worksheet))
  296. (plist-get (ein:worksheet--metadata ws) :name))
  297. (cl-defmethod ein:worksheet-set-name ((ws ein:worksheet) name)
  298. "Set worksheet name.
  299. \(fn ws name)"
  300. (cl-assert (stringp name) nil "NAME must be a string. Got: %S" name)
  301. (setf (ein:worksheet--metadata ws) (plist-put (ein:worksheet--metadata ws) :name name)))
  302. (cl-defmethod ein:worksheet-full-name ((ws ein:worksheet))
  303. (let ((nb-name (ein:worksheet-notebook-name ws)))
  304. (ein:aif (ein:worksheet-name ws)
  305. (concat nb-name "/" it)
  306. nb-name)))
  307. (cl-defmethod ein:worksheet-buffer ((ws ein:worksheet))
  308. (ein:and-let* (((slot-boundp ws :ewoc))
  309. (ewoc (ein:worksheet--ewoc ws))
  310. (buffer (ewoc-buffer ewoc))
  311. ((buffer-live-p buffer)))
  312. buffer))
  313. (cl-defmethod ein:worksheet--buffer-name ((ws ein:worksheet))
  314. (format ein:worksheet-buffer-name-template
  315. (ein:worksheet-url-or-port ws)
  316. (ein:worksheet-full-name ws)))
  317. (cl-defmethod ein:worksheet--get-buffer ((ws ein:worksheet))
  318. (or (ein:worksheet-buffer ws)
  319. (with-current-buffer (generate-new-buffer (ein:worksheet--buffer-name ws))
  320. (let ((buffer-undo-list t))
  321. (setf (ein:worksheet--ewoc ws)
  322. (ein:ewoc-create 'ein:worksheet-pp
  323. (ein:propertize-read-only "\n")
  324. nil t))
  325. (current-buffer)))))
  326. (cl-defmethod ein:worksheet-set-buffer-name ((ws ein:worksheet))
  327. (ein:with-live-buffer (ein:worksheet-buffer ws)
  328. (dolist (b (or (and pm/polymode (eieio-oref pm/polymode '-buffers))
  329. (list (current-buffer))))
  330. (ein:with-live-buffer b
  331. (rename-buffer
  332. (let ((simple-name (ein:worksheet--buffer-name ein:%worksheet%)))
  333. (if (and pm/polymode (not (eq (pm-base-buffer) (current-buffer))))
  334. (let ((chunkmode (nth 3 (pm-innermost-span))))
  335. (format "%s[%s]" simple-name
  336. (replace-regexp-in-string
  337. "poly-\\|-mode" ""
  338. (symbol-name
  339. (pm--get-existing-mode (eieio-oref chunkmode 'mode)
  340. (eieio-oref chunkmode 'fallback-mode))))))
  341. simple-name)))))))
  342. (cl-defmethod ein:worksheet-set-modified-p ((ws ein:worksheet) dirty)
  343. (ein:with-live-buffer (ein:worksheet-buffer ws)
  344. (set-buffer-modified-p dirty))
  345. (setf (slot-value ws 'dirty) dirty))
  346. (cl-defmethod ein:worksheet-undo-setup ((ws ein:worksheet))
  347. (with-current-buffer (ein:worksheet--get-buffer ws)
  348. (setq buffer-local-enable-undo ein:worksheet-enable-undo)
  349. (let ((undo-binding (key-binding (kbd "C-/"))))
  350. (if buffer-local-enable-undo
  351. (unless (eq undo-binding 'undo)
  352. (setq buffer-local-enable-undo nil)
  353. (ein:display-warning-once (format "Disabling undo for %s" undo-binding)))))
  354. (ein:worksheet-reinstall-undo-hooks ws)
  355. (if buffer-local-enable-undo
  356. (progn
  357. (setq buffer-undo-list nil)
  358. (setq ein:%which-cell% nil)
  359. (setq ein:%cell-lengths% nil))
  360. (setq buffer-undo-list t))))
  361. (cl-defmethod ein:worksheet-reinstall-undo-hooks ((ws ein:worksheet))
  362. (with-current-buffer (ein:worksheet--get-buffer ws)
  363. (if buffer-local-enable-undo
  364. (add-hook 'after-change-functions 'ein:worksheet--which-cell-hook nil t)
  365. (remove-hook 'after-change-functions 'ein:worksheet--which-cell-hook t))))
  366. (cl-defmethod ein:worksheet-render ((ws ein:worksheet))
  367. (with-current-buffer (ein:worksheet--get-buffer ws)
  368. (setq ein:%worksheet% ws)
  369. (ein:worksheet-undo-setup ws)
  370. (let ((inhibit-read-only t)
  371. (ewoc (ein:worksheet--ewoc ws)))
  372. (let ((cells (ein:worksheet--saved-cells ws)))
  373. (if cells
  374. (let ((buffer-undo-list t))
  375. (mapc (lambda (c)
  376. (setf (slot-value c 'ewoc) ewoc)
  377. (ein:cell-enter-last c)
  378. (ein:worksheet--update-cell-lengths c (- (ein:cell-input-pos-max c)
  379. (ein:cell-input-pos-min c))))
  380. cells))
  381. (ein:worksheet-insert-cell-below ws 'code nil t))))
  382. (set-buffer-modified-p nil)
  383. (ein:worksheet-bind-events ws)
  384. (ein:worksheet-set-kernel ws)
  385. (ein:log 'info "Worksheet %s is ready" (ein:worksheet-full-name ws))))
  386. (defun ein:worksheet-pp (ewoc-data)
  387. (let ((path (ein:$node-path ewoc-data))
  388. (data (ein:$node-data ewoc-data)))
  389. (cl-case (car path)
  390. (cell (ein:cell-pp (cdr path) data)))))
  391. ;;; Persistance and loading
  392. (cl-defmethod ein:worksheet-from-json ((ws ein:worksheet) data)
  393. (cl-destructuring-bind (&key cells metadata &allow-other-keys) data
  394. (setf (slot-value ws 'metadata) metadata)
  395. (setf (slot-value ws 'saved-cells)
  396. (mapcar (lambda (data) (ein:cell-from-json data)) cells)))
  397. ws)
  398. (cl-defmethod ein:worksheet-from-cells ((ws ein:worksheet) cells)
  399. )
  400. (cl-defmethod ein:worksheet-to-json ((ws ein:worksheet))
  401. "Convert worksheet WS into JSON ready alist.
  402. It sets buffer internally so that caller doesn not have to set
  403. current buffer."
  404. (let* ((discard-output-p (ein:worksheet--discard-output-p ws))
  405. (cells (ein:with-possibly-killed-buffer (ein:worksheet-buffer ws)
  406. (mapcar (lambda (c)
  407. (ein:cell-to-json
  408. c (ein:funcall-packed discard-output-p c)))
  409. (ein:worksheet-get-cells ws)))))
  410. `((cells . ,(apply #'vector cells))
  411. ,@(ein:aand (ein:worksheet--metadata ws) `((metadata . ,it))))))
  412. (cl-defmethod ein:worksheet-to-nb4-json ((ws ein:worksheet) wsidx)
  413. (let* ((discard-output-p (slot-value ws 'discard-output-p))
  414. (cells (ein:with-possibly-killed-buffer (ein:worksheet-buffer ws)
  415. (mapcar (lambda (c)
  416. (ein:cell-to-nb4-json
  417. c wsidx (ein:funcall-packed discard-output-p c)))
  418. (ein:worksheet-get-cells ws)))))
  419. cells))
  420. (cl-defmethod ein:worksheet-save-cells ((ws ein:worksheet) &optional deactivate)
  421. "Save cells in worksheet buffer in cache before killing the buffer.
  422. .. warning:: After called with non-nil DEACTIVATE flag is given,
  423. cells in worksheet cannot be used anymore. Use only just
  424. before killing the buffer.
  425. You don't need to set current buffer to call this function.
  426. Do nothing when the worksheet WS has no buffer.
  427. If the `:dont-save-cells' slot is non-nil (meaning that
  428. `ein:worksheet-dont-save-cells' has been called), cells in the
  429. worksheet buffer are not saved. When the DEACTIVATE option is
  430. given, cached cells are deactivated instead of the cells in
  431. buffer. Calling this function unconditionally resets
  432. `:dont-save-cells' flag to nil to make caching work when the
  433. worksheet WS is reopened.
  434. \(fn ws deactivate)"
  435. (when (ein:worksheet-has-buffer-p ws)
  436. (unless (slot-value ws 'dont-save-cells)
  437. (let ((cells (ein:worksheet-get-cells ws)))
  438. (with-current-buffer (ein:worksheet-buffer ws)
  439. (mapc #'ein:cell-save-text cells))
  440. (when deactivate (mapc #'ein:cell-deactivate cells))
  441. (setf (slot-value ws 'saved-cells) cells)))
  442. (when deactivate
  443. (mapc #'ein:cell-deactivate (slot-value ws 'saved-cells))))
  444. (setf (slot-value ws 'dont-save-cells) nil))
  445. (cl-defmethod ein:worksheet-dont-save-cells ((ws ein:worksheet))
  446. "Turn on `:dont-save-cells' flag so that next call on
  447. `ein:worksheet-save-cells' actually do nothing.
  448. \(fn ws)"
  449. (setf (slot-value ws 'dont-save-cells) t))
  450. ;;; Cell indexing, retrieval, etc.
  451. (cl-defmethod ein:worksheet-cell-from-type ((ws ein:worksheet) type &rest args)
  452. "Create a cell of TYPE (symbol or string)."
  453. ;; FIXME: unify type of TYPE to symbol or string.
  454. (apply #'ein:cell-from-type
  455. (format "%s" type)
  456. :ewoc (slot-value ws 'ewoc)
  457. :events (slot-value ws 'events)
  458. args))
  459. (cl-defmethod ein:worksheet-get-cells ((ws ein:worksheet))
  460. (if (ein:worksheet-has-buffer-p ws)
  461. (let* ((ewoc (slot-value ws 'ewoc))
  462. (nodes (ewoc-collect ewoc
  463. (lambda (n) (ein:cell-node-p n 'prompt)))))
  464. (mapcar #'ein:$node-data nodes))
  465. (slot-value ws 'saved-cells)))
  466. (cl-defmethod ein:worksheet-ncells ((ws ein:worksheet))
  467. (length (ein:worksheet-get-cells ws)))
  468. (defun ein:worksheet-get-ewoc (&optional ws)
  469. (ein:aand (or ws ein:%worksheet%) (slot-value it 'ewoc)))
  470. (defun ein:worksheet-get-current-ewoc-node (&optional pos)
  471. (ein:aand (ein:worksheet-get-ewoc) (ewoc-locate it pos)))
  472. (defun ein:worksheet-get-nearest-cell-ewoc-node (&optional pos max cell-p)
  473. (ein:and-let* ((ewoc-node (ein:worksheet-get-current-ewoc-node pos)))
  474. ;; FIXME: can be optimized using the argument `max'
  475. (while (and ewoc-node
  476. (not (and (ein:cell-ewoc-node-p ewoc-node)
  477. (if cell-p
  478. (funcall cell-p
  479. (ein:cell-from-ewoc-node ewoc-node))
  480. t))))
  481. (setq ewoc-node (ewoc-next (slot-value ein:%worksheet% 'ewoc) ewoc-node)))
  482. ewoc-node))
  483. (cl-defun ein:worksheet-get-current-cell (&key pos noerror (cell-p #'ein:basecell-child-p))
  484. "Return a cell at POS. If POS is not given, it is assumed be the
  485. current cursor position. When the current buffer is not worksheet
  486. buffer or there is no cell in the current buffer, return `nil'."
  487. (let ((cell (ein:cell-from-ewoc-node
  488. (ein:worksheet-get-current-ewoc-node pos))))
  489. (if (funcall cell-p cell)
  490. cell
  491. (unless noerror
  492. (error "No cell of type %s found at current position." cell-p)))))
  493. (defun ein:worksheet-at-codecell-p ()
  494. (ein:worksheet-get-current-cell :noerror t :cell-p #'ein:codecell-p))
  495. (defun ein:worksheet-get-cells-in-region (beg end)
  496. (ein:clip-list (ein:aand ein:%worksheet% (ein:worksheet-get-cells it))
  497. (ein:worksheet-get-current-cell :pos beg)
  498. (ein:worksheet-get-current-cell :pos end)))
  499. (cl-defun ein:worksheet-get-cells-in-region-or-at-point
  500. (&key noerror (cell-p #'ein:basecell-child-p))
  501. (or (seq-filter cell-p
  502. (if (region-active-p)
  503. (ein:worksheet-get-cells-in-region (region-beginning)
  504. (region-end))
  505. (list (ein:worksheet-get-current-cell))))
  506. (unless noerror
  507. (error "Cell not found"))))
  508. ;;; Insertion and deletion of cells
  509. (defun ein:worksheet--get-ws-or-error ()
  510. (or ein:%worksheet% (error "Not in worksheet buffer.")))
  511. (defun ein:worksheet-focus-cell ()
  512. (ein:aand (ein:worksheet-get-current-cell :noerror t) (ein:cell-goto it)))
  513. (defun ein:worksheet-delete-cell (ws cell &optional focus)
  514. "Delete a cell. \(WARNING: no undo!)
  515. This command has no key binding because there is no way to undo
  516. deletion. Use kill to play on the safe side.
  517. If you really want use this command, you can do something like this
  518. \(but be careful when using it!)::
  519. \(define-key ein:notebook-mode-map \"\\C-c\\C-d\"
  520. 'ein:worksheet-delete-cell)"
  521. (interactive (list (ein:worksheet--get-ws-or-error)
  522. (ein:worksheet-get-current-cell)
  523. t))
  524. (ein:worksheet--shift-undo-list cell)
  525. (let ((inhibit-read-only t)
  526. (buffer-undo-list t)) ; disable undo recording
  527. (apply #'ewoc-delete
  528. (slot-value ws 'ewoc)
  529. (ein:cell-all-element cell)))
  530. (oset ws :dirty t)
  531. (when focus (ein:worksheet-focus-cell)))
  532. (defun ein:worksheet-kill-cell (ws cells &optional focus)
  533. "Kill (\"cut\") the cell at point or cells in region.
  534. Note that the kill-ring for cells is not shared with the default
  535. kill-ring of Emacs (kill-ring for texts)."
  536. (interactive (list (ein:worksheet--get-ws-or-error)
  537. (ein:worksheet-get-cells-in-region-or-at-point)
  538. t))
  539. (when cells
  540. (mapc (lambda (c)
  541. (ein:cell-save-text c)
  542. (ein:worksheet-delete-cell ws c)
  543. (ein:cell-deactivate c))
  544. cells)
  545. (ein:kill-new cells)
  546. (when focus
  547. (deactivate-mark)
  548. (ein:worksheet-focus-cell))))
  549. (defun ein:worksheet-copy-cell (cells)
  550. "Copy the cell at point. (Put the current cell into the kill-ring.)"
  551. (interactive
  552. (list (when (ein:worksheet--get-ws-or-error)
  553. (prog1 (ein:worksheet-get-cells-in-region-or-at-point)
  554. (deactivate-mark)))))
  555. (let ((cells (mapcar
  556. (lambda (c)
  557. (ein:cell-deactivate (ein:cell-copy c)))
  558. cells)))
  559. (ein:log 'info "%s cells are copied." (length cells))
  560. (ein:kill-new cells)))
  561. (defun ein:worksheet-insert-clone (ws cell pivot up)
  562. (let ((clone (ein:cell-copy cell)))
  563. ;; Cell can be from another buffer, so reset `ewoc'.
  564. (setf (ein:basecell--ewoc clone) (ein:worksheet--ewoc ws))
  565. (funcall (intern (concat "ein:worksheet-insert-cell-" up)) ws clone pivot)
  566. clone))
  567. (defun ein:worksheet-yank-cell (ws &optional n)
  568. "Insert (\"paste\") the latest killed cell.
  569. Prefixes are act same as the normal `yank' command."
  570. (interactive (list (ein:worksheet--get-ws-or-error)
  571. (let ((arg current-prefix-arg))
  572. (cond ((listp arg) 0)
  573. ((eq arg '-) -2)
  574. (t (1- arg))))))
  575. (let* ((cell (ein:worksheet-get-current-cell :noerror t)) ; can be nil
  576. (killed (ein:current-kill n)))
  577. (cl-loop for c in killed
  578. with last = cell
  579. do (setq last (ein:worksheet-insert-clone ws c last "below"))
  580. finally (ein:cell-goto last))))
  581. (defun ein:worksheet--node-positions (cell)
  582. (let ((result))
  583. (cl-loop for k in (slot-value cell 'element-names)
  584. do (setq result
  585. (plist-put result k
  586. (let* ((en-or-list (ein:cell-element-get cell k))
  587. (en (if (listp en-or-list) (nth 0 en-or-list) en-or-list)))
  588. (if en (marker-position (ewoc-location en)))))))
  589. result))
  590. (defun ein:worksheet-maybe-new-cell (ws type-or-cell)
  591. "Return TYPE-OR-CELL as-is if it is a cell, otherwise return a new cell."
  592. (let ((cell (if (cl-typep type-or-cell 'ein:basecell)
  593. type-or-cell
  594. (ein:worksheet-cell-from-type ws type-or-cell))))
  595. ;; When newly created or copied, kernel is not attached or not the
  596. ;; kernel of this worksheet. So reset it here.
  597. (when (cl-typep cell 'ein:codecell)
  598. (setf (slot-value cell 'kernel) (slot-value ws 'kernel)))
  599. (setf (slot-value cell 'events) (slot-value ws 'events))
  600. cell))
  601. (defun ein:worksheet-insert-cell-below (ws type-or-cell pivot &optional focus)
  602. "Insert cell below. Insert markdown cell instead of code cell
  603. when the prefix argument is given.
  604. When used as a lisp function, insert a cell of TYPE-OR-CELL just
  605. after PIVOT and return the new cell."
  606. (interactive (list (ein:worksheet--get-ws-or-error)
  607. (if current-prefix-arg 'markdown 'code)
  608. (ein:worksheet-get-current-cell :noerror t) ; can be nil
  609. t))
  610. (let ((cell (ein:worksheet-maybe-new-cell ws type-or-cell)))
  611. (cond
  612. ((= (ein:worksheet-ncells ws) 0)
  613. (ein:cell-enter-last cell))
  614. (pivot
  615. (ein:cell-insert-below pivot cell))
  616. (t (error
  617. "PIVOT is `nil' but ncells != 0. There is something wrong...")))
  618. (ein:worksheet--unshift-undo-list cell (- (ein:cell-input-pos-max cell)
  619. (ein:cell-input-pos-min cell)))
  620. (oset ws :dirty t)
  621. (when focus (ein:cell-goto cell))
  622. cell))
  623. (defun ein:worksheet-insert-cell-above (ws type-or-cell pivot &optional focus)
  624. "Insert cell above. Insert markdown cell instead of code cell
  625. when the prefix argument is given.
  626. See also: `ein:worksheet-insert-cell-below'."
  627. (interactive (list (ein:worksheet--get-ws-or-error)
  628. (if current-prefix-arg 'markdown 'code)
  629. (ein:worksheet-get-current-cell :noerror t) ; can be nil
  630. t))
  631. (let ((cell (ein:worksheet-maybe-new-cell ws type-or-cell)))
  632. (cond
  633. ((< (ein:worksheet-ncells ws) 2)
  634. (ein:cell-enter-first cell))
  635. (pivot
  636. (let ((prev-cell (ein:cell-prev pivot)))
  637. (if prev-cell
  638. (ein:cell-insert-below prev-cell cell)
  639. (ein:cell-enter-first cell))))
  640. (t (error
  641. "PIVOT is `nil' but ncells > 0. There is something wrong...")))
  642. (ein:worksheet--unshift-undo-list cell (- (ein:cell-input-pos-max cell)
  643. (ein:cell-input-pos-min cell)))
  644. (oset ws :dirty t)
  645. (when focus (ein:cell-goto cell))
  646. cell))
  647. (defun ein:worksheet-toggle-cell-type (ws cell &optional focus)
  648. "Toggle the cell type of the cell at point.
  649. Use `ein:worksheet-change-cell-type' to change the cell type
  650. directly."
  651. (interactive (list (ein:worksheet--get-ws-or-error)
  652. (ein:worksheet-get-current-cell)
  653. t))
  654. (let ((type (cl-case (slot-value ws 'nbformat)
  655. (2 (ein:case-equal (slot-value cell 'cell-type)
  656. (("code") "markdown")
  657. (("markdown") "code")))
  658. (3 (ein:case-equal (slot-value cell 'cell-type)
  659. (("code") "markdown")
  660. (("markdown") "raw")
  661. (("raw") "heading")
  662. (("heading") "code")))
  663. (4 (ein:case-equal (slot-value cell 'cell-type)
  664. (("code") "markdown")
  665. (("markdown") "raw")
  666. (("raw") "code"))))))
  667. (let ((relpos (ein:cell-relative-point cell))
  668. (new (ein:cell-convert-inplace cell type)))
  669. (when (ein:codecell-p new)
  670. (setf (slot-value new 'kernel) (slot-value ws 'kernel))
  671. (setf (slot-value new 'events) (slot-value ws 'events)))
  672. (when focus (ein:cell-goto new relpos))
  673. (ein:worksheet--unshift-undo-list new nil cell))))
  674. (defun ein:worksheet-toggle-slide-type (ws cell &optional focus)
  675. "Toggle the slide metadata of the cell at point. Available slide settings are:
  676. [slide, subslide, fragment, skip, notes, - (none)]."
  677. (interactive (list (ein:worksheet--get-ws-or-error)
  678. (ein:worksheet-get-current-cell)
  679. t))
  680. (let ((new-slide-type (ein:case-equal (slot-value cell 'slidetype)
  681. (("-") "slide")
  682. (("slide") "subslide")
  683. (("subslide") "fragment")
  684. (("fragment") "skip")
  685. (("skip") "notes")
  686. (("notes") "-"))))
  687. (oset cell :slidetype new-slide-type))
  688. (ein:cell-invalidate-prompt cell)
  689. (ein:worksheet--unshift-undo-list cell)
  690. (when focus (ein:cell-goto cell)))
  691. (defun ein:worksheet-change-cell-type (ws cell type &optional level focus)
  692. "Change the cell type of the current cell.
  693. Prompt will appear in the minibuffer.
  694. When used in as a Lisp function, TYPE (string) should be chose
  695. from \"code\", \"hy-code\", \"markdown\", \"raw\" and \"heading\". LEVEL is
  696. an integer used only when the TYPE is \"heading\"."
  697. (interactive
  698. (let* ((ws (ein:worksheet--get-ws-or-error))
  699. (cell (ein:worksheet-get-current-cell))
  700. (choices (cl-case (slot-value ws 'nbformat)
  701. (2 "cm")
  702. (3 "cmr123456")
  703. (4 "chmr123456")))
  704. (key (ein:ask-choice-char
  705. (format "Cell type [%s]: " choices) choices))
  706. (type (cl-case key
  707. (?c "code")
  708. (?h "hy-code")
  709. (?m "markdown")
  710. (?r "raw")
  711. (t "heading")))
  712. (level (when (equal type "heading")
  713. (string-to-number (char-to-string key)))))
  714. (list ws cell type level t)))
  715. (let ((relpos (ein:cell-relative-point cell))
  716. (new (ein:cell-convert-inplace cell type)))
  717. (when (ein:codecell-p new)
  718. (setf (slot-value new 'kernel) (slot-value ws 'kernel)))
  719. (when level
  720. (ein:cell-change-level new level))
  721. (ein:worksheet--unshift-undo-list cell)
  722. (when focus (ein:cell-goto new relpos))))
  723. (defun ein:worksheet-split-cell-at-point (ws cell &optional no-trim focus)
  724. "Split cell at current position. Newlines at the splitting
  725. point will be removed. This can be omitted by giving a prefix
  726. argument \(C-u)."
  727. (interactive (list (ein:worksheet--get-ws-or-error)
  728. (ein:worksheet-get-current-cell)
  729. current-prefix-arg
  730. t))
  731. (let* ((beg (set-marker (make-marker) (ein:cell-input-pos-min cell)))
  732. (pos (point-marker))
  733. (head (buffer-substring beg pos))
  734. (new (ein:worksheet-insert-cell-above ws
  735. (slot-value cell 'cell-type)
  736. cell))
  737. )
  738. (when (ein:headingcell-p cell)
  739. (ein:cell-change-level new (slot-value cell 'level)))
  740. (undo-boundary)
  741. (delete-region beg pos)
  742. (unless no-trim
  743. (setq head (ein:trim-right head "\n"))
  744. (save-excursion
  745. (goto-char pos)
  746. (let ((end (set-marker (make-marker) (ein:cell-input-pos-max cell))))
  747. (while (and (looking-at-p "\n") (< (point) end))
  748. (delete-char 1)))))
  749. (ein:cell-set-text new head)
  750. (when focus (ein:cell-goto cell))))
  751. (defun ein:worksheet-merge-cell (ws cell &optional next focus)
  752. "Merge previous cell into current cell.
  753. If prefix is given, merge current cell into next cell."
  754. (interactive (list (ein:worksheet--get-ws-or-error)
  755. (ein:worksheet-get-current-cell)
  756. current-prefix-arg
  757. t))
  758. (unless next
  759. (setq cell (ein:cell-prev cell))
  760. (if cell
  761. (ein:cell-goto cell)
  762. (message "No previous cell")))
  763. (when cell
  764. (let* ((next-cell (ein:cell-next cell))
  765. (head (ein:cell-get-text cell)))
  766. (cl-assert next-cell nil "No cell to merge.")
  767. (ein:worksheet-delete-cell ws cell)
  768. (save-excursion
  769. (goto-char (ein:cell-input-pos-min next-cell))
  770. (insert head "\n"))
  771. (when focus (ein:cell-goto next-cell)))))
  772. ;;; Cell selection.
  773. (cl-defun ein:worksheet-next-input-cell (ewoc-node &optional up (nth 1))
  774. "Return a cell containing the next input node after EWOC-NODE.
  775. When UP is non-`nil', do the same for the *previous* input node.
  776. When NTH is specified, return NTH cell. Note that this function is
  777. *not* defined for NTH=0; it returns nil."
  778. (unless (= nth 0)
  779. (when (< nth 0)
  780. (setq nth (* nth -1))
  781. (setq up (not up)))
  782. (let ((cell (ein:worksheet-next-input-cell-1 ewoc-node up)))
  783. (cl-loop repeat (1- nth)
  784. with next = (if up #'ein:cell-prev #'ein:cell-next)
  785. if (funcall next cell)
  786. do (setq cell it)
  787. else
  788. return nil)
  789. cell)))
  790. (defun ein:worksheet-next-input-cell-1 (ewoc-node &optional up)
  791. (let* ((ewoc-data (ewoc-data ewoc-node))
  792. (cell (ein:$node-data ewoc-data))
  793. (path (ein:$node-path ewoc-data))
  794. (element (nth 1 path)))
  795. (if (memql element (if up '(output footer) '(prompt)))
  796. cell
  797. (funcall (if up #'ein:cell-prev #'ein:cell-next) cell))))
  798. (defun ein:worksheet-goto-input (ewoc-node up)
  799. (ein:aif (ein:worksheet-next-input-cell ewoc-node up)
  800. (ein:cell-goto it)
  801. (message "No %s input" (if up "previous" "next"))))
  802. (defun ein:worksheet-goto-next-input (ewoc-node)
  803. (interactive (list (and (ein:worksheet--get-ws-or-error)
  804. (ein:worksheet-get-current-ewoc-node))))
  805. (ein:worksheet-goto-input ewoc-node nil))
  806. (defun ein:worksheet-goto-prev-input (ewoc-node)
  807. (interactive (list (and (ein:worksheet--get-ws-or-error)
  808. (ein:worksheet-get-current-ewoc-node))))
  809. (ein:worksheet-goto-input ewoc-node t))
  810. (defun ein:worksheet-goto-next-cell-element (&optional nth up relpos prop)
  811. "Go to NTH next cell element named PROP and shift cursor by RELPOS.
  812. Go to previous cell if UP is t.
  813. Return t when the movement is succeeded."
  814. (unless prop (setq prop :input))
  815. (ein:and-let* ((current-node (ein:worksheet-get-current-ewoc-node))
  816. (current-cell (ein:cell-from-ewoc-node current-node))
  817. (target-cell
  818. (if (and (= nth 1)
  819. (eq (ein:cell-element-get current-cell :input)
  820. current-node)
  821. (not (and up
  822. (= (1+ (ewoc-location current-node))
  823. (point)))))
  824. current-cell
  825. (ein:worksheet-next-input-cell current-node up nth))))
  826. (ein:cell-goto target-cell relpos prop)
  827. t))
  828. (defun ein:worksheet-beginning-of-cell-input (&optional arg)
  829. "Move backward to the beginning of a cell.
  830. This function is for `beginning-of-defun-function', so behaves
  831. similarly with `beginning-of-defun'.
  832. It is set in `ein:notebook-multilang-mode'."
  833. (ein:worksheet-goto-next-cell-element (or arg 1) t))
  834. (defun ein:worksheet-end-of-cell-input (&optional arg)
  835. "Move forward to the end of a cell.
  836. This function is for `end-of-defun-function', so behaves
  837. similarly with `end-of-defun'.
  838. It is set in `ein:notebook-multilang-mode'."
  839. (ein:worksheet-goto-next-cell-element (or arg 1) nil 0 :after-input))
  840. ;;; Cell movement
  841. (defun ein:worksheet-move-cell (ws cell up)
  842. ;; effectively kill and yank modulo dirtying kill ring
  843. (ein:aif (if up (ein:cell-prev cell) (ein:cell-next cell))
  844. (let ((inhibit-read-only t)
  845. (pivot-cell it) clone)
  846. (ein:cell-save-text cell)
  847. (ein:worksheet-delete-cell ws cell)
  848. (ein:cell-deactivate cell)
  849. ;; the clone conveniently makes otl zero
  850. ;; (as opposed to ein:worksheet-insert-cell)
  851. (setq clone (ein:worksheet-insert-clone ws cell pivot-cell (if up "above" "below")))
  852. (ein:cell-goto clone)
  853. (oset ws :dirty t)
  854. (when pm/polymode
  855. (poly-ein-fontify-buffer (ein:worksheet--get-buffer ein:%worksheet%))))
  856. (message "No %s cell" (if up "previous" "next"))))
  857. (defun ein:worksheet-move-cell-up (ws cell)
  858. (interactive (list (ein:worksheet--get-ws-or-error)
  859. (ein:worksheet-get-current-cell)))
  860. (ein:worksheet-move-cell ws cell t))
  861. (defun ein:worksheet-move-cell-down (ws cell)
  862. (interactive (list (ein:worksheet--get-ws-or-error)
  863. (ein:worksheet-get-current-cell)))
  864. (ein:worksheet-move-cell ws cell nil))
  865. ;;; Cell collapsing and output clearing
  866. (defun ein:worksheet-toggle-output (ws cell)
  867. "Toggle the visibility of the output of the cell at point.
  868. This does not alter the actual data stored in the cell."
  869. (interactive (list (ein:worksheet--get-ws-or-error)
  870. (ein:worksheet-get-current-cell
  871. :cell-p #'ein:codecell-p)))
  872. (let ((buffer-undo-list t))
  873. (ein:cell-toggle-output cell)
  874. (setf (slot-value ws 'dirty) t))
  875. (ein:worksheet--unshift-undo-list cell))
  876. (defun ein:worksheet-set-output-visibility-all (ws &optional collapsed)
  877. "Show all cell output. When prefix is given, hide all cell output."
  878. (interactive (list (ein:worksheet--get-ws-or-error) current-prefix-arg))
  879. (when collapsed (setq collapsed t)) ; force it to be a boolean
  880. (mapc (lambda (c)
  881. (when (ein:codecell-p c)
  882. (let ((buffer-undo-list t))
  883. (ein:cell-set-collapsed c collapsed))
  884. (ein:worksheet--unshift-undo-list c)))
  885. (ein:worksheet-get-cells ws))
  886. (setf (slot-value ws 'dirty) t))
  887. (defun ein:worksheet-clear-output (cell &optional preserve-input-prompt)
  888. "Clear output from the current cell at point.
  889. Do not clear input prompt when the prefix argument is given."
  890. (interactive (list (ein:worksheet-get-current-cell
  891. :cell-p #'ein:codecell-p)
  892. current-prefix-arg))
  893. (ein:cell-clear-output cell t t t)
  894. (unless preserve-input-prompt
  895. (ein:cell-set-input-prompt cell))
  896. (ein:worksheet--unshift-undo-list cell))
  897. (defun ein:worksheet-clear-all-output (ws &optional preserve-input-prompt)
  898. "Clear output from all cells.
  899. Do not clear input prompts when the prefix argument is given."
  900. (interactive (list (ein:worksheet--get-ws-or-error) current-prefix-arg))
  901. (mapc (lambda (c) (ein:worksheet-clear-output c preserve-input-prompt))
  902. (seq-filter #'ein:codecell-p (ein:worksheet-get-cells ws))))
  903. ;;; Kernel related things
  904. (defun ein:worksheet-kernel-status (ws)
  905. "Report kernel status."
  906. (interactive (list (ein:worksheet--get-ws-or-error)))
  907. (let ((kernel (slot-value ws 'kernel)))
  908. (message "%s" (mapcan (lambda (slot)
  909. (let ((channel (funcall slot kernel)))
  910. (and channel
  911. (list (cons slot
  912. (websocket-ready-state
  913. (ein:$websocket-ws channel)))))))
  914. '(ein:$kernel-websocket
  915. ein:$kernel-shell-channel
  916. ein:$kernel-iopub-channel)))))
  917. (cl-defmethod ein:worksheet-set-kernel ((ws ein:worksheet))
  918. (mapc (lambda (cell) (setf (slot-value cell 'kernel) (slot-value ws 'kernel)))
  919. (seq-filter #'ein:codecell-p (ein:worksheet-get-cells ws))))
  920. (defun ein:worksheet-execute-cell (ws cell)
  921. "Execute code type CELL."
  922. (interactive (list (ein:worksheet--get-ws-or-error)
  923. (ein:worksheet-get-current-cell
  924. :cell-p #'ein:codecell-p)))
  925. (ein:kernel-when-ready (slot-value ws 'kernel)
  926. (apply-partially
  927. (lambda (ws* cell* kernel)
  928. (let ((buffer-undo-list t))
  929. (ein:cell-execute cell*)
  930. (oset ws* :dirty t))
  931. (ein:worksheet--unshift-undo-list cell*))
  932. ws cell))
  933. cell)
  934. (defun ein:worksheet-execute-cell-and-goto-next (ws cell &optional insert)
  935. "Execute cell at point if it is a code cell and move to the
  936. next cell, or insert if none."
  937. (interactive (list (ein:worksheet--get-ws-or-error)
  938. (ein:worksheet-get-current-cell)))
  939. (when (cl-typep cell 'ein:codecell)
  940. (ein:worksheet-execute-cell ws cell))
  941. (ein:aif (and (not insert) (ein:cell-next cell))
  942. (ein:cell-goto it)
  943. (ein:worksheet-insert-cell-below ws 'code cell t)))
  944. (defun ein:worksheet-execute-cell-and-insert-below (ws cell)
  945. "Execute cell at point if it is a code cell and insert a
  946. cell bellow."
  947. (interactive (list (ein:worksheet--get-ws-or-error)
  948. (ein:worksheet-get-current-cell)))
  949. (ein:worksheet-execute-cell-and-goto-next ws cell t))
  950. ;;; TODO add version number here before creating a new release
  951. (define-obsolete-function-alias
  952. 'ein:worksheet-execute-all-cell
  953. 'ein:worksheet-execute-all-cells)
  954. (defun ein:worksheet-execute-all-cells (ws)
  955. "Execute all cells in the current worksheet buffer."
  956. (interactive (list (ein:worksheet--get-ws-or-error)))
  957. (cl-loop for c in (ein:worksheet-get-cells ws)
  958. when (ein:codecell-p c)
  959. do (ein:cell-execute c)))
  960. (defun ein:worksheet-execute-all-cells-above (ws)
  961. "Execute all cells above the current cell (exclusively) in the
  962. current worksheet buffer."
  963. (interactive (list (ein:worksheet--get-ws-or-error)))
  964. (cl-loop with curr-cell-id = (ein:cell-id (ein:worksheet-get-current-cell))
  965. for c in (ein:worksheet-get-cells ws)
  966. until (equal (ein:cell-id c) curr-cell-id)
  967. when (ein:codecell-p c)
  968. do (ein:cell-execute c)))
  969. (defun ein:worksheet-execute-all-cells-below (ws)
  970. "Execute all cells below the current cell (inclusively) in the
  971. current worksheet buffer."
  972. (interactive (list (ein:worksheet--get-ws-or-error)))
  973. (cl-loop with curr-cell-id = (ein:cell-id (ein:worksheet-get-current-cell))
  974. and curr-cell-reached?
  975. for c in (ein:worksheet-get-cells ws)
  976. when (and (not curr-cell-reached?) (equal (ein:cell-id c) curr-cell-id))
  977. do (setq curr-cell-reached? t)
  978. when (and curr-cell-reached? (ein:codecell-p c))
  979. do (ein:cell-execute c)))
  980. (defun ein:worksheet-insert-last-input-history (ws cell index)
  981. "Insert INDEX-th previous history into CELL in worksheet WS."
  982. (ein:kernel-history-request
  983. (slot-value ws 'kernel)
  984. (list
  985. :history_reply
  986. (cons
  987. (lambda (cell content -metadata-not-used-)
  988. (cl-destructuring-bind (session line-number input)
  989. (car (plist-get content :history))
  990. (if (eq (ein:worksheet-get-current-cell) cell)
  991. (ein:cell-set-text cell input)
  992. (ein:log 'warning
  993. "Cursor moved from the cell after history request."))
  994. (ein:log 'info "Input history inserted: session:%d line:%d"
  995. session line-number)))
  996. cell))
  997. :hist-access-type "range"
  998. :session 0
  999. :start (- index)
  1000. :stop (- 1 index)))
  1001. (defvar ein:worksheet--history-index 1)
  1002. (defun ein:worksheet--get-history-index (inc)
  1003. "Increment history index by (possibly negative) INC.
  1004. Get history index for `ein:worksheet-previous-input-history' and
  1005. `ein:worksheet-next-input-history'. Raise error if caller tries
  1006. to decrement index to less than or equal to 1."
  1007. (if (or (eq last-command 'ein:worksheet-previous-input-history)
  1008. (eq last-command 'ein:worksheet-next-input-history))
  1009. (progn
  1010. (setq ein:worksheet--history-index
  1011. (+ ein:worksheet--history-index inc))
  1012. (when (< ein:worksheet--history-index 1)
  1013. (setq ein:worksheet--history-index 1)
  1014. (warn "This is the latest input"))
  1015. ein:worksheet--history-index)
  1016. (setq ein:worksheet--history-index 1)))
  1017. (defun ein:worksheet-previous-input-history (ws cell index)
  1018. "Insert the previous input in the execution history.
  1019. You can go back further in the history by repeating this command.
  1020. Use `ein:worksheet-next-input-history' to go forward in the
  1021. history."
  1022. (interactive (list (ein:worksheet--get-ws-or-error)
  1023. (ein:worksheet-get-current-cell)
  1024. (ein:worksheet--get-history-index +1)))
  1025. (ein:worksheet-insert-last-input-history ws cell index))
  1026. (defun ein:worksheet-next-input-history (ws cell index)
  1027. "Insert next input in the execution history.
  1028. You can go forward further in the history by repeating this
  1029. command. Use `ein:worksheet-previous-input-history' to go back
  1030. in the history."
  1031. (interactive (list (ein:worksheet--get-ws-or-error)
  1032. (ein:worksheet-get-current-cell)
  1033. (ein:worksheet--get-history-index -1)))
  1034. (ein:worksheet-insert-last-input-history ws cell index))
  1035. ;;; Metadata
  1036. (defun ein:worksheet-rename-sheet (ws name)
  1037. "Change worksheet name (*not* notebook name)."
  1038. (interactive (let ((ws (ein:worksheet--get-ws-or-error)))
  1039. (list ws
  1040. (read-from-minibuffer
  1041. "New worksheet name: " (ein:worksheet-name ws)))))
  1042. (unless (equal name (or (ein:worksheet-name ws) ""))
  1043. (ein:worksheet-set-name ws name)
  1044. (ein:worksheet-set-modified-p ws t)
  1045. (ein:worksheet-set-buffer-name ws)))
  1046. ;;; Generic getter
  1047. (defun ein:get-url-or-port--worksheet ()
  1048. (when (ein:worksheet-p ein:%worksheet%)
  1049. (ein:worksheet-url-or-port ein:%worksheet%)))
  1050. (defun ein:get-kernel--worksheet ()
  1051. (when (ein:worksheet-p ein:%worksheet%) (slot-value ein:%worksheet% 'kernel)))
  1052. ;; in edit-cell-mode, worksheet is bound as src--ws
  1053. ;; used by ein:get-kernel as a last option so completion, tooltips
  1054. ;; work in edit-cell-mode
  1055. (defun ein:get-kernel--worksheet-in-edit-cell ()
  1056. "Get kernel when in edit-cell-mode."
  1057. (when (ein:worksheet-p ein:src--ws) (slot-value ein:src--ws 'kernel)))
  1058. (defun ein:get-cell-at-point--worksheet ()
  1059. (ein:worksheet-get-current-cell :noerror t))
  1060. (defun ein:get-traceback-data--worksheet ()
  1061. (ein:aand (ein:get-cell-at-point--worksheet) (ein:cell-get-tb-data it)))
  1062. ;;; Predicate
  1063. (defun ein:worksheet-buffer-p ()
  1064. "Return non-`nil' if the current buffer is a worksheet buffer."
  1065. ein:%worksheet%)
  1066. (cl-defmethod ein:worksheet-has-buffer-p ((ws ein:worksheet))
  1067. (ein:aand (ein:worksheet-buffer ws) (buffer-live-p it)))
  1068. (cl-defmethod ein:worksheet-modified-p ((ws ein:worksheet))
  1069. (let ((buffer (ein:worksheet-buffer ws)))
  1070. (and (buffer-live-p buffer)
  1071. (or (slot-value ws 'dirty)
  1072. (buffer-modified-p buffer)))))
  1073. ;;; Utility commands
  1074. (defun ein:worksheet-dedent-cell-text (cell)
  1075. "Dedent text in CELL."
  1076. (interactive (list (ein:worksheet-get-current-cell)))
  1077. (let* ((beg (ein:cell-input-pos-min cell))
  1078. (end (ein:cell-input-pos-max cell)))
  1079. (indent-rigidly
  1080. beg end (- (ein:find-leftmot-column beg end)))))
  1081. (defun ein:worksheet--cells-before-cell (ws cell)
  1082. (let ((cells (ein:worksheet-get-cells ws)))
  1083. (cl-loop for c in cells
  1084. collecting c
  1085. until (eql (ein:cell-id c) (ein:cell-id cell)))))
  1086. (defun ein:worksheet--cells-after-cell (ws cell)
  1087. (let ((cells (ein:worksheet-get-cells ws))
  1088. (prior-cells (length (ein:worksheet--cells-before-cell ws cell))))
  1089. (seq-drop cells prior-cells)))
  1090. (defun ein:worksheet-first-executing-cell (cells)
  1091. (cl-loop for c in cells
  1092. when (and (ein:codecell-p c)
  1093. (slot-value c 'running))
  1094. return c))
  1095. (defun ein:worksheet-jump-to-first-executing-cell ()
  1096. "Move the point to the first executing cell in the current worksheet."
  1097. (interactive)
  1098. (ein:aif (ein:worksheet-first-executing-cell (ein:worksheet-get-cells ein:%worksheet%))
  1099. (ein:cell-goto it)
  1100. (message "No cell currently executing.")))
  1101. (defun ein:worksheet-jump-to-next-executing-cell ()
  1102. "Move the point to the next executing cell in the current worksheet."
  1103. (interactive)
  1104. (let* ((curcell (ein:get-cell-at-point--worksheet))
  1105. (restcells (ein:worksheet--cells-after-cell ein:%worksheet% curcell)))
  1106. (ein:aif (ein:worksheet-first-executing-cell restcells)
  1107. (ein:cell-goto it)
  1108. (message "No additional cells are executing."))))
  1109. ;;; Auto-execution
  1110. (defun ein:worksheet-toggle-autoexec (cell)
  1111. "Toggle auto-execution flag of the cell at point."
  1112. (interactive (list (ein:worksheet-get-current-cell #'ein:codecell-p)))
  1113. (ein:cell-toggle-autoexec cell))
  1114. (defun ein:worksheet-turn-on-autoexec (cells &optional off)
  1115. "Turn on auto-execution flag of the cells in region or cell at point.
  1116. When the prefix argument is given, turn off the flag instead.
  1117. To use autoexec feature, you need to turn on auto-execution mode
  1118. in connected buffers, using the `ein:connect-toggle-autoexec'
  1119. command."
  1120. (interactive
  1121. (list (ein:worksheet-get-cells-in-region-or-at-point
  1122. :cell-p #'ein:codecell-p)
  1123. current-prefix-arg))
  1124. (mapc (lambda (c) (ein:cell-set-autoexec c (not off))) cells)
  1125. (ein:log 'info "Turn %s auto-execution flag of %s cells."
  1126. (if off "off" "on")
  1127. (length cells)))
  1128. (defun ein:worksheet-execute-autoexec-cells (ws)
  1129. "Execute cells of which auto-execution flag is on.
  1130. This function internally sets current buffer to the worksheet
  1131. buffer, so you don't need to set current buffer to call this
  1132. function."
  1133. (interactive (list (ein:worksheet--get-ws-or-error)))
  1134. (ein:with-live-buffer (ein:worksheet-buffer ws)
  1135. (ein:kernel-when-ready
  1136. (slot-value ws 'kernel)
  1137. (apply-partially
  1138. (lambda (ws kernel)
  1139. (let ((buffer-undo-list t))
  1140. (mapc #'ein:cell-execute
  1141. (seq-filter #'ein:cell-autoexec-p
  1142. (ein:worksheet-get-cells ws)))))
  1143. ws))))
  1144. ;;; Imenu
  1145. (defun ein:worksheet-imenu-create-index ()
  1146. "`imenu-create-index-function' for notebook buffer."
  1147. ;; As Imenu does not provide the way to represent level *and*
  1148. ;; position, use #'s to do that.
  1149. (cl-loop for cell in (when (ein:worksheet-p ein:%worksheet%)
  1150. (seq-filter #'(lambda (cell) (or (ein:headingcell-p cell)
  1151. (ein:cell--markdown-heading-p cell)))
  1152. (ein:worksheet-get-cells ein:%worksheet%)))
  1153. for sharps = (if (ein:headingcell-p cell)
  1154. (cl-loop repeat (slot-value cell 'level) collect "#")
  1155. (cl-loop repeat(progn
  1156. (string-match "^#+" (ein:cell-get-text cell))
  1157. (match-end 0))
  1158. collect "#"))
  1159. for text = (ein:cell-get-text cell)
  1160. for name = (if (ein:headingcell-p cell)
  1161. (ein:join-str "" (append sharps (list " " text)))
  1162. text)
  1163. collect (cons name (ein:cell-input-pos-min cell))))
  1164. (defun ein:worksheet-imenu-setup ()
  1165. "Called via notebook mode hooks."
  1166. (setq imenu-create-index-function #'ein:worksheet-imenu-create-index))
  1167. ;;; Workarounds
  1168. (defadvice fill-paragraph (around ein:worksheet-fill-paragraph activate)
  1169. "Prevent \"Text is read-only\" error when filling paragraph in
  1170. EIN worksheet."
  1171. (if ein:%worksheet%
  1172. (let* ((cell (ein:worksheet-get-current-cell))
  1173. (beg (copy-marker (ein:cell-input-pos-min cell))))
  1174. (save-excursion
  1175. (goto-char beg)
  1176. (insert "\n"))
  1177. (unwind-protect
  1178. ad-do-it
  1179. (save-excursion
  1180. (goto-char beg)
  1181. (delete-char 1))))
  1182. ad-do-it))
  1183. (provide 'ein-worksheet)
  1184. ;;; ein-worksheet.el ends here