forked from millejoh/emacs-ipython-notebook
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ein-notebook.el
1810 lines (1578 loc) · 77 KB
/
ein-notebook.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; ein-notebook.el --- Notebook module -*- lexical-binding: t -*-
;; Copyright (C) 2012- Takafumi Arakaki
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
;; This file is NOT part of GNU Emacs.
;; ein-notebook.el is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; ein-notebook.el is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with ein-notebook.el. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; * Coding rule about current buffer.
;; A lot of notebook and cell functions touches to current buffer and
;; it is not ideal to wrap all these functions by `with-current-buffer'.
;; Therefore, when the function takes `notebook' to the first argument
;; ("method" function), it is always assumed that the current buffer
;; is the notebook buffer. **However**, functions called as callback
;; (via `url-retrieve', for example) must protect themselves by
;; calling from unknown buffer.
;;; Code:
(eval-when-compile (require 'auto-complete))
(require 'ewoc)
(require 'mumamo nil t)
(require 'company nil t)
(require 'px nil t)
(require 'eldoc nil t)
(require 'ein-core)
(require 'ein-classes)
(require 'ein-console)
(require 'ein-log)
(require 'ein-node)
(require 'ein-contents-api)
(require 'ein-kernel)
(require 'ein-kernelinfo)
(require 'ein-cell)
(require 'ein-cell-edit)
(require 'ein-cell-output)
(require 'ein-worksheet)
(require 'ein-iexec)
(require 'ein-scratchsheet)
(require 'ein-notification)
(require 'ein-completer)
(require 'ein-pager)
(require 'ein-pseudo-console)
(require 'ein-events)
(require 'ein-notification)
(require 'ein-kill-ring)
(require 'ein-query)
(require 'ein-pytools)
(require 'ein-traceback)
(require 'ein-inspector)
(require 'ein-shared-output)
(require 'ein-notebooklist)
(require 'ein-multilang)
(require 'ob-ein)
(require 'poly-ein)
;;; Configuration
(make-obsolete-variable 'ein:notebook-discard-output-on-save nil "0.2.0")
(declare-function ein:smartrep-config "ein-smartrep")
(defcustom ein:use-smartrep nil
"Set to `t' to use preset smartrep configuration.
.. warning:: When used with MuMaMo (see `ein:notebook-modes'),
keyboard macro which manipulates cell (add, remove, move,
etc.) may start infinite loop (you need to stop it with
``C-g``). Please be careful using this option if you are a
heavy keyboard macro user. Using keyboard macro for other
commands is fine.
.. (Comment) I guess this infinite loop happens because the three
modules (kmacro.el, mumamo.el and smartrep.el) touches to
`unread-command-events' in somehow inconsistent ways."
:type 'boolean
:group 'ein)
(defvar *ein:notebook--pending-query* (make-hash-table :test 'equal)
"A map: (URL-OR-PORT . PATH) => t/nil")
(defcustom ein:notebook-autosave-frequency 300
"Sets the frequency (in seconds) at which the notebook is
automatically saved, per IPEP15. Set to 0 to disable this feature.
Autosaves are automatically enabled when a notebook is opened,
but can be controlled manually via `ein:notebook-enable-autosave'
and `ein:notebook-disable-autosave'.
If you wish to change the autosave frequency for the current
notebook call `ein:notebook-update-autosave-freqency'.
"
:type 'number
:group 'ein)
(defcustom ein:notebook-create-checkpoint-on-save t
"If non-nil a checkpoint will be created every time the
notebook is saved. Otherwise checkpoints must be created manually
via `ein:notebook-create-checkpoint'."
:type 'boolean
:group 'ein)
(defcustom ein:notebook-discard-output-on-save 'no
"Configure if the output part of the cell should be saved or not.
.. warning:: This configuration is obsolete now.
Use nbconvert (https://github.com/ipython/nbconvert) to
strip output.
`no' : symbol
Save output. This is the default.
`yes' : symbol
Always discard output.
a function
This function takes two arguments, notebook and cell. Return
`t' to discard output and return `nil' to save. For example,
if you don't want to save image output but other kind of
output, use `ein:notebook-cell-has-image-output-p'.
"
:type '(choice (const :tag "No" 'no)
(const :tag "Yes" 'yes)
)
:group 'ein)
(defun ein:notebook-cell-has-image-output-p (_ignore cell)
(ein:cell-has-image-ouput-p cell))
(defun ein:notebook-discard-output-p (notebook cell)
"Return non-`nil' if the output must be discarded, otherwise save."
(cl-case ein:notebook-discard-output-on-save
(no nil)
(yes t)
(t (funcall ein:notebook-discard-output-on-save notebook cell))))
;; As opening/saving notebook treats possibly huge data, define these
;; timeouts separately:
(defcustom ein:notebook-querty-timeout-save (* 60 1000) ; 1 min
"Query timeout for saving notebook.
Similar to `ein:notebook-querty-timeout-open', but for saving
notebook. For global setting and more information, see
`ein:query-timeout'."
:type '(choice (integer :tag "Timeout [ms]" 5000)
(const :tag "Use global setting" nil))
:group 'ein)
(defcustom ein:helm-kernel-history-search-key nil
"Bind `helm-ein-kernel-history' to this key in notebook mode.
Example::
(setq ein:helm-kernel-history-search-key \"\\M-r\")
This key will be installed in the `ein:notebook-mode-map'."
:type 'boolean
:group 'ein)
(defcustom ein:anything-kernel-history-search-key nil
"Bind `anything-ein-kernel-history' to this key in notebook mode.
Example::
(setq ein:anything-kernel-history-search-key \"\\M-r\")
This key will be installed in the `ein:notebook-mode-map'."
:type 'boolean
:group 'ein)
(defcustom ein:notebook-set-buffer-file-name nil
"[DEPRECATED] Set `buffer-file-name' of notebook buffer. Currently does nothing."
:type 'boolean
:group 'ein)
(defvar ein:notebook-after-rename-hook nil
"Hooks to run after notebook is renamed successfully.
Current buffer for these functions is set to the notebook buffer.")
;;; Class and variable
(defvar ein:base-kernel-url "/api/")
(defvar ein:create-session-url "/api/sessions")
;; Currently there is no way to know this setting. Maybe I should ask IPython
;; developers for an API to get this from notebook server.
;;
;; 10April2014 (JMM) - The most recent documentation for the RESTful interface
;; is at:
;; https://github.com/ipython/ipython/wiki/IPEP-16%3A-Notebook-multi-directory-dashboard-and-URL-mapping
(defvar ein:notebook-pager-buffer-name-template "*ein:pager %s/%s*")
(defvar ein:notebook-buffer-name-template "*ein: %s/%s*")
(ein:deflocal ein:%notebook% nil
"Buffer local variable to store an instance of `ein:$notebook'.")
(ein:deflocal ein:%notebook-latex-p% nil
"Is latex preview toggled")
(define-obsolete-variable-alias 'ein:notebook 'ein:%notebook% "0.1.2")
;;; Constructor
(defun ein:notebook-new (url-or-port notebook-path pre-kernelspec &rest args)
(let ((kernelspec
(cond ((ein:$kernelspec-p pre-kernelspec) pre-kernelspec)
((consp pre-kernelspec)
(cl-loop for (_name ks) on (ein:need-kernelspecs url-or-port) by 'cddr
when (and (ein:$kernelspec-p ks)
(string= (cdr pre-kernelspec)
(cl-struct-slot-value
'ein:$kernelspec (car pre-kernelspec) ks)))
return ks))
(t (ein:get-kernelspec url-or-port pre-kernelspec)))))
(apply #'make-ein:$notebook
:url-or-port url-or-port
:kernelspec kernelspec
:notebook-path notebook-path
args)))
;;; Destructor
(defun ein:notebook-del (notebook)
"Destructor for `ein:$notebook'."
(ein:log-ignore-errors
(ein:kernel-del (ein:$notebook-kernel notebook))))
(defun ein:notebook-close-worksheet (notebook ws)
"Close worksheet WS in NOTEBOOK. If WS is the last worksheet,
call notebook destructor `ein:notebook-del'."
(cl-symbol-macrolet ((worksheets (ein:$notebook-worksheets notebook))
(scratchsheets (ein:$notebook-scratchsheets notebook)))
(cond
((ein:worksheet-p ws) (ein:worksheet-save-cells ws t))
(t (setq scratchsheets (delq ws scratchsheets))))
(unless (or (seq-filter (lambda (x)
(and (not (eq x ws))
(ein:worksheet-has-buffer-p x)))
worksheets)
scratchsheets)
(ein:notebook-del notebook))))
;;; Notebook utility functions
(defun ein:notebook-update-url-or-port (new-url-or-port notebook)
"Change the url and port the notebook is saved to. Calling
this will propagate the change to the kernel, trying to restart
the kernel in the process. Use case for this command is when
the jupyter server dies and restarted on a different port.
If you have enabled token or password security on server running
at the new url/port, then please be aware that this new url-port
combo must match exactly these url/port you used format
`ein:notebooklist-login'."
(interactive (list
(ein:notebooklist-ask-url-or-port)
(ein:notebook--get-nb-or-error)))
(message "Updating server info and restarting kernel for notebooklist %s"
(ein:$notebook-notebook-name notebook))
(setf (ein:$notebook-url-or-port notebook) new-url-or-port)
(with-current-buffer (ein:notebook-buffer notebook)
(ein:kernel-retrieve-session (ein:$notebook-kernel notebook))
(rename-buffer (format ein:notebook-buffer-name-template
(ein:$notebook-url-or-port notebook)
(ein:$notebook-notebook-name notebook)))))
(defun ein:notebook-buffer (notebook)
"Return first buffer in NOTEBOOK's worksheets."
(cl-loop for ws in (append (ein:$notebook-worksheets notebook)
(ein:$notebook-scratchsheets notebook))
if (ein:worksheet-buffer ws)
return it))
(defun ein:notebook-buffer-list (notebook)
"Return the buffers associated with NOTEBOOK's kernel.
The buffer local variable `default-directory' of these buffers
will be updated with kernel's cwd."
(delete nil
(mapcar #'ein:worksheet-buffer
(append (ein:$notebook-worksheets notebook)
(ein:$notebook-scratchsheets notebook)))))
(defun ein:notebook--get-nb-or-error ()
(or ein:%notebook% (error "Not in notebook buffer.")))
;;;###autoload
(defalias 'ein:notebook-name 'ein:$notebook-notebook-name)
(defun ein:notebook-name-getter (notebook)
(cons #'ein:notebook-name notebook))
;;; Open notebook
(defun ein:notebook-url (notebook)
(ein:notebook-url-from-url-and-id (ein:$notebook-url-or-port notebook)
(ein:$notebook-api-version notebook)
(ein:$notebook-notebook-path notebook)))
(defun ein:notebook-url-from-url-and-id (url-or-port api-version path)
(cond ((= api-version 2)
(ein:url url-or-port "api/notebooks" path))
((>= api-version 3)
(ein:url url-or-port "api/contents" path))))
(defun ein:notebook-open--decorate-callback (notebook existing pending-clear callback no-pop)
"In addition to CALLBACK, also clear the pending semaphore, pop-to-buffer the new notebook, save to disk the kernelspec metadata, and put last warning in minibuffer."
(apply-partially
(lambda (notebook* created callback* pending-clear* no-pop*)
(funcall pending-clear*)
(with-current-buffer (ein:notebook-buffer notebook*)
(ein:worksheet-focus-cell))
(unless no-pop*
(with-current-buffer (ein:notebook-buffer notebook*)
(if ein:polymode
(progn
(pm-select-buffer (pm-innermost-span))
(pop-to-buffer (pm-span-buffer (pm-innermost-span))))
(pop-to-buffer (ein:notebook-buffer notebook*)))))
(when (and (not noninteractive)
(null (plist-member (ein:$notebook-metadata notebook*) :kernelspec)))
(ein:aif (ein:$notebook-kernelspec notebook*)
(progn
(setf (ein:$notebook-metadata notebook*)
(plist-put (ein:$notebook-metadata notebook*)
:kernelspec (ein:notebook--spec-insert-name
(ein:$kernelspec-name it) (ein:$kernelspec-spec it))))
(ein:notebook-save-notebook notebook*))))
(when callback*
(funcall callback* notebook* created))
(ein:and-let* ((created)
(buffer (get-buffer "*Warnings*"))
(last-warning (with-current-buffer buffer
(thing-at-point 'line t))))
(message "%s" last-warning)))
notebook (not existing) callback pending-clear no-pop))
(defun ein:notebook-open-or-create (url-or-port path &optional kernelspec callback no-pop)
"Same as `ein:notebook-open' but create PATH if not found."
(let ((if-not-found (lambda (_contents _status-code) )))
(ein:notebook-open url-or-port path kernelspec callback if-not-found no-pop)))
;;;###autoload
(defun ein:notebook-jump-to-opened-notebook (notebook)
"List all opened notebook buffers and switch to one that the user selects."
(interactive
(list (completing-read "Jump to notebook:" (ein:notebook-opened-buffer-names) nil t)))
(switch-to-buffer notebook))
;;;###autoload
(defun ein:notebook-open (url-or-port path &optional kernelspec callback errback no-pop)
"Returns notebook at URL-OR-PORT/PATH.
Note that notebook sends for its contents and won't have them right away.
After the notebook is opened, CALLBACK is called as::
\(funcall CALLBACK notebook created)
where `created' indicates a new notebook or an existing one.
"
(interactive
(ein:notebooklist-parse-nbpath (ein:notebooklist-ask-path "notebook")))
(unless errback (setq errback #'ignore))
(let* ((pending-key (cons url-or-port path))
(pending-p (gethash pending-key *ein:notebook--pending-query*))
(pending-clear (apply-partially (lambda (pending-key* &rest _args)
(remhash pending-key*
*ein:notebook--pending-query*))
pending-key))
(existing (ein:notebook-get-opened-notebook url-or-port path))
(notebook (ein:aif existing it
(ein:notebook-new url-or-port path kernelspec)))
(callback0 (ein:notebook-open--decorate-callback notebook existing pending-clear
callback no-pop)))
(if existing
(progn
(ein:log 'info "Notebook %s is already open"
(ein:$notebook-notebook-name notebook))
(funcall callback0))
(if (and pending-p noninteractive)
(ein:log 'error "Notebook %s pending open!" path)
(when (or (not pending-p)
(y-or-n-p (format "Notebook %s pending open! Retry? " path)))
(setf (gethash pending-key *ein:notebook--pending-query*) t)
(add-function :before (var errback) pending-clear)
(ein:content-query-contents url-or-port path
(apply-partially #'ein:notebook-open--callback
notebook callback0 (not no-pop))
errback))))
notebook))
(defun ein:notebook-open--callback (notebook callback0 q-checkpoints content)
(ein:log 'verbose "Opened notebook %s" (ein:$notebook-notebook-path notebook))
(let ((_notebook-path (ein:$notebook-notebook-path notebook)))
(ein:gc-prepare-operation)
(setf (ein:$notebook-api-version notebook) (ein:$content-notebook-version content)
(ein:$notebook-notebook-name notebook) (ein:$content-name content))
(ein:notebook-bind-events notebook (ein:events-new))
(ein:notebook-maybe-set-kernelspec notebook (plist-get (ein:$content-raw-content content) :metadata))
(ein:notebook-install-kernel notebook)
(ein:notebook-from-json notebook (ein:$content-raw-content content))
(if (not (with-current-buffer (ein:notebook-buffer notebook)
(ein:get-notebook)))
(error "ein:notebook-open--callback: notebook instantiation failed")
;; Start websocket only after worksheet is rendered
;; because ein:notification-bind-events only gets called after worksheet's
;; buffer local notification widget is instantiated
(ein:kernel-retrieve-session (ein:$notebook-kernel notebook) nil
(apply-partially (lambda (callback0* name* _kernel)
(funcall callback0*)
(ein:log 'info "Notebook %s is ready" name*))
callback0
(ein:$notebook-notebook-name notebook)))
(setf (ein:$notebook-kernelinfo notebook)
(ein:kernelinfo-new (ein:$notebook-kernel notebook)
(cons #'ein:notebook-buffer-list notebook)
(symbol-name (ein:get-mode-for-kernel (ein:$notebook-kernelspec notebook)))))
(ein:notebook-put-opened-notebook notebook)
(ein:notebook--check-nbformat (ein:$content-raw-content content))
(setf (ein:$notebook-q-checkpoints notebook) q-checkpoints)
(ein:notebook-enable-autosaves notebook)
(ein:gc-complete-operation))))
(defun ein:notebook-maybe-set-kernelspec (notebook content-metadata)
(ein:aif (plist-get content-metadata :kernelspec)
(let ((kernelspec (ein:get-kernelspec (ein:$notebook-url-or-port notebook)
(plist-get it :name))))
(setf (ein:$notebook-kernelspec notebook) kernelspec))))
(defun ein:notebook--different-number (n1 n2)
(and (numberp n1) (numberp n2) (not (= n1 n2))))
(defun ein:notebook--check-nbformat (data)
"Warn user when nbformat is changed on server side.
See https://github.com/ipython/ipython/pull/1934 for the purpose
of minor mode."
;; See `Notebook.prototype.load_notebook_success'
;; at IPython/frontend/html/notebook/static/js/notebook.js
(cl-destructuring-bind (&key nbformat orig_nbformat
nbformat_minor orig_nbformat_minor
&allow-other-keys)
data
(cond
((ein:notebook--different-number nbformat orig_nbformat)
(ein:display-warning
(format "Notebook major version updated (v%d -> v%d).
To not update version, do not save this notebook."
orig_nbformat nbformat)))
((ein:notebook--different-number nbformat_minor orig_nbformat_minor)
(ein:display-warning
(format "This notebook is version v%s.%s, but IPython
server you are using only fully support up to v%s.%s.
Some features may not be available."
orig_nbformat orig_nbformat_minor
nbformat nbformat_minor))))))
;;; Initialization.
(defun ein:notebook-enable-autosaves (notebook)
"Enable automatic, periodic saving for notebook."
(interactive
(list (or (ein:get-notebook)
(ein:aand (ein:notebook-opened-buffer-names)
(with-current-buffer (ein:completing-read
"Notebook: " it nil t)
(ein:get-notebook))))))
(when (and (ein:$notebook-q-checkpoints notebook)
(> ein:notebook-autosave-frequency 0))
(setf (ein:$notebook-autosave-timer notebook)
(run-at-time ein:notebook-autosave-frequency
ein:notebook-autosave-frequency
#'ein:notebook-maybe-save-notebook
notebook))
(ein:log 'verbose "Enabling autosaves for %s with frequency %s seconds."
(ein:$notebook-notebook-name notebook)
ein:notebook-autosave-frequency)))
(defun ein:notebook-disable-autosaves (notebook)
"Disable automatic, periodic saving for current notebook."
(interactive
(list (or (ein:get-notebook)
(ein:aand (ein:notebook-opened-buffer-names)
(with-current-buffer (ein:completing-read
"Notebook: " it nil t)
(ein:get-notebook))))))
(if (and notebook (ein:$notebook-autosave-timer notebook))
(progn
(ein:log 'verbose "Disabling auto checkpoints for notebook %s" (ein:$notebook-notebook-name notebook))
(cancel-timer (ein:$notebook-autosave-timer notebook)))))
(defun ein:notebook-update-autosave-frequency (new-frequency notebook)
"Change the autosaves frequency for the current notebook, or
for a notebook selected by the user if not currently inside a
notebook buffer."
(interactive
(list (read-number "New autosaves frequency (0 to disable): ")
(or (ein:get-notebook)
(ein:aand (ein:notebook-opened-buffer-names)
(with-current-buffer (ein:completing-read
"Notebook: " it nil t)
(ein:get-notebook))))))
(if notebook
(progn
(setq ein:notebook-autosave-frequency new-frequency)
(ein:notebook-disable-autosaves notebook)
(ein:notebook-enable-autosaves notebook))
(message "Open notebook first")))
(defun ein:notebook-bind-events (notebook events)
"Bind events related to PAGER to the event handler EVENTS."
(setf (ein:$notebook-events notebook) events)
(ein:worksheet-class-bind-events events)
;; Bind events for sub components:
(setf (ein:$notebook-pager notebook)
(ein:pager-new
(format ein:notebook-pager-buffer-name-template
(ein:$notebook-url-or-port notebook)
(ein:$notebook-notebook-name notebook))
(ein:$notebook-events notebook))))
(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.")
(define-obsolete-function-alias
'ein:notebook-show-in-shared-output
'ein:shared-output-show-code-cell-at-point "0.1.2")
(eval-when-compile (defvar outline-regexp))
(defsubst ein:notebook-toggle-latex-fragment ()
(interactive)
(cond (ein:polymode (ein:display-warning "ein:notebook-toggle-latex-fragment: delegate to markdown-mode"))
((featurep 'px)
(let ((outline-regexp "$a")
(kill-buffer-hook nil)) ;; outline-regexp never matches to avoid headline
(cl-letf (((symbol-function 'px-remove) #'ignore))
(if ein:%notebook-latex-p%
(progn
(ein:worksheet-render (ein:worksheet--get-ws-or-error))
(setq ein:%notebook-latex-p% nil))
(px-preview)
(setq ein:%notebook-latex-p% t)))))
(t (ein:display-warning "px package not found"))))
;;; Kernel related things
(defun ein:list-available-kernels (url-or-port)
(let ((kernelspecs (ein:need-kernelspecs url-or-port)))
(if kernelspecs
(cl-sort (cl-loop for (_key spec) on (ein:plist-exclude kernelspecs '(:default)) by 'cddr
collecting (cons (ein:$kernelspec-name spec)
(ein:$kernelspec-display-name spec)))
#'string< :key #'cdr))))
(defun ein:notebook-switch-kernel (notebook kernel-name)
"Change the kernel for a running notebook. If not called from a
notebook buffer then the user will be prompted to select an opened notebook."
(interactive
(let* ((notebook (or (ein:get-notebook)
(ein:completing-read
"Select notebook: "
(ein:notebook-opened-buffer-names))))
(kernel-name (ein:completing-read
"Select kernel: "
(ein:list-available-kernels (ein:$notebook-url-or-port notebook)))))
(list notebook kernel-name)))
(let* ((kernelspec (ein:get-kernelspec
(ein:$notebook-url-or-port notebook) kernel-name)))
(setf (ein:$notebook-kernelspec notebook) kernelspec)
(setf (ein:$notebook-metadata notebook)
(plist-put (ein:$notebook-metadata notebook)
:kernelspec (ein:notebook--spec-insert-name
(ein:$kernelspec-name kernelspec) (ein:$kernelspec-spec kernelspec))))
(ein:notebook-save-notebook notebook #'ein:notebook-kill-kernel-then-close-command
(list notebook))
(cl-loop repeat 10
until (null (ein:$kernel-websocket (ein:$notebook-kernel notebook)))
do (sleep-for 0 500)
finally return (ein:notebook-open (ein:$notebook-url-or-port notebook)
(ein:$notebook-notebook-path notebook)))))
(defun ein:notebook-install-kernel (notebook)
(let* ((base-url (concat ein:base-kernel-url "kernels"))
(kernelspec (ein:$notebook-kernelspec notebook))
(kernel (ein:kernel-new (ein:$notebook-url-or-port notebook)
(ein:$notebook-notebook-path notebook)
kernelspec
base-url
(ein:$notebook-events notebook)
(ein:$notebook-api-version notebook))))
(setf (ein:$notebook-kernel notebook) kernel)
(when (eq (ein:get-mode-for-kernel (ein:$notebook-kernelspec notebook)) 'python)
(ein:pytools-setup-hooks kernel notebook))))
(defun ein:notebook-reconnect-session-command ()
"It seems convenient but undisciplined to blithely create a new session if the original one no longer exists."
(interactive)
(ein:kernel-reconnect-session (ein:$notebook-kernel ein:%notebook%)))
(defun ein:notebook-restart-session-command ()
"Delete session on server side. Start new session."
(interactive)
(ein:aif ein:%notebook%
(if (y-or-n-p "Are you sure? ")
(ein:kernel-restart-session (ein:$notebook-kernel it)))
(message "Not in notebook buffer!")))
(define-obsolete-function-alias
'ein:notebook-request-tool-tip-or-help-command
'ein:pytools-request-tooltip-or-help "0.1.2")
(defun ein:notebook-ac-dot-complete ()
"Insert dot and request completion."
(interactive)
(if (and (ein:get-notebook)
(ein:codecell-p (ein:get-cell-at-point)))
(call-interactively #'ein:ac-dot-complete)
(insert ".")))
(defun ein:notebook-kernel-interrupt-command ()
"Interrupt the kernel.
This is equivalent to do ``C-c`` in the console program."
(interactive)
(ein:kernel-interrupt (ein:$notebook-kernel ein:%notebook%)))
;; autoexec
(defun ein:notebook-execute-autoexec-cells (notebook)
"Execute cells of which auto-execution flag is on."
(interactive (list (or ein:%notebook% (error "Not in notebook buffer!"))))
(mapc #'ein:worksheet-execute-autoexec-cells
(ein:$notebook-worksheets notebook)))
(define-obsolete-function-alias
'ein:notebook-eval-string
'ein:shared-output-eval-string "0.1.2")
;;; Persistence and loading
(defun ein:notebook-set-notebook-name (notebook name)
"Check NAME and change the name of NOTEBOOK to it."
(if (ein:notebook-test-notebook-name name)
(setf (ein:$notebook-notebook-name notebook) name
(ein:$notebook-notebook-id notebook) name)
(ein:log 'error "%S is not a good notebook name." name)
(error "%S is not a good notebook name." name)))
(defun ein:notebook-test-notebook-name (name)
(and (stringp name)
(> (length name) 0)
(not (string-match "[\\/\\\\:]" name))))
(cl-defun ein:notebook--worksheet-new (notebook &optional (func #'ein:worksheet-new))
(funcall func
(ein:$notebook-nbformat notebook)
(ein:notebook-name-getter notebook)
(cons (lambda (notebook cell)
(ein:notebook-discard-output-p notebook cell))
notebook)
(ein:$notebook-kernel notebook)
(ein:$notebook-events notebook)))
(defun ein:notebook--worksheet-render (notebook ws)
(ein:worksheet-render ws)
(with-current-buffer (ein:worksheet-buffer ws)
(let (multilang-failed)
(if ein:polymode
(poly-ein-mode)
;; Changing major mode here is super dangerous as it
;; kill-all-local-variables.
;; Our saviour has been `ein:deflocal' which applies 'permanent-local
;; to variables assigned up to this point, but we ought not rely on it
(funcall (ein:notebook-choose-mode))
(ein:worksheet-reinstall-undo-hooks ws)
(condition-case err
(ein:aif (ein:$notebook-kernelspec notebook)
(ein:ml-lang-setup it))
(error (ein:log 'error (error-message-string err))
(setq multilang-failed t))))
(unless multilang-failed
(ein:notebook-mode)
(ein:notebook--notification-setup notebook)
(ein:notebook-setup-kill-buffer-hook)
(setq ein:%notebook% notebook)
(when ein:polymode
(poly-ein-fontify-buffer (ein:notebook-buffer notebook)))))))
(defun ein:notebook--notification-setup (notebook)
(ein:notification-setup
(current-buffer)
(ein:$notebook-events notebook)
:get-list
(lambda () (ein:$notebook-worksheets ein:%notebook%))
:get-current
(lambda () ein:%worksheet%)
:get-name
#'ein:worksheet-name
:get-buffer
(lambda (ws)
(ein:notebook-worksheet--render-maybe ein:%notebook% ws "clicked")
(ein:worksheet-buffer ws))
:delete
(lambda (ws)
(ein:notebook-worksheet-delete ein:%notebook% ws t))
:insert-prev
(lambda (ws) (ein:notebook-worksheet-insert-prev ein:%notebook% ws))
:insert-next
(lambda (ws) (ein:notebook-worksheet-insert-next ein:%notebook% ws))
:move-prev
(lambda (ws) (ein:notebook-worksheet-move-prev ein:%notebook% ws))
:move-next
(lambda (ws) (ein:notebook-worksheet-move-next ein:%notebook% ws))
))
(defun ein:notebook-set-buffer-file-name-maybe (_notebook)
(ein:log 'warn "This function is deprecated. Who could be calling me?"))
;; (defun ein:notebook-set-buffer-file-name-maybe (notebook)
;; "Set `buffer-file-name' of the current buffer to ipynb file
;; of NOTEBOOK."
;; (when ein:notebook-set-buffer-file-name
;; (ein:notebook-fetch-data
;; notebook
;; (lambda (data notebook buffer)
;; (with-current-buffer buffer
;; (cl-destructuring-bind (&key project &allow-other-keys)
;; data
;; (setq buffer-file-name
;; (expand-file-name
;; (format "%s.ipynb"
;; (ein:$notebook-notebook-name notebook))
;; project)))))
;; (list notebook (current-buffer)))))
(defun ein:notebook-from-json (notebook data)
(cl-destructuring-bind (&key metadata nbformat nbformat_minor
&allow-other-keys)
data
(setf (ein:$notebook-metadata notebook) metadata)
(setf (ein:$notebook-nbformat notebook) nbformat)
(setf (ein:$notebook-nbformat-minor notebook) nbformat_minor))
(setf (ein:$notebook-worksheets notebook)
(cl-case (ein:$notebook-nbformat notebook)
(3 (ein:read-nbformat3-worksheets notebook data))
(4 (ein:read-nbformat4-worksheets notebook data))
(t (ein:log 'error "nbformat version %s unsupported"
(ein:$notebook-nbformat notebook)))))
(ein:notebook--worksheet-render notebook (car (ein:$notebook-worksheets notebook)))
notebook)
(defun ein:read-nbformat3-worksheets (notebook data)
(mapcar (lambda (ws-data)
(ein:worksheet-from-json
(ein:notebook--worksheet-new notebook)
ws-data))
(or (plist-get data :worksheets)
(list nil))))
;; nbformat4 gets rid of the concept of worksheets. That means, for the moment,
;; ein will no longer support worksheets. There may be a path forward for
;; reimplementing this feature, however. The nbformat 4 json definition says
;; that cells are allowed to have tags. Clever use of this feature may lead to
;; good things.
(defun ein:read-nbformat4-worksheets (notebook data)
"Convert a notebook in nbformat4 to a list of worksheet-like
objects suitable for processing in ein:notebook-from-json."
(let* ((cells (plist-get data :cells))
(ws-cells (mapcar (lambda (data) (ein:cell-from-json data)) cells))
(worksheet (ein:notebook--worksheet-new notebook)))
(oset worksheet :saved-cells ws-cells)
;(mapcar (lambda (data) (message "test %s" (slot-value data 'metadata))) ws-cells)
(list worksheet)))
(defun ein:notebook-to-json (notebook)
"Return json-ready alist."
(let ((data
(cl-case (ein:$notebook-nbformat notebook)
(3 (ein:write-nbformat3-worksheets notebook))
(4 (ein:write-nbformat4-worksheets notebook))
(t (ein:log 'error "nbformat version %s unsupported"
(ein:$notebook-nbformat notebook))))))
;; Apparently metadata can be either a hashtable or a plist...
(let ((metadata (cdr (assq 'metadata data))))
(if (hash-table-p metadata)
(setf (gethash 'name metadata) (ein:$notebook-notebook-name notebook))
(plist-put metadata
:name (ein:$notebook-notebook-name notebook)))
(ein:aif (ein:$notebook-nbformat-minor notebook)
;; Do not set nbformat when it is not given from server.
(push `(nbformat_minor . ,it) data))
(push `(nbformat . ,(ein:$notebook-nbformat notebook)) data)
data)))
(defun ein:write-nbformat3-worksheets (notebook)
(let ((worksheets (mapcar #'ein:worksheet-to-json
(ein:$notebook-worksheets notebook))))
`((worksheets . ,(apply #'vector worksheets))
(metadata . ,(ein:$notebook-metadata notebook))
)))
(defsubst ein:notebook--spec-insert-name (name spec)
"Add kernel NAME, e.g., 'python2', to the kernelspec member of ipynb metadata."
(if (plist-member spec :name)
spec
(plist-put spec :name name)))
(defun ein:write-nbformat4-worksheets (notebook)
(let ((all-cells (cl-loop for ws in (ein:$notebook-worksheets notebook)
for i from 0
append (ein:worksheet-to-nb4-json ws i))))
;; should be in notebook constructor, not here
(ein:aif (ein:$notebook-kernelspec notebook)
(setf (ein:$notebook-metadata notebook)
(plist-put (ein:$notebook-metadata notebook)
:kernelspec (ein:notebook--spec-insert-name
(ein:$kernelspec-name it) (ein:$kernelspec-spec it)))))
`((metadata . ,(ein:aif (ein:$notebook-metadata notebook)
it
(make-hash-table)))
(cells . ,(apply #'vector all-cells)))))
(defun ein:notebook-maybe-save-notebook (notebook &optional callback cbargs)
(if (cl-some #'(lambda (ws)
(buffer-modified-p
(ein:worksheet-buffer ws)))
(ein:$notebook-worksheets notebook))
(ein:notebook-save-notebook notebook callback cbargs)))
(defun ein:notebook-save-notebook (notebook &optional callback cbargs errback)
(unless (ein:notebook-buffer notebook)
(let ((buf (format ein:notebook-buffer-name-template
(ein:$notebook-url-or-port notebook)
(ein:$notebook-notebook-name notebook))))
(ein:log 'error "ein:notebook-save-notebook: notebook %s has no buffer!" buf)
(setf (ewoc--buffer (ein:worksheet--ewoc
(car (ein:$notebook-worksheets notebook))))
(get-buffer buf))))
(condition-case err
(with-current-buffer (ein:notebook-buffer notebook)
(cl-letf (((symbol-function 'delete-trailing-whitespace) #'ignore))
(run-hooks 'before-save-hook)))
(error (ein:log 'warn "ein:notebook-save-notebook: Saving despite '%s'."
(error-message-string err))))
(let ((content (ein:content-from-notebook notebook)))
(ein:events-trigger (ein:$notebook-events notebook)
'notebook_saving.Notebook)
(ein:content-save content
#'ein:notebook-save-notebook-success
(list notebook callback cbargs)
#'ein:notebook-save-notebook-error
(list notebook errback))))
(defun ein:notebook-save-notebook-command ()
"Save the notebook."
(interactive)
(ein:notebook-save-notebook ein:%notebook%))
(defun ein:notebook-save-notebook-success (notebook &optional callback cbargs)
(ein:log 'verbose "Notebook is saved.")
(setf (ein:$notebook-dirty notebook) nil)
(mapc (lambda (ws)
(ein:worksheet-save-cells ws) ; [#]_
(ein:worksheet-set-modified-p ws nil))
(ein:$notebook-worksheets notebook))
(ein:events-trigger (ein:$notebook-events notebook)
'notebook_saved.Notebook)
(when ein:notebook-create-checkpoint-on-save
(ein:notebook-create-checkpoint notebook))
(when callback
(apply callback cbargs)))
;; .. [#] Consider the following case.
;; (1) Open worksheet WS0 and other worksheets.
;; (2) Edit worksheet WS0 then save the notebook.
;; (3) Edit worksheet WS0.
;; (4) Kill WS0 buffer by discarding the edit.
;; (5) Save the notebook.
;; This should save the latest WS0. To do so, WS0 at the point (2)
;; must be cached in the worksheet slot `:saved-cells'.
(cl-defun ein:notebook-save-notebook-error (notebook &key symbol-status &allow-other-keys)
(if (eq symbol-status 'user-cancel)
(ein:log 'info "Cancel saving notebook.")
(ein:log 'info "Failed to save notebook!")
(ein:events-trigger (ein:$notebook-events notebook)
'notebook_save_failed.Notebook)))
(defun ein:notebook-rename-command (path)
"Rename current notebook and save it immediately.
NAME is any non-empty string that does not contain '/' or '\\'."
(interactive
(list (read-string "Rename to: "
(ein:$notebook-notebook-path ein:%notebook%))))
(unless (and (string-match "\\.ipynb" path) (= (match-end 0) (length path)))
(setq path (format "%s.ipynb" path)))
(let* ((notebook (ein:notebook--get-nb-or-error))
(content (ein:content-from-notebook notebook)))
(ein:log 'verbose "Renaming notebook %s to '%s'" (ein:notebook-url notebook) path)
(ein:content-rename content path #'ein:notebook-rename-success
(list notebook content))))
(defun ein:notebook-save-to-command (path)
"Make a copy of the notebook and save it to a new path specified by NAME.
NAME is any non-empty string that does not contain '/' or '\\'.
"
(interactive
(list (read-string "Save copy to: " (ein:$notebook-notebook-path ein:%notebook%))))
(unless (and (string-match ".ipynb" path) (= (match-end 0) (length path)))
(setq path (format "%s.ipynb" path)))
(let* ((content (ein:content-from-notebook ein:%notebook%))
(name (substring path (or (cl-position ?/ path :from-end t) 0))))
(setf (ein:$content-path content) path
(ein:$content-name content) name)
(ein:content-save content #'ein:notebook-open
(list (ein:$notebook-url-or-port ein:%notebook%)
path))))
(cl-defun ein:notebook-rename-success (notebook content)
(ein:notebook-remove-opened-notebook notebook)
(ein:notebook-set-notebook-name notebook (ein:$content-name content))
(setf (ein:$notebook-notebook-path notebook) (ein:$content-path content))
(ein:notebook-put-opened-notebook notebook)
(mapc #'ein:worksheet-set-buffer-name
(append (ein:$notebook-worksheets notebook)
(ein:$notebook-scratchsheets notebook)))
(ein:and-let* ((kernel (ein:$notebook-kernel notebook)))
(ein:session-rename (ein:$kernel-url-or-port kernel)
(ein:$kernel-session-id kernel)
(ein:$content-path content))
(setf (ein:$kernel-path kernel) (ein:$content-path content)))
(ein:log 'info "Notebook renamed to %s." (ein:$content-name content)))
(defmacro ein:notebook-avoid-recursion (&rest body)
`(let ((kill-buffer-query-functions
(remove 'ein:notebook-kill-buffer-query kill-buffer-query-functions)))
,@body))
(defun ein:notebook-kill-notebook-buffers (notebook)
"Kill all of NOTEBOOK's buffers"
(mapc #'ein:notebook-kill-current-buffer (ein:notebook-buffer-list notebook)))
(defun ein:notebook-kill-current-buffer (buf)
"Kill BUF and avoid recursion in kill-buffer-query-functions"
(ein:notebook-avoid-recursion
(let ((notebook (buffer-local-value 'ein:%notebook% buf)))
(when (kill-buffer buf)
(ein:notebook-tidy-opened-notebooks notebook)))))
(defsubst ein:notebook-kill-buffer-query ()
(ein:aif (ein:get-notebook--notebook)
(let ((buf (or (buffer-base-buffer (current-buffer))
(current-buffer))))
(ein:notebook-ask-save it (apply-partially
#'ein:notebook-kill-current-buffer
buf))
;; don't kill buffer!
nil)
;; kill buffer!
t))
(defun ein:notebook-ask-save (notebook callback0)
(unless callback0
(setq callback0 #'ignore))
(if (and (ein:notebook-modified-p notebook)
(not (ob-ein-anonymous-p (ein:$notebook-notebook-path notebook))))
(if (y-or-n-p (format "Save %s?" (ein:$notebook-notebook-name notebook)))
(let ((success-positive 0))
(add-function :before (var callback0) (lambda () (setq success-positive 1)))
(ein:notebook-save-notebook notebook callback0 nil
(lambda () (setq success-positive -1)))
(cl-loop repeat 10
until (not (zerop success-positive))
do (sleep-for 0 200)
finally return (> success-positive 0)))
(when (ein:worksheet-p ein:%worksheet%)
(ein:worksheet-dont-save-cells ein:%worksheet%)) ;; TODO de-obfuscate
(funcall callback0)