##// END OF EJS Templates
s/trigger_edit_mode/edit_mode
Jonathan Frederic -
Show More
@@ -1,786 +1,786 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Keyboard management
9 // Keyboard management
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 // Setup global keycodes and inverse keycodes.
15 // Setup global keycodes and inverse keycodes.
16
16
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
20 // but have minor differences.
20 // but have minor differences.
21
21
22 // These apply to Firefox, (Webkit and IE)
22 // These apply to Firefox, (Webkit and IE)
23 var _keycodes = {
23 var _keycodes = {
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
30 '\\ |': 220, '\' "': 222,
30 '\\ |': 220, '\' "': 222,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
39 'insert': 45, 'delete': 46, 'numlock': 144,
39 'insert': 45, 'delete': 46, 'numlock': 144,
40 };
40 };
41
41
42 // These apply to Firefox and Opera
42 // These apply to Firefox and Opera
43 var _mozilla_keycodes = {
43 var _mozilla_keycodes = {
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
45 };
45 };
46
46
47 // This apply to Webkit and IE
47 // This apply to Webkit and IE
48 var _ie_keycodes = {
48 var _ie_keycodes = {
49 '; :': 186, '= +': 187, '- _': 189,
49 '; :': 186, '= +': 187, '- _': 189,
50 };
50 };
51
51
52 var browser = IPython.utils.browser[0];
52 var browser = IPython.utils.browser[0];
53 var platform = IPython.utils.platform;
53 var platform = IPython.utils.platform;
54
54
55 if (browser === 'Firefox' || browser === 'Opera') {
55 if (browser === 'Firefox' || browser === 'Opera') {
56 $.extend(_keycodes, _mozilla_keycodes);
56 $.extend(_keycodes, _mozilla_keycodes);
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
58 $.extend(_keycodes, _ie_keycodes);
58 $.extend(_keycodes, _ie_keycodes);
59 }
59 }
60
60
61 var keycodes = {};
61 var keycodes = {};
62 var inv_keycodes = {};
62 var inv_keycodes = {};
63 for (var name in _keycodes) {
63 for (var name in _keycodes) {
64 var names = name.split(' ');
64 var names = name.split(' ');
65 if (names.length === 1) {
65 if (names.length === 1) {
66 var n = names[0];
66 var n = names[0];
67 keycodes[n] = _keycodes[n];
67 keycodes[n] = _keycodes[n];
68 inv_keycodes[_keycodes[n]] = n;
68 inv_keycodes[_keycodes[n]] = n;
69 } else {
69 } else {
70 var primary = names[0];
70 var primary = names[0];
71 var secondary = names[1];
71 var secondary = names[1];
72 keycodes[primary] = _keycodes[name];
72 keycodes[primary] = _keycodes[name];
73 keycodes[secondary] = _keycodes[name];
73 keycodes[secondary] = _keycodes[name];
74 inv_keycodes[_keycodes[name]] = primary;
74 inv_keycodes[_keycodes[name]] = primary;
75 }
75 }
76 }
76 }
77
77
78
78
79 // Default keyboard shortcuts
79 // Default keyboard shortcuts
80
80
81 var default_common_shortcuts = {
81 var default_common_shortcuts = {
82 'shift' : {
82 'shift' : {
83 help : '',
83 help : '',
84 help_index : '',
84 help_index : '',
85 handler : function (event) {
85 handler : function (event) {
86 // ignore shift keydown
86 // ignore shift keydown
87 return true;
87 return true;
88 }
88 }
89 },
89 },
90 'shift+enter' : {
90 'shift+enter' : {
91 help : 'run cell, select below',
91 help : 'run cell, select below',
92 help_index : 'ba',
92 help_index : 'ba',
93 handler : function (event) {
93 handler : function (event) {
94 IPython.notebook.execute_cell_and_select_below();
94 IPython.notebook.execute_cell_and_select_below();
95 return false;
95 return false;
96 }
96 }
97 },
97 },
98 'ctrl+enter' : {
98 'ctrl+enter' : {
99 help : 'run cell',
99 help : 'run cell',
100 help_index : 'bb',
100 help_index : 'bb',
101 handler : function (event) {
101 handler : function (event) {
102 IPython.notebook.execute_cell();
102 IPython.notebook.execute_cell();
103 return false;
103 return false;
104 }
104 }
105 },
105 },
106 'alt+enter' : {
106 'alt+enter' : {
107 help : 'run cell, insert below',
107 help : 'run cell, insert below',
108 help_index : 'bc',
108 help_index : 'bc',
109 handler : function (event) {
109 handler : function (event) {
110 IPython.notebook.execute_cell_and_insert_below();
110 IPython.notebook.execute_cell_and_insert_below();
111 return false;
111 return false;
112 }
112 }
113 }
113 }
114 };
114 };
115
115
116 if (platform === 'MacOS') {
116 if (platform === 'MacOS') {
117 default_common_shortcuts['cmd+s'] =
117 default_common_shortcuts['cmd+s'] =
118 {
118 {
119 help : 'save notebook',
119 help : 'save notebook',
120 help_index : 'fb',
120 help_index : 'fb',
121 handler : function (event) {
121 handler : function (event) {
122 IPython.notebook.save_checkpoint();
122 IPython.notebook.save_checkpoint();
123 event.preventDefault();
123 event.preventDefault();
124 return false;
124 return false;
125 }
125 }
126 };
126 };
127 } else {
127 } else {
128 default_common_shortcuts['ctrl+s'] =
128 default_common_shortcuts['ctrl+s'] =
129 {
129 {
130 help : 'save notebook',
130 help : 'save notebook',
131 help_index : 'fb',
131 help_index : 'fb',
132 handler : function (event) {
132 handler : function (event) {
133 IPython.notebook.save_checkpoint();
133 IPython.notebook.save_checkpoint();
134 event.preventDefault();
134 event.preventDefault();
135 return false;
135 return false;
136 }
136 }
137 };
137 };
138 }
138 }
139
139
140 // Edit mode defaults
140 // Edit mode defaults
141
141
142 var default_edit_shortcuts = {
142 var default_edit_shortcuts = {
143 'esc' : {
143 'esc' : {
144 help : 'command mode',
144 help : 'command mode',
145 help_index : 'aa',
145 help_index : 'aa',
146 handler : function (event) {
146 handler : function (event) {
147 IPython.notebook.command_mode();
147 IPython.notebook.command_mode();
148 IPython.notebook.focus_cell();
148 IPython.notebook.focus_cell();
149 return false;
149 return false;
150 }
150 }
151 },
151 },
152 'ctrl+m' : {
152 'ctrl+m' : {
153 help : 'command mode',
153 help : 'command mode',
154 help_index : 'ab',
154 help_index : 'ab',
155 handler : function (event) {
155 handler : function (event) {
156 IPython.notebook.command_mode();
156 IPython.notebook.command_mode();
157 IPython.notebook.focus_cell();
157 IPython.notebook.focus_cell();
158 return false;
158 return false;
159 }
159 }
160 },
160 },
161 'up' : {
161 'up' : {
162 help : '',
162 help : '',
163 help_index : '',
163 help_index : '',
164 handler : function (event) {
164 handler : function (event) {
165 var cell = IPython.notebook.get_selected_cell();
165 var cell = IPython.notebook.get_selected_cell();
166 if (cell && cell.at_top()) {
166 if (cell && cell.at_top()) {
167 event.preventDefault();
167 event.preventDefault();
168 IPython.notebook.command_mode();
168 IPython.notebook.command_mode();
169 IPython.notebook.select_prev();
169 IPython.notebook.select_prev();
170 IPython.notebook.trigger_edit_mode();
170 IPython.notebook.edit_mode();
171 return false;
171 return false;
172 }
172 }
173 }
173 }
174 },
174 },
175 'down' : {
175 'down' : {
176 help : '',
176 help : '',
177 help_index : '',
177 help_index : '',
178 handler : function (event) {
178 handler : function (event) {
179 var cell = IPython.notebook.get_selected_cell();
179 var cell = IPython.notebook.get_selected_cell();
180 if (cell && cell.at_bottom()) {
180 if (cell && cell.at_bottom()) {
181 event.preventDefault();
181 event.preventDefault();
182 IPython.notebook.command_mode();
182 IPython.notebook.command_mode();
183 IPython.notebook.select_next();
183 IPython.notebook.select_next();
184 IPython.notebook.trigger_edit_mode();
184 IPython.notebook.edit_mode();
185 return false;
185 return false;
186 }
186 }
187 }
187 }
188 },
188 },
189 'alt+-' : {
189 'alt+-' : {
190 help : 'split cell',
190 help : 'split cell',
191 help_index : 'ea',
191 help_index : 'ea',
192 handler : function (event) {
192 handler : function (event) {
193 IPython.notebook.split_cell();
193 IPython.notebook.split_cell();
194 return false;
194 return false;
195 }
195 }
196 },
196 },
197 'alt+subtract' : {
197 'alt+subtract' : {
198 help : '',
198 help : '',
199 help_index : 'eb',
199 help_index : 'eb',
200 handler : function (event) {
200 handler : function (event) {
201 IPython.notebook.split_cell();
201 IPython.notebook.split_cell();
202 return false;
202 return false;
203 }
203 }
204 },
204 },
205 'tab' : {
205 'tab' : {
206 help : 'indent or complete',
206 help : 'indent or complete',
207 help_index : 'ec',
207 help_index : 'ec',
208 },
208 },
209 'shift+tab' : {
209 'shift+tab' : {
210 help : 'tooltip',
210 help : 'tooltip',
211 help_index : 'ed',
211 help_index : 'ed',
212 },
212 },
213 };
213 };
214
214
215 if (platform === 'MacOS') {
215 if (platform === 'MacOS') {
216 default_edit_shortcuts['cmd+/'] =
216 default_edit_shortcuts['cmd+/'] =
217 {
217 {
218 help : 'toggle comment',
218 help : 'toggle comment',
219 help_index : 'ee'
219 help_index : 'ee'
220 };
220 };
221 default_edit_shortcuts['cmd+]'] =
221 default_edit_shortcuts['cmd+]'] =
222 {
222 {
223 help : 'indent',
223 help : 'indent',
224 help_index : 'ef'
224 help_index : 'ef'
225 };
225 };
226 default_edit_shortcuts['cmd+['] =
226 default_edit_shortcuts['cmd+['] =
227 {
227 {
228 help : 'dedent',
228 help : 'dedent',
229 help_index : 'eg'
229 help_index : 'eg'
230 };
230 };
231 } else {
231 } else {
232 default_edit_shortcuts['ctrl+/'] =
232 default_edit_shortcuts['ctrl+/'] =
233 {
233 {
234 help : 'toggle comment',
234 help : 'toggle comment',
235 help_index : 'ee'
235 help_index : 'ee'
236 };
236 };
237 default_edit_shortcuts['ctrl+]'] =
237 default_edit_shortcuts['ctrl+]'] =
238 {
238 {
239 help : 'indent',
239 help : 'indent',
240 help_index : 'ef'
240 help_index : 'ef'
241 };
241 };
242 default_edit_shortcuts['ctrl+['] =
242 default_edit_shortcuts['ctrl+['] =
243 {
243 {
244 help : 'dedent',
244 help : 'dedent',
245 help_index : 'eg'
245 help_index : 'eg'
246 };
246 };
247 }
247 }
248
248
249 // Command mode defaults
249 // Command mode defaults
250
250
251 var default_command_shortcuts = {
251 var default_command_shortcuts = {
252 'enter' : {
252 'enter' : {
253 help : 'edit mode',
253 help : 'edit mode',
254 help_index : 'aa',
254 help_index : 'aa',
255 handler : function (event) {
255 handler : function (event) {
256 IPython.notebook.trigger_edit_mode();
256 IPython.notebook.edit_mode();
257 return false;
257 return false;
258 }
258 }
259 },
259 },
260 'up' : {
260 'up' : {
261 help : 'select previous cell',
261 help : 'select previous cell',
262 help_index : 'da',
262 help_index : 'da',
263 handler : function (event) {
263 handler : function (event) {
264 var index = IPython.notebook.get_selected_index();
264 var index = IPython.notebook.get_selected_index();
265 if (index !== 0 && index !== null) {
265 if (index !== 0 && index !== null) {
266 IPython.notebook.select_prev();
266 IPython.notebook.select_prev();
267 IPython.notebook.focus_cell();
267 IPython.notebook.focus_cell();
268 }
268 }
269 return false;
269 return false;
270 }
270 }
271 },
271 },
272 'down' : {
272 'down' : {
273 help : 'select next cell',
273 help : 'select next cell',
274 help_index : 'db',
274 help_index : 'db',
275 handler : function (event) {
275 handler : function (event) {
276 var index = IPython.notebook.get_selected_index();
276 var index = IPython.notebook.get_selected_index();
277 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
277 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
278 IPython.notebook.select_next();
278 IPython.notebook.select_next();
279 IPython.notebook.focus_cell();
279 IPython.notebook.focus_cell();
280 }
280 }
281 return false;
281 return false;
282 }
282 }
283 },
283 },
284 'k' : {
284 'k' : {
285 help : 'select previous cell',
285 help : 'select previous cell',
286 help_index : 'dc',
286 help_index : 'dc',
287 handler : function (event) {
287 handler : function (event) {
288 var index = IPython.notebook.get_selected_index();
288 var index = IPython.notebook.get_selected_index();
289 if (index !== 0 && index !== null) {
289 if (index !== 0 && index !== null) {
290 IPython.notebook.select_prev();
290 IPython.notebook.select_prev();
291 IPython.notebook.focus_cell();
291 IPython.notebook.focus_cell();
292 }
292 }
293 return false;
293 return false;
294 }
294 }
295 },
295 },
296 'j' : {
296 'j' : {
297 help : 'select next cell',
297 help : 'select next cell',
298 help_index : 'dd',
298 help_index : 'dd',
299 handler : function (event) {
299 handler : function (event) {
300 var index = IPython.notebook.get_selected_index();
300 var index = IPython.notebook.get_selected_index();
301 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
301 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
302 IPython.notebook.select_next();
302 IPython.notebook.select_next();
303 IPython.notebook.focus_cell();
303 IPython.notebook.focus_cell();
304 }
304 }
305 return false;
305 return false;
306 }
306 }
307 },
307 },
308 'x' : {
308 'x' : {
309 help : 'cut cell',
309 help : 'cut cell',
310 help_index : 'ee',
310 help_index : 'ee',
311 handler : function (event) {
311 handler : function (event) {
312 IPython.notebook.cut_cell();
312 IPython.notebook.cut_cell();
313 return false;
313 return false;
314 }
314 }
315 },
315 },
316 'c' : {
316 'c' : {
317 help : 'copy cell',
317 help : 'copy cell',
318 help_index : 'ef',
318 help_index : 'ef',
319 handler : function (event) {
319 handler : function (event) {
320 IPython.notebook.copy_cell();
320 IPython.notebook.copy_cell();
321 return false;
321 return false;
322 }
322 }
323 },
323 },
324 'shift+v' : {
324 'shift+v' : {
325 help : 'paste cell above',
325 help : 'paste cell above',
326 help_index : 'eg',
326 help_index : 'eg',
327 handler : function (event) {
327 handler : function (event) {
328 IPython.notebook.paste_cell_above();
328 IPython.notebook.paste_cell_above();
329 return false;
329 return false;
330 }
330 }
331 },
331 },
332 'v' : {
332 'v' : {
333 help : 'paste cell below',
333 help : 'paste cell below',
334 help_index : 'eh',
334 help_index : 'eh',
335 handler : function (event) {
335 handler : function (event) {
336 IPython.notebook.paste_cell_below();
336 IPython.notebook.paste_cell_below();
337 return false;
337 return false;
338 }
338 }
339 },
339 },
340 'd' : {
340 'd' : {
341 help : 'delete cell (press twice)',
341 help : 'delete cell (press twice)',
342 help_index : 'ej',
342 help_index : 'ej',
343 count: 2,
343 count: 2,
344 handler : function (event) {
344 handler : function (event) {
345 IPython.notebook.delete_cell();
345 IPython.notebook.delete_cell();
346 return false;
346 return false;
347 }
347 }
348 },
348 },
349 'a' : {
349 'a' : {
350 help : 'insert cell above',
350 help : 'insert cell above',
351 help_index : 'ec',
351 help_index : 'ec',
352 handler : function (event) {
352 handler : function (event) {
353 IPython.notebook.insert_cell_above('code');
353 IPython.notebook.insert_cell_above('code');
354 IPython.notebook.select_prev();
354 IPython.notebook.select_prev();
355 IPython.notebook.focus_cell();
355 IPython.notebook.focus_cell();
356 return false;
356 return false;
357 }
357 }
358 },
358 },
359 'b' : {
359 'b' : {
360 help : 'insert cell below',
360 help : 'insert cell below',
361 help_index : 'ed',
361 help_index : 'ed',
362 handler : function (event) {
362 handler : function (event) {
363 IPython.notebook.insert_cell_below('code');
363 IPython.notebook.insert_cell_below('code');
364 IPython.notebook.select_next();
364 IPython.notebook.select_next();
365 IPython.notebook.focus_cell();
365 IPython.notebook.focus_cell();
366 return false;
366 return false;
367 }
367 }
368 },
368 },
369 'y' : {
369 'y' : {
370 help : 'to code',
370 help : 'to code',
371 help_index : 'ca',
371 help_index : 'ca',
372 handler : function (event) {
372 handler : function (event) {
373 IPython.notebook.to_code();
373 IPython.notebook.to_code();
374 return false;
374 return false;
375 }
375 }
376 },
376 },
377 'm' : {
377 'm' : {
378 help : 'to markdown',
378 help : 'to markdown',
379 help_index : 'cb',
379 help_index : 'cb',
380 handler : function (event) {
380 handler : function (event) {
381 IPython.notebook.to_markdown();
381 IPython.notebook.to_markdown();
382 return false;
382 return false;
383 }
383 }
384 },
384 },
385 'r' : {
385 'r' : {
386 help : 'to raw',
386 help : 'to raw',
387 help_index : 'cc',
387 help_index : 'cc',
388 handler : function (event) {
388 handler : function (event) {
389 IPython.notebook.to_raw();
389 IPython.notebook.to_raw();
390 return false;
390 return false;
391 }
391 }
392 },
392 },
393 '1' : {
393 '1' : {
394 help : 'to heading 1',
394 help : 'to heading 1',
395 help_index : 'cd',
395 help_index : 'cd',
396 handler : function (event) {
396 handler : function (event) {
397 IPython.notebook.to_heading(undefined, 1);
397 IPython.notebook.to_heading(undefined, 1);
398 return false;
398 return false;
399 }
399 }
400 },
400 },
401 '2' : {
401 '2' : {
402 help : 'to heading 2',
402 help : 'to heading 2',
403 help_index : 'ce',
403 help_index : 'ce',
404 handler : function (event) {
404 handler : function (event) {
405 IPython.notebook.to_heading(undefined, 2);
405 IPython.notebook.to_heading(undefined, 2);
406 return false;
406 return false;
407 }
407 }
408 },
408 },
409 '3' : {
409 '3' : {
410 help : 'to heading 3',
410 help : 'to heading 3',
411 help_index : 'cf',
411 help_index : 'cf',
412 handler : function (event) {
412 handler : function (event) {
413 IPython.notebook.to_heading(undefined, 3);
413 IPython.notebook.to_heading(undefined, 3);
414 return false;
414 return false;
415 }
415 }
416 },
416 },
417 '4' : {
417 '4' : {
418 help : 'to heading 4',
418 help : 'to heading 4',
419 help_index : 'cg',
419 help_index : 'cg',
420 handler : function (event) {
420 handler : function (event) {
421 IPython.notebook.to_heading(undefined, 4);
421 IPython.notebook.to_heading(undefined, 4);
422 return false;
422 return false;
423 }
423 }
424 },
424 },
425 '5' : {
425 '5' : {
426 help : 'to heading 5',
426 help : 'to heading 5',
427 help_index : 'ch',
427 help_index : 'ch',
428 handler : function (event) {
428 handler : function (event) {
429 IPython.notebook.to_heading(undefined, 5);
429 IPython.notebook.to_heading(undefined, 5);
430 return false;
430 return false;
431 }
431 }
432 },
432 },
433 '6' : {
433 '6' : {
434 help : 'to heading 6',
434 help : 'to heading 6',
435 help_index : 'ci',
435 help_index : 'ci',
436 handler : function (event) {
436 handler : function (event) {
437 IPython.notebook.to_heading(undefined, 6);
437 IPython.notebook.to_heading(undefined, 6);
438 return false;
438 return false;
439 }
439 }
440 },
440 },
441 'o' : {
441 'o' : {
442 help : 'toggle output',
442 help : 'toggle output',
443 help_index : 'gb',
443 help_index : 'gb',
444 handler : function (event) {
444 handler : function (event) {
445 IPython.notebook.toggle_output();
445 IPython.notebook.toggle_output();
446 return false;
446 return false;
447 }
447 }
448 },
448 },
449 'shift+o' : {
449 'shift+o' : {
450 help : 'toggle output scrolling',
450 help : 'toggle output scrolling',
451 help_index : 'gc',
451 help_index : 'gc',
452 handler : function (event) {
452 handler : function (event) {
453 IPython.notebook.toggle_output_scroll();
453 IPython.notebook.toggle_output_scroll();
454 return false;
454 return false;
455 }
455 }
456 },
456 },
457 's' : {
457 's' : {
458 help : 'save notebook',
458 help : 'save notebook',
459 help_index : 'fa',
459 help_index : 'fa',
460 handler : function (event) {
460 handler : function (event) {
461 IPython.notebook.save_checkpoint();
461 IPython.notebook.save_checkpoint();
462 return false;
462 return false;
463 }
463 }
464 },
464 },
465 'ctrl+j' : {
465 'ctrl+j' : {
466 help : 'move cell down',
466 help : 'move cell down',
467 help_index : 'eb',
467 help_index : 'eb',
468 handler : function (event) {
468 handler : function (event) {
469 IPython.notebook.move_cell_down();
469 IPython.notebook.move_cell_down();
470 return false;
470 return false;
471 }
471 }
472 },
472 },
473 'ctrl+k' : {
473 'ctrl+k' : {
474 help : 'move cell up',
474 help : 'move cell up',
475 help_index : 'ea',
475 help_index : 'ea',
476 handler : function (event) {
476 handler : function (event) {
477 IPython.notebook.move_cell_up();
477 IPython.notebook.move_cell_up();
478 return false;
478 return false;
479 }
479 }
480 },
480 },
481 'l' : {
481 'l' : {
482 help : 'toggle line numbers',
482 help : 'toggle line numbers',
483 help_index : 'ga',
483 help_index : 'ga',
484 handler : function (event) {
484 handler : function (event) {
485 IPython.notebook.cell_toggle_line_numbers();
485 IPython.notebook.cell_toggle_line_numbers();
486 return false;
486 return false;
487 }
487 }
488 },
488 },
489 'i' : {
489 'i' : {
490 help : 'interrupt kernel (press twice)',
490 help : 'interrupt kernel (press twice)',
491 help_index : 'ha',
491 help_index : 'ha',
492 count: 2,
492 count: 2,
493 handler : function (event) {
493 handler : function (event) {
494 IPython.notebook.kernel.interrupt();
494 IPython.notebook.kernel.interrupt();
495 return false;
495 return false;
496 }
496 }
497 },
497 },
498 '0' : {
498 '0' : {
499 help : 'restart kernel (press twice)',
499 help : 'restart kernel (press twice)',
500 help_index : 'hb',
500 help_index : 'hb',
501 count: 2,
501 count: 2,
502 handler : function (event) {
502 handler : function (event) {
503 IPython.notebook.restart_kernel();
503 IPython.notebook.restart_kernel();
504 return false;
504 return false;
505 }
505 }
506 },
506 },
507 'h' : {
507 'h' : {
508 help : 'keyboard shortcuts',
508 help : 'keyboard shortcuts',
509 help_index : 'ge',
509 help_index : 'ge',
510 handler : function (event) {
510 handler : function (event) {
511 IPython.quick_help.show_keyboard_shortcuts();
511 IPython.quick_help.show_keyboard_shortcuts();
512 return false;
512 return false;
513 }
513 }
514 },
514 },
515 'z' : {
515 'z' : {
516 help : 'undo last delete',
516 help : 'undo last delete',
517 help_index : 'ei',
517 help_index : 'ei',
518 handler : function (event) {
518 handler : function (event) {
519 IPython.notebook.undelete_cell();
519 IPython.notebook.undelete_cell();
520 return false;
520 return false;
521 }
521 }
522 },
522 },
523 'shift+m' : {
523 'shift+m' : {
524 help : 'merge cell below',
524 help : 'merge cell below',
525 help_index : 'ek',
525 help_index : 'ek',
526 handler : function (event) {
526 handler : function (event) {
527 IPython.notebook.merge_cell_below();
527 IPython.notebook.merge_cell_below();
528 return false;
528 return false;
529 }
529 }
530 },
530 },
531 'q' : {
531 'q' : {
532 help : 'close pager',
532 help : 'close pager',
533 help_index : 'gd',
533 help_index : 'gd',
534 handler : function (event) {
534 handler : function (event) {
535 IPython.pager.collapse();
535 IPython.pager.collapse();
536 return false;
536 return false;
537 }
537 }
538 },
538 },
539 };
539 };
540
540
541
541
542 // Shortcut manager class
542 // Shortcut manager class
543
543
544 var ShortcutManager = function (delay) {
544 var ShortcutManager = function (delay) {
545 this._shortcuts = {};
545 this._shortcuts = {};
546 this._counts = {};
546 this._counts = {};
547 this._timers = {};
547 this._timers = {};
548 this.delay = delay || 800; // delay in milliseconds
548 this.delay = delay || 800; // delay in milliseconds
549 };
549 };
550
550
551 ShortcutManager.prototype.help = function () {
551 ShortcutManager.prototype.help = function () {
552 var help = [];
552 var help = [];
553 for (var shortcut in this._shortcuts) {
553 for (var shortcut in this._shortcuts) {
554 var help_string = this._shortcuts[shortcut].help;
554 var help_string = this._shortcuts[shortcut].help;
555 var help_index = this._shortcuts[shortcut].help_index;
555 var help_index = this._shortcuts[shortcut].help_index;
556 if (help_string) {
556 if (help_string) {
557 if (platform === 'MacOS') {
557 if (platform === 'MacOS') {
558 shortcut = shortcut.replace('meta', 'cmd');
558 shortcut = shortcut.replace('meta', 'cmd');
559 }
559 }
560 help.push({
560 help.push({
561 shortcut: shortcut,
561 shortcut: shortcut,
562 help: help_string,
562 help: help_string,
563 help_index: help_index}
563 help_index: help_index}
564 );
564 );
565 }
565 }
566 }
566 }
567 help.sort(function (a, b) {
567 help.sort(function (a, b) {
568 if (a.help_index > b.help_index)
568 if (a.help_index > b.help_index)
569 return 1;
569 return 1;
570 if (a.help_index < b.help_index)
570 if (a.help_index < b.help_index)
571 return -1;
571 return -1;
572 return 0;
572 return 0;
573 });
573 });
574 return help;
574 return help;
575 };
575 };
576
576
577 ShortcutManager.prototype.normalize_key = function (key) {
577 ShortcutManager.prototype.normalize_key = function (key) {
578 return inv_keycodes[keycodes[key]];
578 return inv_keycodes[keycodes[key]];
579 };
579 };
580
580
581 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
581 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
582 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
582 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
583 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
583 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
584 var values = shortcut.split("+");
584 var values = shortcut.split("+");
585 if (values.length === 1) {
585 if (values.length === 1) {
586 return this.normalize_key(values[0]);
586 return this.normalize_key(values[0]);
587 } else {
587 } else {
588 var modifiers = values.slice(0,-1);
588 var modifiers = values.slice(0,-1);
589 var key = this.normalize_key(values[values.length-1]);
589 var key = this.normalize_key(values[values.length-1]);
590 modifiers.sort();
590 modifiers.sort();
591 return modifiers.join('+') + '+' + key;
591 return modifiers.join('+') + '+' + key;
592 }
592 }
593 };
593 };
594
594
595 ShortcutManager.prototype.event_to_shortcut = function (event) {
595 ShortcutManager.prototype.event_to_shortcut = function (event) {
596 // Convert a jQuery keyboard event to a strong based keyboard shortcut
596 // Convert a jQuery keyboard event to a strong based keyboard shortcut
597 var shortcut = '';
597 var shortcut = '';
598 var key = inv_keycodes[event.which];
598 var key = inv_keycodes[event.which];
599 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
599 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
600 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
600 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
601 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
601 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
602 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
602 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
603 shortcut += key;
603 shortcut += key;
604 return shortcut;
604 return shortcut;
605 };
605 };
606
606
607 ShortcutManager.prototype.clear_shortcuts = function () {
607 ShortcutManager.prototype.clear_shortcuts = function () {
608 this._shortcuts = {};
608 this._shortcuts = {};
609 };
609 };
610
610
611 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
611 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
612 if (typeof(data) === 'function') {
612 if (typeof(data) === 'function') {
613 data = {help: '', help_index: '', handler: data};
613 data = {help: '', help_index: '', handler: data};
614 }
614 }
615 data.help_index = data.help_index || '';
615 data.help_index = data.help_index || '';
616 data.help = data.help || '';
616 data.help = data.help || '';
617 data.count = data.count || 1;
617 data.count = data.count || 1;
618 if (data.help_index === '') {
618 if (data.help_index === '') {
619 data.help_index = 'zz';
619 data.help_index = 'zz';
620 }
620 }
621 shortcut = this.normalize_shortcut(shortcut);
621 shortcut = this.normalize_shortcut(shortcut);
622 this._counts[shortcut] = 0;
622 this._counts[shortcut] = 0;
623 this._shortcuts[shortcut] = data;
623 this._shortcuts[shortcut] = data;
624 };
624 };
625
625
626 ShortcutManager.prototype.add_shortcuts = function (data) {
626 ShortcutManager.prototype.add_shortcuts = function (data) {
627 for (var shortcut in data) {
627 for (var shortcut in data) {
628 this.add_shortcut(shortcut, data[shortcut]);
628 this.add_shortcut(shortcut, data[shortcut]);
629 }
629 }
630 };
630 };
631
631
632 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
632 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
633 shortcut = this.normalize_shortcut(shortcut);
633 shortcut = this.normalize_shortcut(shortcut);
634 delete this._counts[shortcut];
634 delete this._counts[shortcut];
635 delete this._shortcuts[shortcut];
635 delete this._shortcuts[shortcut];
636 };
636 };
637
637
638 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
638 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
639 var that = this;
639 var that = this;
640 var c = this._counts;
640 var c = this._counts;
641 var t = this._timers;
641 var t = this._timers;
642 var timer = null;
642 var timer = null;
643 if (c[shortcut] === data.count-1) {
643 if (c[shortcut] === data.count-1) {
644 c[shortcut] = 0;
644 c[shortcut] = 0;
645 timer = t[shortcut];
645 timer = t[shortcut];
646 if (timer) {clearTimeout(timer); delete t[shortcut];}
646 if (timer) {clearTimeout(timer); delete t[shortcut];}
647 return data.handler(event);
647 return data.handler(event);
648 } else {
648 } else {
649 c[shortcut] = c[shortcut] + 1;
649 c[shortcut] = c[shortcut] + 1;
650 timer = setTimeout(function () {
650 timer = setTimeout(function () {
651 c[shortcut] = 0;
651 c[shortcut] = 0;
652 }, that.delay);
652 }, that.delay);
653 t[shortcut] = timer;
653 t[shortcut] = timer;
654 }
654 }
655 return false;
655 return false;
656 };
656 };
657
657
658 ShortcutManager.prototype.call_handler = function (event) {
658 ShortcutManager.prototype.call_handler = function (event) {
659 var shortcut = this.event_to_shortcut(event);
659 var shortcut = this.event_to_shortcut(event);
660 var data = this._shortcuts[shortcut];
660 var data = this._shortcuts[shortcut];
661 if (data) {
661 if (data) {
662 var handler = data.handler;
662 var handler = data.handler;
663 if (handler) {
663 if (handler) {
664 if (data.count === 1) {
664 if (data.count === 1) {
665 return handler(event);
665 return handler(event);
666 } else if (data.count > 1) {
666 } else if (data.count > 1) {
667 return this.count_handler(shortcut, event, data);
667 return this.count_handler(shortcut, event, data);
668 }
668 }
669 }
669 }
670 }
670 }
671 return true;
671 return true;
672 };
672 };
673
673
674
674
675 // Main keyboard manager for the notebook
675 // Main keyboard manager for the notebook
676
676
677 var KeyboardManager = function () {
677 var KeyboardManager = function () {
678 this.mode = 'command';
678 this.mode = 'command';
679 this.enabled = true;
679 this.enabled = true;
680 this.bind_events();
680 this.bind_events();
681 this.command_shortcuts = new ShortcutManager();
681 this.command_shortcuts = new ShortcutManager();
682 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
682 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
683 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
683 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
684 this.edit_shortcuts = new ShortcutManager();
684 this.edit_shortcuts = new ShortcutManager();
685 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
685 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
686 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
686 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
687 };
687 };
688
688
689 KeyboardManager.prototype.bind_events = function () {
689 KeyboardManager.prototype.bind_events = function () {
690 var that = this;
690 var that = this;
691 $(document).keydown(function (event) {
691 $(document).keydown(function (event) {
692 return that.handle_keydown(event);
692 return that.handle_keydown(event);
693 });
693 });
694 };
694 };
695
695
696 KeyboardManager.prototype.handle_keydown = function (event) {
696 KeyboardManager.prototype.handle_keydown = function (event) {
697 var notebook = IPython.notebook;
697 var notebook = IPython.notebook;
698
698
699 if (event.which === keycodes.esc) {
699 if (event.which === keycodes.esc) {
700 // Intercept escape at highest level to avoid closing
700 // Intercept escape at highest level to avoid closing
701 // websocket connection with firefox
701 // websocket connection with firefox
702 event.preventDefault();
702 event.preventDefault();
703 }
703 }
704
704
705 if (!this.enabled) {
705 if (!this.enabled) {
706 if (event.which === keycodes.esc) {
706 if (event.which === keycodes.esc) {
707 // ESC
707 // ESC
708 notebook.command_mode();
708 notebook.command_mode();
709 return false;
709 return false;
710 }
710 }
711 return true;
711 return true;
712 }
712 }
713
713
714 if (this.mode === 'edit') {
714 if (this.mode === 'edit') {
715 return this.edit_shortcuts.call_handler(event);
715 return this.edit_shortcuts.call_handler(event);
716 } else if (this.mode === 'command') {
716 } else if (this.mode === 'command') {
717 return this.command_shortcuts.call_handler(event);
717 return this.command_shortcuts.call_handler(event);
718 }
718 }
719 return true;
719 return true;
720 };
720 };
721
721
722 KeyboardManager.prototype.edit_mode = function () {
722 KeyboardManager.prototype.edit_mode = function () {
723 this.last_mode = this.mode;
723 this.last_mode = this.mode;
724 this.mode = 'edit';
724 this.mode = 'edit';
725 };
725 };
726
726
727 KeyboardManager.prototype.command_mode = function () {
727 KeyboardManager.prototype.command_mode = function () {
728 this.last_mode = this.mode;
728 this.last_mode = this.mode;
729 this.mode = 'command';
729 this.mode = 'command';
730 };
730 };
731
731
732 KeyboardManager.prototype.enable = function () {
732 KeyboardManager.prototype.enable = function () {
733 this.enabled = true;
733 this.enabled = true;
734 };
734 };
735
735
736 KeyboardManager.prototype.disable = function () {
736 KeyboardManager.prototype.disable = function () {
737 this.enabled = false;
737 this.enabled = false;
738 };
738 };
739
739
740 KeyboardManager.prototype.register_events = function (e) {
740 KeyboardManager.prototype.register_events = function (e) {
741 var that = this;
741 var that = this;
742 var handle_focus = function () {
742 var handle_focus = function () {
743 that.disable();
743 that.disable();
744 };
744 };
745 var handle_blur = function () {
745 var handle_blur = function () {
746 that.enable();
746 that.enable();
747 };
747 };
748 e.on('focusin', handle_focus);
748 e.on('focusin', handle_focus);
749 e.on('focusout', handle_blur);
749 e.on('focusout', handle_blur);
750 // TODO: Very strange. The focusout event does not seem fire for the
750 // TODO: Very strange. The focusout event does not seem fire for the
751 // bootstrap textboxes on FF25&26... This works around that by
751 // bootstrap textboxes on FF25&26... This works around that by
752 // registering focus and blur events recursively on all inputs within
752 // registering focus and blur events recursively on all inputs within
753 // registered element.
753 // registered element.
754 e.find('input').blur(handle_blur);
754 e.find('input').blur(handle_blur);
755 e.on('DOMNodeInserted', function (event) {
755 e.on('DOMNodeInserted', function (event) {
756 var target = $(event.target);
756 var target = $(event.target);
757 if (target.is('input')) {
757 if (target.is('input')) {
758 target.blur(handle_blur);
758 target.blur(handle_blur);
759 } else {
759 } else {
760 target.find('input').blur(handle_blur);
760 target.find('input').blur(handle_blur);
761 }
761 }
762 });
762 });
763 // There are times (raw_input) where we remove the element from the DOM before
763 // There are times (raw_input) where we remove the element from the DOM before
764 // focusout is called. In this case we bind to the remove event of jQueryUI,
764 // focusout is called. In this case we bind to the remove event of jQueryUI,
765 // which gets triggered upon removal, iff it is focused at the time.
765 // which gets triggered upon removal, iff it is focused at the time.
766 // is_focused must be used to check for the case where an element within
766 // is_focused must be used to check for the case where an element within
767 // the element being removed is focused.
767 // the element being removed is focused.
768 e.on('remove', function () {
768 e.on('remove', function () {
769 if (IPython.utils.is_focused(e[0])) {
769 if (IPython.utils.is_focused(e[0])) {
770 that.enable();
770 that.enable();
771 }
771 }
772 });
772 });
773 };
773 };
774
774
775
775
776 IPython.keycodes = keycodes;
776 IPython.keycodes = keycodes;
777 IPython.inv_keycodes = inv_keycodes;
777 IPython.inv_keycodes = inv_keycodes;
778 IPython.default_common_shortcuts = default_common_shortcuts;
778 IPython.default_common_shortcuts = default_common_shortcuts;
779 IPython.default_edit_shortcuts = default_edit_shortcuts;
779 IPython.default_edit_shortcuts = default_edit_shortcuts;
780 IPython.default_command_shortcuts = default_command_shortcuts;
780 IPython.default_command_shortcuts = default_command_shortcuts;
781 IPython.ShortcutManager = ShortcutManager;
781 IPython.ShortcutManager = ShortcutManager;
782 IPython.KeyboardManager = KeyboardManager;
782 IPython.KeyboardManager = KeyboardManager;
783
783
784 return IPython;
784 return IPython;
785
785
786 }(IPython));
786 }(IPython));
@@ -1,2332 +1,2332 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 this.options = options = options || {};
26 this.options = options = options || {};
27 this.base_url = options.base_url;
27 this.base_url = options.base_url;
28 this.notebook_path = options.notebook_path;
28 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebook_name;
29 this.notebook_name = options.notebook_name;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3; // Increment this when changing the nbformat
56 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 };
61 };
62
62
63 /**
63 /**
64 * Tweak the notebook's CSS style.
64 * Tweak the notebook's CSS style.
65 *
65 *
66 * @method style
66 * @method style
67 */
67 */
68 Notebook.prototype.style = function () {
68 Notebook.prototype.style = function () {
69 $('div#notebook').addClass('border-box-sizing');
69 $('div#notebook').addClass('border-box-sizing');
70 };
70 };
71
71
72 /**
72 /**
73 * Create an HTML and CSS representation of the notebook.
73 * Create an HTML and CSS representation of the notebook.
74 *
74 *
75 * @method create_elements
75 * @method create_elements
76 */
76 */
77 Notebook.prototype.create_elements = function () {
77 Notebook.prototype.create_elements = function () {
78 var that = this;
78 var that = this;
79 this.element.attr('tabindex','-1');
79 this.element.attr('tabindex','-1');
80 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
80 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
81 // We add this end_space div to the end of the notebook div to:
81 // We add this end_space div to the end of the notebook div to:
82 // i) provide a margin between the last cell and the end of the notebook
82 // i) provide a margin between the last cell and the end of the notebook
83 // ii) to prevent the div from scrolling up when the last cell is being
83 // ii) to prevent the div from scrolling up when the last cell is being
84 // edited, but is too low on the page, which browsers will do automatically.
84 // edited, but is too low on the page, which browsers will do automatically.
85 var end_space = $('<div/>').addClass('end_space');
85 var end_space = $('<div/>').addClass('end_space');
86 end_space.dblclick(function (e) {
86 end_space.dblclick(function (e) {
87 var ncells = that.ncells();
87 var ncells = that.ncells();
88 that.insert_cell_below('code',ncells-1);
88 that.insert_cell_below('code',ncells-1);
89 });
89 });
90 this.element.append(this.container);
90 this.element.append(this.container);
91 this.container.append(end_space);
91 this.container.append(end_space);
92 };
92 };
93
93
94 /**
94 /**
95 * Bind JavaScript events: key presses and custom IPython events.
95 * Bind JavaScript events: key presses and custom IPython events.
96 *
96 *
97 * @method bind_events
97 * @method bind_events
98 */
98 */
99 Notebook.prototype.bind_events = function () {
99 Notebook.prototype.bind_events = function () {
100 var that = this;
100 var that = this;
101
101
102 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
102 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
103 var index = that.find_cell_index(data.cell);
103 var index = that.find_cell_index(data.cell);
104 var new_cell = that.insert_cell_below('code',index);
104 var new_cell = that.insert_cell_below('code',index);
105 new_cell.set_text(data.text);
105 new_cell.set_text(data.text);
106 that.dirty = true;
106 that.dirty = true;
107 });
107 });
108
108
109 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
109 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
110 that.dirty = data.value;
110 that.dirty = data.value;
111 });
111 });
112
112
113 $([IPython.events]).on('select.Cell', function (event, data) {
113 $([IPython.events]).on('select.Cell', function (event, data) {
114 var index = that.find_cell_index(data.cell);
114 var index = that.find_cell_index(data.cell);
115 that.select(index);
115 that.select(index);
116 });
116 });
117
117
118 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
118 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
119 that.handle_edit_mode(that.find_cell_index(data.cell));
119 that.handle_edit_mode(that.find_cell_index(data.cell));
120 });
120 });
121
121
122 $([IPython.events]).on('command_mode.Cell', function (event, data) {
122 $([IPython.events]).on('command_mode.Cell', function (event, data) {
123 that.command_mode();
123 that.command_mode();
124 });
124 });
125
125
126 $([IPython.events]).on('status_autorestarting.Kernel', function () {
126 $([IPython.events]).on('status_autorestarting.Kernel', function () {
127 IPython.dialog.modal({
127 IPython.dialog.modal({
128 title: "Kernel Restarting",
128 title: "Kernel Restarting",
129 body: "The kernel appears to have died. It will restart automatically.",
129 body: "The kernel appears to have died. It will restart automatically.",
130 buttons: {
130 buttons: {
131 OK : {
131 OK : {
132 class : "btn-primary"
132 class : "btn-primary"
133 }
133 }
134 }
134 }
135 });
135 });
136 });
136 });
137
137
138 var collapse_time = function (time) {
138 var collapse_time = function (time) {
139 var app_height = $('#ipython-main-app').height(); // content height
139 var app_height = $('#ipython-main-app').height(); // content height
140 var splitter_height = $('div#pager_splitter').outerHeight(true);
140 var splitter_height = $('div#pager_splitter').outerHeight(true);
141 var new_height = app_height - splitter_height;
141 var new_height = app_height - splitter_height;
142 that.element.animate({height : new_height + 'px'}, time);
142 that.element.animate({height : new_height + 'px'}, time);
143 };
143 };
144
144
145 this.element.bind('collapse_pager', function (event, extrap) {
145 this.element.bind('collapse_pager', function (event, extrap) {
146 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
146 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
147 collapse_time(time);
147 collapse_time(time);
148 });
148 });
149
149
150 var expand_time = function (time) {
150 var expand_time = function (time) {
151 var app_height = $('#ipython-main-app').height(); // content height
151 var app_height = $('#ipython-main-app').height(); // content height
152 var splitter_height = $('div#pager_splitter').outerHeight(true);
152 var splitter_height = $('div#pager_splitter').outerHeight(true);
153 var pager_height = $('div#pager').outerHeight(true);
153 var pager_height = $('div#pager').outerHeight(true);
154 var new_height = app_height - pager_height - splitter_height;
154 var new_height = app_height - pager_height - splitter_height;
155 that.element.animate({height : new_height + 'px'}, time);
155 that.element.animate({height : new_height + 'px'}, time);
156 };
156 };
157
157
158 this.element.bind('expand_pager', function (event, extrap) {
158 this.element.bind('expand_pager', function (event, extrap) {
159 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
159 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
160 expand_time(time);
160 expand_time(time);
161 });
161 });
162
162
163 // Firefox 22 broke $(window).on("beforeunload")
163 // Firefox 22 broke $(window).on("beforeunload")
164 // I'm not sure why or how.
164 // I'm not sure why or how.
165 window.onbeforeunload = function (e) {
165 window.onbeforeunload = function (e) {
166 // TODO: Make killing the kernel configurable.
166 // TODO: Make killing the kernel configurable.
167 var kill_kernel = false;
167 var kill_kernel = false;
168 if (kill_kernel) {
168 if (kill_kernel) {
169 that.session.kill_kernel();
169 that.session.kill_kernel();
170 }
170 }
171 // if we are autosaving, trigger an autosave on nav-away.
171 // if we are autosaving, trigger an autosave on nav-away.
172 // still warn, because if we don't the autosave may fail.
172 // still warn, because if we don't the autosave may fail.
173 if (that.dirty) {
173 if (that.dirty) {
174 if ( that.autosave_interval ) {
174 if ( that.autosave_interval ) {
175 // schedule autosave in a timeout
175 // schedule autosave in a timeout
176 // this gives you a chance to forcefully discard changes
176 // this gives you a chance to forcefully discard changes
177 // by reloading the page if you *really* want to.
177 // by reloading the page if you *really* want to.
178 // the timer doesn't start until you *dismiss* the dialog.
178 // the timer doesn't start until you *dismiss* the dialog.
179 setTimeout(function () {
179 setTimeout(function () {
180 if (that.dirty) {
180 if (that.dirty) {
181 that.save_notebook();
181 that.save_notebook();
182 }
182 }
183 }, 1000);
183 }, 1000);
184 return "Autosave in progress, latest changes may be lost.";
184 return "Autosave in progress, latest changes may be lost.";
185 } else {
185 } else {
186 return "Unsaved changes will be lost.";
186 return "Unsaved changes will be lost.";
187 }
187 }
188 }
188 }
189 // Null is the *only* return value that will make the browser not
189 // Null is the *only* return value that will make the browser not
190 // pop up the "don't leave" dialog.
190 // pop up the "don't leave" dialog.
191 return null;
191 return null;
192 };
192 };
193 };
193 };
194
194
195 /**
195 /**
196 * Set the dirty flag, and trigger the set_dirty.Notebook event
196 * Set the dirty flag, and trigger the set_dirty.Notebook event
197 *
197 *
198 * @method set_dirty
198 * @method set_dirty
199 */
199 */
200 Notebook.prototype.set_dirty = function (value) {
200 Notebook.prototype.set_dirty = function (value) {
201 if (value === undefined) {
201 if (value === undefined) {
202 value = true;
202 value = true;
203 }
203 }
204 if (this.dirty == value) {
204 if (this.dirty == value) {
205 return;
205 return;
206 }
206 }
207 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
207 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
208 };
208 };
209
209
210 /**
210 /**
211 * Scroll the top of the page to a given cell.
211 * Scroll the top of the page to a given cell.
212 *
212 *
213 * @method scroll_to_cell
213 * @method scroll_to_cell
214 * @param {Number} cell_number An index of the cell to view
214 * @param {Number} cell_number An index of the cell to view
215 * @param {Number} time Animation time in milliseconds
215 * @param {Number} time Animation time in milliseconds
216 * @return {Number} Pixel offset from the top of the container
216 * @return {Number} Pixel offset from the top of the container
217 */
217 */
218 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
218 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
219 var cells = this.get_cells();
219 var cells = this.get_cells();
220 time = time || 0;
220 time = time || 0;
221 cell_number = Math.min(cells.length-1,cell_number);
221 cell_number = Math.min(cells.length-1,cell_number);
222 cell_number = Math.max(0 ,cell_number);
222 cell_number = Math.max(0 ,cell_number);
223 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
223 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
224 this.element.animate({scrollTop:scroll_value}, time);
224 this.element.animate({scrollTop:scroll_value}, time);
225 return scroll_value;
225 return scroll_value;
226 };
226 };
227
227
228 /**
228 /**
229 * Scroll to the bottom of the page.
229 * Scroll to the bottom of the page.
230 *
230 *
231 * @method scroll_to_bottom
231 * @method scroll_to_bottom
232 */
232 */
233 Notebook.prototype.scroll_to_bottom = function () {
233 Notebook.prototype.scroll_to_bottom = function () {
234 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
234 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
235 };
235 };
236
236
237 /**
237 /**
238 * Scroll to the top of the page.
238 * Scroll to the top of the page.
239 *
239 *
240 * @method scroll_to_top
240 * @method scroll_to_top
241 */
241 */
242 Notebook.prototype.scroll_to_top = function () {
242 Notebook.prototype.scroll_to_top = function () {
243 this.element.animate({scrollTop:0}, 0);
243 this.element.animate({scrollTop:0}, 0);
244 };
244 };
245
245
246 // Edit Notebook metadata
246 // Edit Notebook metadata
247
247
248 Notebook.prototype.edit_metadata = function () {
248 Notebook.prototype.edit_metadata = function () {
249 var that = this;
249 var that = this;
250 IPython.dialog.edit_metadata(this.metadata, function (md) {
250 IPython.dialog.edit_metadata(this.metadata, function (md) {
251 that.metadata = md;
251 that.metadata = md;
252 }, 'Notebook');
252 }, 'Notebook');
253 };
253 };
254
254
255 // Cell indexing, retrieval, etc.
255 // Cell indexing, retrieval, etc.
256
256
257 /**
257 /**
258 * Get all cell elements in the notebook.
258 * Get all cell elements in the notebook.
259 *
259 *
260 * @method get_cell_elements
260 * @method get_cell_elements
261 * @return {jQuery} A selector of all cell elements
261 * @return {jQuery} A selector of all cell elements
262 */
262 */
263 Notebook.prototype.get_cell_elements = function () {
263 Notebook.prototype.get_cell_elements = function () {
264 return this.container.children("div.cell");
264 return this.container.children("div.cell");
265 };
265 };
266
266
267 /**
267 /**
268 * Get a particular cell element.
268 * Get a particular cell element.
269 *
269 *
270 * @method get_cell_element
270 * @method get_cell_element
271 * @param {Number} index An index of a cell to select
271 * @param {Number} index An index of a cell to select
272 * @return {jQuery} A selector of the given cell.
272 * @return {jQuery} A selector of the given cell.
273 */
273 */
274 Notebook.prototype.get_cell_element = function (index) {
274 Notebook.prototype.get_cell_element = function (index) {
275 var result = null;
275 var result = null;
276 var e = this.get_cell_elements().eq(index);
276 var e = this.get_cell_elements().eq(index);
277 if (e.length !== 0) {
277 if (e.length !== 0) {
278 result = e;
278 result = e;
279 }
279 }
280 return result;
280 return result;
281 };
281 };
282
282
283 /**
283 /**
284 * Try to get a particular cell by msg_id.
284 * Try to get a particular cell by msg_id.
285 *
285 *
286 * @method get_msg_cell
286 * @method get_msg_cell
287 * @param {String} msg_id A message UUID
287 * @param {String} msg_id A message UUID
288 * @return {Cell} Cell or null if no cell was found.
288 * @return {Cell} Cell or null if no cell was found.
289 */
289 */
290 Notebook.prototype.get_msg_cell = function (msg_id) {
290 Notebook.prototype.get_msg_cell = function (msg_id) {
291 return IPython.CodeCell.msg_cells[msg_id] || null;
291 return IPython.CodeCell.msg_cells[msg_id] || null;
292 };
292 };
293
293
294 /**
294 /**
295 * Count the cells in this notebook.
295 * Count the cells in this notebook.
296 *
296 *
297 * @method ncells
297 * @method ncells
298 * @return {Number} The number of cells in this notebook
298 * @return {Number} The number of cells in this notebook
299 */
299 */
300 Notebook.prototype.ncells = function () {
300 Notebook.prototype.ncells = function () {
301 return this.get_cell_elements().length;
301 return this.get_cell_elements().length;
302 };
302 };
303
303
304 /**
304 /**
305 * Get all Cell objects in this notebook.
305 * Get all Cell objects in this notebook.
306 *
306 *
307 * @method get_cells
307 * @method get_cells
308 * @return {Array} This notebook's Cell objects
308 * @return {Array} This notebook's Cell objects
309 */
309 */
310 // TODO: we are often calling cells as cells()[i], which we should optimize
310 // TODO: we are often calling cells as cells()[i], which we should optimize
311 // to cells(i) or a new method.
311 // to cells(i) or a new method.
312 Notebook.prototype.get_cells = function () {
312 Notebook.prototype.get_cells = function () {
313 return this.get_cell_elements().toArray().map(function (e) {
313 return this.get_cell_elements().toArray().map(function (e) {
314 return $(e).data("cell");
314 return $(e).data("cell");
315 });
315 });
316 };
316 };
317
317
318 /**
318 /**
319 * Get a Cell object from this notebook.
319 * Get a Cell object from this notebook.
320 *
320 *
321 * @method get_cell
321 * @method get_cell
322 * @param {Number} index An index of a cell to retrieve
322 * @param {Number} index An index of a cell to retrieve
323 * @return {Cell} A particular cell
323 * @return {Cell} A particular cell
324 */
324 */
325 Notebook.prototype.get_cell = function (index) {
325 Notebook.prototype.get_cell = function (index) {
326 var result = null;
326 var result = null;
327 var ce = this.get_cell_element(index);
327 var ce = this.get_cell_element(index);
328 if (ce !== null) {
328 if (ce !== null) {
329 result = ce.data('cell');
329 result = ce.data('cell');
330 }
330 }
331 return result;
331 return result;
332 };
332 };
333
333
334 /**
334 /**
335 * Get the cell below a given cell.
335 * Get the cell below a given cell.
336 *
336 *
337 * @method get_next_cell
337 * @method get_next_cell
338 * @param {Cell} cell The provided cell
338 * @param {Cell} cell The provided cell
339 * @return {Cell} The next cell
339 * @return {Cell} The next cell
340 */
340 */
341 Notebook.prototype.get_next_cell = function (cell) {
341 Notebook.prototype.get_next_cell = function (cell) {
342 var result = null;
342 var result = null;
343 var index = this.find_cell_index(cell);
343 var index = this.find_cell_index(cell);
344 if (this.is_valid_cell_index(index+1)) {
344 if (this.is_valid_cell_index(index+1)) {
345 result = this.get_cell(index+1);
345 result = this.get_cell(index+1);
346 }
346 }
347 return result;
347 return result;
348 };
348 };
349
349
350 /**
350 /**
351 * Get the cell above a given cell.
351 * Get the cell above a given cell.
352 *
352 *
353 * @method get_prev_cell
353 * @method get_prev_cell
354 * @param {Cell} cell The provided cell
354 * @param {Cell} cell The provided cell
355 * @return {Cell} The previous cell
355 * @return {Cell} The previous cell
356 */
356 */
357 Notebook.prototype.get_prev_cell = function (cell) {
357 Notebook.prototype.get_prev_cell = function (cell) {
358 // TODO: off-by-one
358 // TODO: off-by-one
359 // nb.get_prev_cell(nb.get_cell(1)) is null
359 // nb.get_prev_cell(nb.get_cell(1)) is null
360 var result = null;
360 var result = null;
361 var index = this.find_cell_index(cell);
361 var index = this.find_cell_index(cell);
362 if (index !== null && index > 1) {
362 if (index !== null && index > 1) {
363 result = this.get_cell(index-1);
363 result = this.get_cell(index-1);
364 }
364 }
365 return result;
365 return result;
366 };
366 };
367
367
368 /**
368 /**
369 * Get the numeric index of a given cell.
369 * Get the numeric index of a given cell.
370 *
370 *
371 * @method find_cell_index
371 * @method find_cell_index
372 * @param {Cell} cell The provided cell
372 * @param {Cell} cell The provided cell
373 * @return {Number} The cell's numeric index
373 * @return {Number} The cell's numeric index
374 */
374 */
375 Notebook.prototype.find_cell_index = function (cell) {
375 Notebook.prototype.find_cell_index = function (cell) {
376 var result = null;
376 var result = null;
377 this.get_cell_elements().filter(function (index) {
377 this.get_cell_elements().filter(function (index) {
378 if ($(this).data("cell") === cell) {
378 if ($(this).data("cell") === cell) {
379 result = index;
379 result = index;
380 }
380 }
381 });
381 });
382 return result;
382 return result;
383 };
383 };
384
384
385 /**
385 /**
386 * Get a given index , or the selected index if none is provided.
386 * Get a given index , or the selected index if none is provided.
387 *
387 *
388 * @method index_or_selected
388 * @method index_or_selected
389 * @param {Number} index A cell's index
389 * @param {Number} index A cell's index
390 * @return {Number} The given index, or selected index if none is provided.
390 * @return {Number} The given index, or selected index if none is provided.
391 */
391 */
392 Notebook.prototype.index_or_selected = function (index) {
392 Notebook.prototype.index_or_selected = function (index) {
393 var i;
393 var i;
394 if (index === undefined || index === null) {
394 if (index === undefined || index === null) {
395 i = this.get_selected_index();
395 i = this.get_selected_index();
396 if (i === null) {
396 if (i === null) {
397 i = 0;
397 i = 0;
398 }
398 }
399 } else {
399 } else {
400 i = index;
400 i = index;
401 }
401 }
402 return i;
402 return i;
403 };
403 };
404
404
405 /**
405 /**
406 * Get the currently selected cell.
406 * Get the currently selected cell.
407 * @method get_selected_cell
407 * @method get_selected_cell
408 * @return {Cell} The selected cell
408 * @return {Cell} The selected cell
409 */
409 */
410 Notebook.prototype.get_selected_cell = function () {
410 Notebook.prototype.get_selected_cell = function () {
411 var index = this.get_selected_index();
411 var index = this.get_selected_index();
412 return this.get_cell(index);
412 return this.get_cell(index);
413 };
413 };
414
414
415 /**
415 /**
416 * Check whether a cell index is valid.
416 * Check whether a cell index is valid.
417 *
417 *
418 * @method is_valid_cell_index
418 * @method is_valid_cell_index
419 * @param {Number} index A cell index
419 * @param {Number} index A cell index
420 * @return True if the index is valid, false otherwise
420 * @return True if the index is valid, false otherwise
421 */
421 */
422 Notebook.prototype.is_valid_cell_index = function (index) {
422 Notebook.prototype.is_valid_cell_index = function (index) {
423 if (index !== null && index >= 0 && index < this.ncells()) {
423 if (index !== null && index >= 0 && index < this.ncells()) {
424 return true;
424 return true;
425 } else {
425 } else {
426 return false;
426 return false;
427 }
427 }
428 };
428 };
429
429
430 /**
430 /**
431 * Get the index of the currently selected cell.
431 * Get the index of the currently selected cell.
432
432
433 * @method get_selected_index
433 * @method get_selected_index
434 * @return {Number} The selected cell's numeric index
434 * @return {Number} The selected cell's numeric index
435 */
435 */
436 Notebook.prototype.get_selected_index = function () {
436 Notebook.prototype.get_selected_index = function () {
437 var result = null;
437 var result = null;
438 this.get_cell_elements().filter(function (index) {
438 this.get_cell_elements().filter(function (index) {
439 if ($(this).data("cell").selected === true) {
439 if ($(this).data("cell").selected === true) {
440 result = index;
440 result = index;
441 }
441 }
442 });
442 });
443 return result;
443 return result;
444 };
444 };
445
445
446
446
447 // Cell selection.
447 // Cell selection.
448
448
449 /**
449 /**
450 * Programmatically select a cell.
450 * Programmatically select a cell.
451 *
451 *
452 * @method select
452 * @method select
453 * @param {Number} index A cell's index
453 * @param {Number} index A cell's index
454 * @return {Notebook} This notebook
454 * @return {Notebook} This notebook
455 */
455 */
456 Notebook.prototype.select = function (index) {
456 Notebook.prototype.select = function (index) {
457 if (this.is_valid_cell_index(index)) {
457 if (this.is_valid_cell_index(index)) {
458 var sindex = this.get_selected_index();
458 var sindex = this.get_selected_index();
459 if (sindex !== null && index !== sindex) {
459 if (sindex !== null && index !== sindex) {
460 this.get_cell(sindex).unselect();
460 this.get_cell(sindex).unselect();
461 }
461 }
462 var cell = this.get_cell(index);
462 var cell = this.get_cell(index);
463 cell.select();
463 cell.select();
464 if (cell.cell_type === 'heading') {
464 if (cell.cell_type === 'heading') {
465 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
465 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
466 {'cell_type':cell.cell_type,level:cell.level}
466 {'cell_type':cell.cell_type,level:cell.level}
467 );
467 );
468 } else {
468 } else {
469 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
469 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
470 {'cell_type':cell.cell_type}
470 {'cell_type':cell.cell_type}
471 );
471 );
472 }
472 }
473 }
473 }
474 return this;
474 return this;
475 };
475 };
476
476
477 /**
477 /**
478 * Programmatically select the next cell.
478 * Programmatically select the next cell.
479 *
479 *
480 * @method select_next
480 * @method select_next
481 * @return {Notebook} This notebook
481 * @return {Notebook} This notebook
482 */
482 */
483 Notebook.prototype.select_next = function () {
483 Notebook.prototype.select_next = function () {
484 var index = this.get_selected_index();
484 var index = this.get_selected_index();
485 this.select(index+1);
485 this.select(index+1);
486 return this;
486 return this;
487 };
487 };
488
488
489 /**
489 /**
490 * Programmatically select the previous cell.
490 * Programmatically select the previous cell.
491 *
491 *
492 * @method select_prev
492 * @method select_prev
493 * @return {Notebook} This notebook
493 * @return {Notebook} This notebook
494 */
494 */
495 Notebook.prototype.select_prev = function () {
495 Notebook.prototype.select_prev = function () {
496 var index = this.get_selected_index();
496 var index = this.get_selected_index();
497 this.select(index-1);
497 this.select(index-1);
498 return this;
498 return this;
499 };
499 };
500
500
501
501
502 // Edit/Command mode
502 // Edit/Command mode
503
503
504 /**
504 /**
505 * Gets the index of the cell that is in edit mode.
505 * Gets the index of the cell that is in edit mode.
506 *
506 *
507 * @method get_edit_index
507 * @method get_edit_index
508 *
508 *
509 * @return index {int}
509 * @return index {int}
510 **/
510 **/
511 Notebook.prototype.get_edit_index = function () {
511 Notebook.prototype.get_edit_index = function () {
512 var result = null;
512 var result = null;
513 this.get_cell_elements().filter(function (index) {
513 this.get_cell_elements().filter(function (index) {
514 if ($(this).data("cell").mode === 'edit') {
514 if ($(this).data("cell").mode === 'edit') {
515 result = index;
515 result = index;
516 }
516 }
517 });
517 });
518 return result;
518 return result;
519 };
519 };
520
520
521 /**
521 /**
522 * Make the notebook enter command mode.
522 * Make the notebook enter command mode.
523 *
523 *
524 * @method command_mode
524 * @method command_mode
525 **/
525 **/
526 Notebook.prototype.command_mode = function () {
526 Notebook.prototype.command_mode = function () {
527 // Make sure there isn't an edit mode cell lingering around.
527 // Make sure there isn't an edit mode cell lingering around.
528 var cell = this.get_cell(this.get_edit_index());
528 var cell = this.get_cell(this.get_edit_index());
529 if (cell) {
529 if (cell) {
530 cell.command_mode();
530 cell.command_mode();
531 }
531 }
532
532
533 // Notify the keyboard manager if this is a change of mode for the
533 // Notify the keyboard manager if this is a change of mode for the
534 // notebook as a whole.
534 // notebook as a whole.
535 if (this.mode !== 'command') {
535 if (this.mode !== 'command') {
536 this.mode = 'command';
536 this.mode = 'command';
537 $([IPython.events]).trigger('command_mode.Notebook');
537 $([IPython.events]).trigger('command_mode.Notebook');
538 IPython.keyboard_manager.command_mode();
538 IPython.keyboard_manager.command_mode();
539 }
539 }
540 };
540 };
541
541
542 /**
542 /**
543 * Handle when a cell fires it's edit_mode event.
543 * Handle when a cell fires it's edit_mode event.
544 *
544 *
545 * @method handle_edit_mode
545 * @method handle_edit_mode
546 * @param [index] {int} Cell index to select. If no index is provided,
546 * @param [index] {int} Cell index to select. If no index is provided,
547 * the current selected cell is used.
547 * the current selected cell is used.
548 **/
548 **/
549 Notebook.prototype.handle_edit_mode = function (index) {
549 Notebook.prototype.handle_edit_mode = function (index) {
550 // Make sure the cell exists.
550 // Make sure the cell exists.
551 var cell = this.get_cell(index);
551 var cell = this.get_cell(index);
552 if (cell === null) { return; }
552 if (cell === null) { return; }
553
553
554 // Set the cell to edit mode and notify the keyboard manager if this
554 // Set the cell to edit mode and notify the keyboard manager if this
555 // is a change of mode for the notebook as a whole.
555 // is a change of mode for the notebook as a whole.
556 if (this.mode !== 'edit') {
556 if (this.mode !== 'edit') {
557 cell.edit_mode();
557 cell.edit_mode();
558 this.mode = 'edit';
558 this.mode = 'edit';
559 $([IPython.events]).trigger('edit_mode.Notebook');
559 $([IPython.events]).trigger('edit_mode.Notebook');
560 IPython.keyboard_manager.edit_mode();
560 IPython.keyboard_manager.edit_mode();
561 }
561 }
562 };
562 };
563
563
564 /**
564 /**
565 * Make a cell enter edit mode.
565 * Make a cell enter edit mode.
566 *
566 *
567 * @method trigger_edit_mode
567 * @method edit_mode
568 * @param [index] {int} Cell index to select. If no index is provided,
568 * @param [index] {int} Cell index to select. If no index is provided,
569 * the current selected cell is used.
569 * the current selected cell is used.
570 **/
570 **/
571 Notebook.prototype.trigger_edit_mode = function (index) {
571 Notebook.prototype.edit_mode = function (index) {
572 if (index===undefined) {
572 if (index===undefined) {
573 index = this.get_selected_index();
573 index = this.get_selected_index();
574 }
574 }
575 // Make sure the cell exists.
575 // Make sure the cell exists.
576 var cell = this.get_cell(index);
576 var cell = this.get_cell(index);
577 if (cell === null) { return; }
577 if (cell === null) { return; }
578 if (cell.mode != 'edit') {
578 if (cell.mode != 'edit') {
579 cell.unrender();
579 cell.unrender();
580 cell.focus_editor();
580 cell.focus_editor();
581 }
581 }
582 };
582 };
583
583
584 /**
584 /**
585 * Focus the currently selected cell.
585 * Focus the currently selected cell.
586 *
586 *
587 * @method focus_cell
587 * @method focus_cell
588 **/
588 **/
589 Notebook.prototype.focus_cell = function () {
589 Notebook.prototype.focus_cell = function () {
590 var cell = this.get_selected_cell();
590 var cell = this.get_selected_cell();
591 if (cell === null) {return;} // No cell is selected
591 if (cell === null) {return;} // No cell is selected
592 cell.focus_cell();
592 cell.focus_cell();
593 };
593 };
594
594
595 // Cell movement
595 // Cell movement
596
596
597 /**
597 /**
598 * Move given (or selected) cell up and select it.
598 * Move given (or selected) cell up and select it.
599 *
599 *
600 * @method move_cell_up
600 * @method move_cell_up
601 * @param [index] {integer} cell index
601 * @param [index] {integer} cell index
602 * @return {Notebook} This notebook
602 * @return {Notebook} This notebook
603 **/
603 **/
604 Notebook.prototype.move_cell_up = function (index) {
604 Notebook.prototype.move_cell_up = function (index) {
605 var i = this.index_or_selected(index);
605 var i = this.index_or_selected(index);
606 if (this.is_valid_cell_index(i) && i > 0) {
606 if (this.is_valid_cell_index(i) && i > 0) {
607 var pivot = this.get_cell_element(i-1);
607 var pivot = this.get_cell_element(i-1);
608 var tomove = this.get_cell_element(i);
608 var tomove = this.get_cell_element(i);
609 if (pivot !== null && tomove !== null) {
609 if (pivot !== null && tomove !== null) {
610 tomove.detach();
610 tomove.detach();
611 pivot.before(tomove);
611 pivot.before(tomove);
612 this.select(i-1);
612 this.select(i-1);
613 var cell = this.get_selected_cell();
613 var cell = this.get_selected_cell();
614 cell.focus_cell();
614 cell.focus_cell();
615 }
615 }
616 this.set_dirty(true);
616 this.set_dirty(true);
617 }
617 }
618 return this;
618 return this;
619 };
619 };
620
620
621
621
622 /**
622 /**
623 * Move given (or selected) cell down and select it
623 * Move given (or selected) cell down and select it
624 *
624 *
625 * @method move_cell_down
625 * @method move_cell_down
626 * @param [index] {integer} cell index
626 * @param [index] {integer} cell index
627 * @return {Notebook} This notebook
627 * @return {Notebook} This notebook
628 **/
628 **/
629 Notebook.prototype.move_cell_down = function (index) {
629 Notebook.prototype.move_cell_down = function (index) {
630 var i = this.index_or_selected(index);
630 var i = this.index_or_selected(index);
631 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
631 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
632 var pivot = this.get_cell_element(i+1);
632 var pivot = this.get_cell_element(i+1);
633 var tomove = this.get_cell_element(i);
633 var tomove = this.get_cell_element(i);
634 if (pivot !== null && tomove !== null) {
634 if (pivot !== null && tomove !== null) {
635 tomove.detach();
635 tomove.detach();
636 pivot.after(tomove);
636 pivot.after(tomove);
637 this.select(i+1);
637 this.select(i+1);
638 var cell = this.get_selected_cell();
638 var cell = this.get_selected_cell();
639 cell.focus_cell();
639 cell.focus_cell();
640 }
640 }
641 }
641 }
642 this.set_dirty();
642 this.set_dirty();
643 return this;
643 return this;
644 };
644 };
645
645
646
646
647 // Insertion, deletion.
647 // Insertion, deletion.
648
648
649 /**
649 /**
650 * Delete a cell from the notebook.
650 * Delete a cell from the notebook.
651 *
651 *
652 * @method delete_cell
652 * @method delete_cell
653 * @param [index] A cell's numeric index
653 * @param [index] A cell's numeric index
654 * @return {Notebook} This notebook
654 * @return {Notebook} This notebook
655 */
655 */
656 Notebook.prototype.delete_cell = function (index) {
656 Notebook.prototype.delete_cell = function (index) {
657 var i = this.index_or_selected(index);
657 var i = this.index_or_selected(index);
658 var cell = this.get_selected_cell();
658 var cell = this.get_selected_cell();
659 this.undelete_backup = cell.toJSON();
659 this.undelete_backup = cell.toJSON();
660 $('#undelete_cell').removeClass('disabled');
660 $('#undelete_cell').removeClass('disabled');
661 if (this.is_valid_cell_index(i)) {
661 if (this.is_valid_cell_index(i)) {
662 var old_ncells = this.ncells();
662 var old_ncells = this.ncells();
663 var ce = this.get_cell_element(i);
663 var ce = this.get_cell_element(i);
664 ce.remove();
664 ce.remove();
665 if (i === 0) {
665 if (i === 0) {
666 // Always make sure we have at least one cell.
666 // Always make sure we have at least one cell.
667 if (old_ncells === 1) {
667 if (old_ncells === 1) {
668 this.insert_cell_below('code');
668 this.insert_cell_below('code');
669 }
669 }
670 this.select(0);
670 this.select(0);
671 this.undelete_index = 0;
671 this.undelete_index = 0;
672 this.undelete_below = false;
672 this.undelete_below = false;
673 } else if (i === old_ncells-1 && i !== 0) {
673 } else if (i === old_ncells-1 && i !== 0) {
674 this.select(i-1);
674 this.select(i-1);
675 this.undelete_index = i - 1;
675 this.undelete_index = i - 1;
676 this.undelete_below = true;
676 this.undelete_below = true;
677 } else {
677 } else {
678 this.select(i);
678 this.select(i);
679 this.undelete_index = i;
679 this.undelete_index = i;
680 this.undelete_below = false;
680 this.undelete_below = false;
681 }
681 }
682 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
682 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
683 this.set_dirty(true);
683 this.set_dirty(true);
684 }
684 }
685 return this;
685 return this;
686 };
686 };
687
687
688 /**
688 /**
689 * Restore the most recently deleted cell.
689 * Restore the most recently deleted cell.
690 *
690 *
691 * @method undelete
691 * @method undelete
692 */
692 */
693 Notebook.prototype.undelete_cell = function() {
693 Notebook.prototype.undelete_cell = function() {
694 if (this.undelete_backup !== null && this.undelete_index !== null) {
694 if (this.undelete_backup !== null && this.undelete_index !== null) {
695 var current_index = this.get_selected_index();
695 var current_index = this.get_selected_index();
696 if (this.undelete_index < current_index) {
696 if (this.undelete_index < current_index) {
697 current_index = current_index + 1;
697 current_index = current_index + 1;
698 }
698 }
699 if (this.undelete_index >= this.ncells()) {
699 if (this.undelete_index >= this.ncells()) {
700 this.select(this.ncells() - 1);
700 this.select(this.ncells() - 1);
701 }
701 }
702 else {
702 else {
703 this.select(this.undelete_index);
703 this.select(this.undelete_index);
704 }
704 }
705 var cell_data = this.undelete_backup;
705 var cell_data = this.undelete_backup;
706 var new_cell = null;
706 var new_cell = null;
707 if (this.undelete_below) {
707 if (this.undelete_below) {
708 new_cell = this.insert_cell_below(cell_data.cell_type);
708 new_cell = this.insert_cell_below(cell_data.cell_type);
709 } else {
709 } else {
710 new_cell = this.insert_cell_above(cell_data.cell_type);
710 new_cell = this.insert_cell_above(cell_data.cell_type);
711 }
711 }
712 new_cell.fromJSON(cell_data);
712 new_cell.fromJSON(cell_data);
713 if (this.undelete_below) {
713 if (this.undelete_below) {
714 this.select(current_index+1);
714 this.select(current_index+1);
715 } else {
715 } else {
716 this.select(current_index);
716 this.select(current_index);
717 }
717 }
718 this.undelete_backup = null;
718 this.undelete_backup = null;
719 this.undelete_index = null;
719 this.undelete_index = null;
720 }
720 }
721 $('#undelete_cell').addClass('disabled');
721 $('#undelete_cell').addClass('disabled');
722 };
722 };
723
723
724 /**
724 /**
725 * Insert a cell so that after insertion the cell is at given index.
725 * Insert a cell so that after insertion the cell is at given index.
726 *
726 *
727 * Similar to insert_above, but index parameter is mandatory
727 * Similar to insert_above, but index parameter is mandatory
728 *
728 *
729 * Index will be brought back into the accissible range [0,n]
729 * Index will be brought back into the accissible range [0,n]
730 *
730 *
731 * @method insert_cell_at_index
731 * @method insert_cell_at_index
732 * @param type {string} in ['code','markdown','heading']
732 * @param type {string} in ['code','markdown','heading']
733 * @param [index] {int} a valid index where to inser cell
733 * @param [index] {int} a valid index where to inser cell
734 *
734 *
735 * @return cell {cell|null} created cell or null
735 * @return cell {cell|null} created cell or null
736 **/
736 **/
737 Notebook.prototype.insert_cell_at_index = function(type, index){
737 Notebook.prototype.insert_cell_at_index = function(type, index){
738
738
739 var ncells = this.ncells();
739 var ncells = this.ncells();
740 index = Math.min(index,ncells);
740 index = Math.min(index,ncells);
741 index = Math.max(index,0);
741 index = Math.max(index,0);
742 var cell = null;
742 var cell = null;
743
743
744 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
744 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
745 if (type === 'code') {
745 if (type === 'code') {
746 cell = new IPython.CodeCell(this.kernel);
746 cell = new IPython.CodeCell(this.kernel);
747 cell.set_input_prompt();
747 cell.set_input_prompt();
748 } else if (type === 'markdown') {
748 } else if (type === 'markdown') {
749 cell = new IPython.MarkdownCell();
749 cell = new IPython.MarkdownCell();
750 } else if (type === 'raw') {
750 } else if (type === 'raw') {
751 cell = new IPython.RawCell();
751 cell = new IPython.RawCell();
752 } else if (type === 'heading') {
752 } else if (type === 'heading') {
753 cell = new IPython.HeadingCell();
753 cell = new IPython.HeadingCell();
754 }
754 }
755
755
756 if(this._insert_element_at_index(cell.element,index)) {
756 if(this._insert_element_at_index(cell.element,index)) {
757 cell.render();
757 cell.render();
758 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
758 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
759 cell.refresh();
759 cell.refresh();
760 // We used to select the cell after we refresh it, but there
760 // We used to select the cell after we refresh it, but there
761 // are now cases were this method is called where select is
761 // are now cases were this method is called where select is
762 // not appropriate. The selection logic should be handled by the
762 // not appropriate. The selection logic should be handled by the
763 // caller of the the top level insert_cell methods.
763 // caller of the the top level insert_cell methods.
764 this.set_dirty(true);
764 this.set_dirty(true);
765 }
765 }
766 }
766 }
767 return cell;
767 return cell;
768
768
769 };
769 };
770
770
771 /**
771 /**
772 * Insert an element at given cell index.
772 * Insert an element at given cell index.
773 *
773 *
774 * @method _insert_element_at_index
774 * @method _insert_element_at_index
775 * @param element {dom element} a cell element
775 * @param element {dom element} a cell element
776 * @param [index] {int} a valid index where to inser cell
776 * @param [index] {int} a valid index where to inser cell
777 * @private
777 * @private
778 *
778 *
779 * return true if everything whent fine.
779 * return true if everything whent fine.
780 **/
780 **/
781 Notebook.prototype._insert_element_at_index = function(element, index){
781 Notebook.prototype._insert_element_at_index = function(element, index){
782 if (element === undefined){
782 if (element === undefined){
783 return false;
783 return false;
784 }
784 }
785
785
786 var ncells = this.ncells();
786 var ncells = this.ncells();
787
787
788 if (ncells === 0) {
788 if (ncells === 0) {
789 // special case append if empty
789 // special case append if empty
790 this.element.find('div.end_space').before(element);
790 this.element.find('div.end_space').before(element);
791 } else if ( ncells === index ) {
791 } else if ( ncells === index ) {
792 // special case append it the end, but not empty
792 // special case append it the end, but not empty
793 this.get_cell_element(index-1).after(element);
793 this.get_cell_element(index-1).after(element);
794 } else if (this.is_valid_cell_index(index)) {
794 } else if (this.is_valid_cell_index(index)) {
795 // otherwise always somewhere to append to
795 // otherwise always somewhere to append to
796 this.get_cell_element(index).before(element);
796 this.get_cell_element(index).before(element);
797 } else {
797 } else {
798 return false;
798 return false;
799 }
799 }
800
800
801 if (this.undelete_index !== null && index <= this.undelete_index) {
801 if (this.undelete_index !== null && index <= this.undelete_index) {
802 this.undelete_index = this.undelete_index + 1;
802 this.undelete_index = this.undelete_index + 1;
803 this.set_dirty(true);
803 this.set_dirty(true);
804 }
804 }
805 return true;
805 return true;
806 };
806 };
807
807
808 /**
808 /**
809 * Insert a cell of given type above given index, or at top
809 * Insert a cell of given type above given index, or at top
810 * of notebook if index smaller than 0.
810 * of notebook if index smaller than 0.
811 *
811 *
812 * default index value is the one of currently selected cell
812 * default index value is the one of currently selected cell
813 *
813 *
814 * @method insert_cell_above
814 * @method insert_cell_above
815 * @param type {string} cell type
815 * @param type {string} cell type
816 * @param [index] {integer}
816 * @param [index] {integer}
817 *
817 *
818 * @return handle to created cell or null
818 * @return handle to created cell or null
819 **/
819 **/
820 Notebook.prototype.insert_cell_above = function (type, index) {
820 Notebook.prototype.insert_cell_above = function (type, index) {
821 index = this.index_or_selected(index);
821 index = this.index_or_selected(index);
822 return this.insert_cell_at_index(type, index);
822 return this.insert_cell_at_index(type, index);
823 };
823 };
824
824
825 /**
825 /**
826 * Insert a cell of given type below given index, or at bottom
826 * Insert a cell of given type below given index, or at bottom
827 * of notebook if index greater thatn number of cell
827 * of notebook if index greater thatn number of cell
828 *
828 *
829 * default index value is the one of currently selected cell
829 * default index value is the one of currently selected cell
830 *
830 *
831 * @method insert_cell_below
831 * @method insert_cell_below
832 * @param type {string} cell type
832 * @param type {string} cell type
833 * @param [index] {integer}
833 * @param [index] {integer}
834 *
834 *
835 * @return handle to created cell or null
835 * @return handle to created cell or null
836 *
836 *
837 **/
837 **/
838 Notebook.prototype.insert_cell_below = function (type, index) {
838 Notebook.prototype.insert_cell_below = function (type, index) {
839 index = this.index_or_selected(index);
839 index = this.index_or_selected(index);
840 return this.insert_cell_at_index(type, index+1);
840 return this.insert_cell_at_index(type, index+1);
841 };
841 };
842
842
843
843
844 /**
844 /**
845 * Insert cell at end of notebook
845 * Insert cell at end of notebook
846 *
846 *
847 * @method insert_cell_at_bottom
847 * @method insert_cell_at_bottom
848 * @param {String} type cell type
848 * @param {String} type cell type
849 *
849 *
850 * @return the added cell; or null
850 * @return the added cell; or null
851 **/
851 **/
852 Notebook.prototype.insert_cell_at_bottom = function (type){
852 Notebook.prototype.insert_cell_at_bottom = function (type){
853 var len = this.ncells();
853 var len = this.ncells();
854 return this.insert_cell_below(type,len-1);
854 return this.insert_cell_below(type,len-1);
855 };
855 };
856
856
857 /**
857 /**
858 * Turn a cell into a code cell.
858 * Turn a cell into a code cell.
859 *
859 *
860 * @method to_code
860 * @method to_code
861 * @param {Number} [index] A cell's index
861 * @param {Number} [index] A cell's index
862 */
862 */
863 Notebook.prototype.to_code = function (index) {
863 Notebook.prototype.to_code = function (index) {
864 var i = this.index_or_selected(index);
864 var i = this.index_or_selected(index);
865 if (this.is_valid_cell_index(i)) {
865 if (this.is_valid_cell_index(i)) {
866 var source_element = this.get_cell_element(i);
866 var source_element = this.get_cell_element(i);
867 var source_cell = source_element.data("cell");
867 var source_cell = source_element.data("cell");
868 if (!(source_cell instanceof IPython.CodeCell)) {
868 if (!(source_cell instanceof IPython.CodeCell)) {
869 var target_cell = this.insert_cell_below('code',i);
869 var target_cell = this.insert_cell_below('code',i);
870 var text = source_cell.get_text();
870 var text = source_cell.get_text();
871 if (text === source_cell.placeholder) {
871 if (text === source_cell.placeholder) {
872 text = '';
872 text = '';
873 }
873 }
874 target_cell.set_text(text);
874 target_cell.set_text(text);
875 // make this value the starting point, so that we can only undo
875 // make this value the starting point, so that we can only undo
876 // to this state, instead of a blank cell
876 // to this state, instead of a blank cell
877 target_cell.code_mirror.clearHistory();
877 target_cell.code_mirror.clearHistory();
878 source_element.remove();
878 source_element.remove();
879 this.select(i);
879 this.select(i);
880 this.set_dirty(true);
880 this.set_dirty(true);
881 }
881 }
882 }
882 }
883 };
883 };
884
884
885 /**
885 /**
886 * Turn a cell into a Markdown cell.
886 * Turn a cell into a Markdown cell.
887 *
887 *
888 * @method to_markdown
888 * @method to_markdown
889 * @param {Number} [index] A cell's index
889 * @param {Number} [index] A cell's index
890 */
890 */
891 Notebook.prototype.to_markdown = function (index) {
891 Notebook.prototype.to_markdown = function (index) {
892 var i = this.index_or_selected(index);
892 var i = this.index_or_selected(index);
893 if (this.is_valid_cell_index(i)) {
893 if (this.is_valid_cell_index(i)) {
894 var source_element = this.get_cell_element(i);
894 var source_element = this.get_cell_element(i);
895 var source_cell = source_element.data("cell");
895 var source_cell = source_element.data("cell");
896 if (!(source_cell instanceof IPython.MarkdownCell)) {
896 if (!(source_cell instanceof IPython.MarkdownCell)) {
897 var target_cell = this.insert_cell_below('markdown',i);
897 var target_cell = this.insert_cell_below('markdown',i);
898 var text = source_cell.get_text();
898 var text = source_cell.get_text();
899 if (text === source_cell.placeholder) {
899 if (text === source_cell.placeholder) {
900 text = '';
900 text = '';
901 }
901 }
902 // We must show the editor before setting its contents
902 // We must show the editor before setting its contents
903 target_cell.unrender();
903 target_cell.unrender();
904 target_cell.set_text(text);
904 target_cell.set_text(text);
905 // make this value the starting point, so that we can only undo
905 // make this value the starting point, so that we can only undo
906 // to this state, instead of a blank cell
906 // to this state, instead of a blank cell
907 target_cell.code_mirror.clearHistory();
907 target_cell.code_mirror.clearHistory();
908 source_element.remove();
908 source_element.remove();
909 this.select(i);
909 this.select(i);
910 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
910 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
911 target_cell.render();
911 target_cell.render();
912 }
912 }
913 this.set_dirty(true);
913 this.set_dirty(true);
914 }
914 }
915 }
915 }
916 };
916 };
917
917
918 /**
918 /**
919 * Turn a cell into a raw text cell.
919 * Turn a cell into a raw text cell.
920 *
920 *
921 * @method to_raw
921 * @method to_raw
922 * @param {Number} [index] A cell's index
922 * @param {Number} [index] A cell's index
923 */
923 */
924 Notebook.prototype.to_raw = function (index) {
924 Notebook.prototype.to_raw = function (index) {
925 var i = this.index_or_selected(index);
925 var i = this.index_or_selected(index);
926 if (this.is_valid_cell_index(i)) {
926 if (this.is_valid_cell_index(i)) {
927 var source_element = this.get_cell_element(i);
927 var source_element = this.get_cell_element(i);
928 var source_cell = source_element.data("cell");
928 var source_cell = source_element.data("cell");
929 var target_cell = null;
929 var target_cell = null;
930 if (!(source_cell instanceof IPython.RawCell)) {
930 if (!(source_cell instanceof IPython.RawCell)) {
931 target_cell = this.insert_cell_below('raw',i);
931 target_cell = this.insert_cell_below('raw',i);
932 var text = source_cell.get_text();
932 var text = source_cell.get_text();
933 if (text === source_cell.placeholder) {
933 if (text === source_cell.placeholder) {
934 text = '';
934 text = '';
935 }
935 }
936 // We must show the editor before setting its contents
936 // We must show the editor before setting its contents
937 target_cell.unrender();
937 target_cell.unrender();
938 target_cell.set_text(text);
938 target_cell.set_text(text);
939 // make this value the starting point, so that we can only undo
939 // make this value the starting point, so that we can only undo
940 // to this state, instead of a blank cell
940 // to this state, instead of a blank cell
941 target_cell.code_mirror.clearHistory();
941 target_cell.code_mirror.clearHistory();
942 source_element.remove();
942 source_element.remove();
943 this.select(i);
943 this.select(i);
944 this.set_dirty(true);
944 this.set_dirty(true);
945 }
945 }
946 }
946 }
947 };
947 };
948
948
949 /**
949 /**
950 * Turn a cell into a heading cell.
950 * Turn a cell into a heading cell.
951 *
951 *
952 * @method to_heading
952 * @method to_heading
953 * @param {Number} [index] A cell's index
953 * @param {Number} [index] A cell's index
954 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
954 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
955 */
955 */
956 Notebook.prototype.to_heading = function (index, level) {
956 Notebook.prototype.to_heading = function (index, level) {
957 level = level || 1;
957 level = level || 1;
958 var i = this.index_or_selected(index);
958 var i = this.index_or_selected(index);
959 if (this.is_valid_cell_index(i)) {
959 if (this.is_valid_cell_index(i)) {
960 var source_element = this.get_cell_element(i);
960 var source_element = this.get_cell_element(i);
961 var source_cell = source_element.data("cell");
961 var source_cell = source_element.data("cell");
962 var target_cell = null;
962 var target_cell = null;
963 if (source_cell instanceof IPython.HeadingCell) {
963 if (source_cell instanceof IPython.HeadingCell) {
964 source_cell.set_level(level);
964 source_cell.set_level(level);
965 } else {
965 } else {
966 target_cell = this.insert_cell_below('heading',i);
966 target_cell = this.insert_cell_below('heading',i);
967 var text = source_cell.get_text();
967 var text = source_cell.get_text();
968 if (text === source_cell.placeholder) {
968 if (text === source_cell.placeholder) {
969 text = '';
969 text = '';
970 }
970 }
971 // We must show the editor before setting its contents
971 // We must show the editor before setting its contents
972 target_cell.set_level(level);
972 target_cell.set_level(level);
973 target_cell.unrender();
973 target_cell.unrender();
974 target_cell.set_text(text);
974 target_cell.set_text(text);
975 // make this value the starting point, so that we can only undo
975 // make this value the starting point, so that we can only undo
976 // to this state, instead of a blank cell
976 // to this state, instead of a blank cell
977 target_cell.code_mirror.clearHistory();
977 target_cell.code_mirror.clearHistory();
978 source_element.remove();
978 source_element.remove();
979 this.select(i);
979 this.select(i);
980 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
980 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
981 target_cell.render();
981 target_cell.render();
982 }
982 }
983 }
983 }
984 this.set_dirty(true);
984 this.set_dirty(true);
985 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
985 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
986 {'cell_type':'heading',level:level}
986 {'cell_type':'heading',level:level}
987 );
987 );
988 }
988 }
989 };
989 };
990
990
991
991
992 // Cut/Copy/Paste
992 // Cut/Copy/Paste
993
993
994 /**
994 /**
995 * Enable UI elements for pasting cells.
995 * Enable UI elements for pasting cells.
996 *
996 *
997 * @method enable_paste
997 * @method enable_paste
998 */
998 */
999 Notebook.prototype.enable_paste = function () {
999 Notebook.prototype.enable_paste = function () {
1000 var that = this;
1000 var that = this;
1001 if (!this.paste_enabled) {
1001 if (!this.paste_enabled) {
1002 $('#paste_cell_replace').removeClass('disabled')
1002 $('#paste_cell_replace').removeClass('disabled')
1003 .on('click', function () {that.paste_cell_replace();});
1003 .on('click', function () {that.paste_cell_replace();});
1004 $('#paste_cell_above').removeClass('disabled')
1004 $('#paste_cell_above').removeClass('disabled')
1005 .on('click', function () {that.paste_cell_above();});
1005 .on('click', function () {that.paste_cell_above();});
1006 $('#paste_cell_below').removeClass('disabled')
1006 $('#paste_cell_below').removeClass('disabled')
1007 .on('click', function () {that.paste_cell_below();});
1007 .on('click', function () {that.paste_cell_below();});
1008 this.paste_enabled = true;
1008 this.paste_enabled = true;
1009 }
1009 }
1010 };
1010 };
1011
1011
1012 /**
1012 /**
1013 * Disable UI elements for pasting cells.
1013 * Disable UI elements for pasting cells.
1014 *
1014 *
1015 * @method disable_paste
1015 * @method disable_paste
1016 */
1016 */
1017 Notebook.prototype.disable_paste = function () {
1017 Notebook.prototype.disable_paste = function () {
1018 if (this.paste_enabled) {
1018 if (this.paste_enabled) {
1019 $('#paste_cell_replace').addClass('disabled').off('click');
1019 $('#paste_cell_replace').addClass('disabled').off('click');
1020 $('#paste_cell_above').addClass('disabled').off('click');
1020 $('#paste_cell_above').addClass('disabled').off('click');
1021 $('#paste_cell_below').addClass('disabled').off('click');
1021 $('#paste_cell_below').addClass('disabled').off('click');
1022 this.paste_enabled = false;
1022 this.paste_enabled = false;
1023 }
1023 }
1024 };
1024 };
1025
1025
1026 /**
1026 /**
1027 * Cut a cell.
1027 * Cut a cell.
1028 *
1028 *
1029 * @method cut_cell
1029 * @method cut_cell
1030 */
1030 */
1031 Notebook.prototype.cut_cell = function () {
1031 Notebook.prototype.cut_cell = function () {
1032 this.copy_cell();
1032 this.copy_cell();
1033 this.delete_cell();
1033 this.delete_cell();
1034 };
1034 };
1035
1035
1036 /**
1036 /**
1037 * Copy a cell.
1037 * Copy a cell.
1038 *
1038 *
1039 * @method copy_cell
1039 * @method copy_cell
1040 */
1040 */
1041 Notebook.prototype.copy_cell = function () {
1041 Notebook.prototype.copy_cell = function () {
1042 var cell = this.get_selected_cell();
1042 var cell = this.get_selected_cell();
1043 this.clipboard = cell.toJSON();
1043 this.clipboard = cell.toJSON();
1044 this.enable_paste();
1044 this.enable_paste();
1045 };
1045 };
1046
1046
1047 /**
1047 /**
1048 * Replace the selected cell with a cell in the clipboard.
1048 * Replace the selected cell with a cell in the clipboard.
1049 *
1049 *
1050 * @method paste_cell_replace
1050 * @method paste_cell_replace
1051 */
1051 */
1052 Notebook.prototype.paste_cell_replace = function () {
1052 Notebook.prototype.paste_cell_replace = function () {
1053 if (this.clipboard !== null && this.paste_enabled) {
1053 if (this.clipboard !== null && this.paste_enabled) {
1054 var cell_data = this.clipboard;
1054 var cell_data = this.clipboard;
1055 var new_cell = this.insert_cell_above(cell_data.cell_type);
1055 var new_cell = this.insert_cell_above(cell_data.cell_type);
1056 new_cell.fromJSON(cell_data);
1056 new_cell.fromJSON(cell_data);
1057 var old_cell = this.get_next_cell(new_cell);
1057 var old_cell = this.get_next_cell(new_cell);
1058 this.delete_cell(this.find_cell_index(old_cell));
1058 this.delete_cell(this.find_cell_index(old_cell));
1059 this.select(this.find_cell_index(new_cell));
1059 this.select(this.find_cell_index(new_cell));
1060 }
1060 }
1061 };
1061 };
1062
1062
1063 /**
1063 /**
1064 * Paste a cell from the clipboard above the selected cell.
1064 * Paste a cell from the clipboard above the selected cell.
1065 *
1065 *
1066 * @method paste_cell_above
1066 * @method paste_cell_above
1067 */
1067 */
1068 Notebook.prototype.paste_cell_above = function () {
1068 Notebook.prototype.paste_cell_above = function () {
1069 if (this.clipboard !== null && this.paste_enabled) {
1069 if (this.clipboard !== null && this.paste_enabled) {
1070 var cell_data = this.clipboard;
1070 var cell_data = this.clipboard;
1071 var new_cell = this.insert_cell_above(cell_data.cell_type);
1071 var new_cell = this.insert_cell_above(cell_data.cell_type);
1072 new_cell.fromJSON(cell_data);
1072 new_cell.fromJSON(cell_data);
1073 new_cell.focus_cell();
1073 new_cell.focus_cell();
1074 }
1074 }
1075 };
1075 };
1076
1076
1077 /**
1077 /**
1078 * Paste a cell from the clipboard below the selected cell.
1078 * Paste a cell from the clipboard below the selected cell.
1079 *
1079 *
1080 * @method paste_cell_below
1080 * @method paste_cell_below
1081 */
1081 */
1082 Notebook.prototype.paste_cell_below = function () {
1082 Notebook.prototype.paste_cell_below = function () {
1083 if (this.clipboard !== null && this.paste_enabled) {
1083 if (this.clipboard !== null && this.paste_enabled) {
1084 var cell_data = this.clipboard;
1084 var cell_data = this.clipboard;
1085 var new_cell = this.insert_cell_below(cell_data.cell_type);
1085 var new_cell = this.insert_cell_below(cell_data.cell_type);
1086 new_cell.fromJSON(cell_data);
1086 new_cell.fromJSON(cell_data);
1087 new_cell.focus_cell();
1087 new_cell.focus_cell();
1088 }
1088 }
1089 };
1089 };
1090
1090
1091 // Split/merge
1091 // Split/merge
1092
1092
1093 /**
1093 /**
1094 * Split the selected cell into two, at the cursor.
1094 * Split the selected cell into two, at the cursor.
1095 *
1095 *
1096 * @method split_cell
1096 * @method split_cell
1097 */
1097 */
1098 Notebook.prototype.split_cell = function () {
1098 Notebook.prototype.split_cell = function () {
1099 var mdc = IPython.MarkdownCell;
1099 var mdc = IPython.MarkdownCell;
1100 var rc = IPython.RawCell;
1100 var rc = IPython.RawCell;
1101 var cell = this.get_selected_cell();
1101 var cell = this.get_selected_cell();
1102 if (cell.is_splittable()) {
1102 if (cell.is_splittable()) {
1103 var texta = cell.get_pre_cursor();
1103 var texta = cell.get_pre_cursor();
1104 var textb = cell.get_post_cursor();
1104 var textb = cell.get_post_cursor();
1105 if (cell instanceof IPython.CodeCell) {
1105 if (cell instanceof IPython.CodeCell) {
1106 // In this case the operations keep the notebook in its existing mode
1106 // In this case the operations keep the notebook in its existing mode
1107 // so we don't need to do any post-op mode changes.
1107 // so we don't need to do any post-op mode changes.
1108 cell.set_text(textb);
1108 cell.set_text(textb);
1109 var new_cell = this.insert_cell_above('code');
1109 var new_cell = this.insert_cell_above('code');
1110 new_cell.set_text(texta);
1110 new_cell.set_text(texta);
1111 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1111 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1112 // We know cell is !rendered so we can use set_text.
1112 // We know cell is !rendered so we can use set_text.
1113 cell.set_text(textb);
1113 cell.set_text(textb);
1114 var new_cell = this.insert_cell_above(cell.cell_type);
1114 var new_cell = this.insert_cell_above(cell.cell_type);
1115 // Unrender the new cell so we can call set_text.
1115 // Unrender the new cell so we can call set_text.
1116 new_cell.unrender();
1116 new_cell.unrender();
1117 new_cell.set_text(texta);
1117 new_cell.set_text(texta);
1118 }
1118 }
1119 }
1119 }
1120 };
1120 };
1121
1121
1122 /**
1122 /**
1123 * Combine the selected cell into the cell above it.
1123 * Combine the selected cell into the cell above it.
1124 *
1124 *
1125 * @method merge_cell_above
1125 * @method merge_cell_above
1126 */
1126 */
1127 Notebook.prototype.merge_cell_above = function () {
1127 Notebook.prototype.merge_cell_above = function () {
1128 var mdc = IPython.MarkdownCell;
1128 var mdc = IPython.MarkdownCell;
1129 var rc = IPython.RawCell;
1129 var rc = IPython.RawCell;
1130 var index = this.get_selected_index();
1130 var index = this.get_selected_index();
1131 var cell = this.get_cell(index);
1131 var cell = this.get_cell(index);
1132 var render = cell.rendered;
1132 var render = cell.rendered;
1133 if (!cell.is_mergeable()) {
1133 if (!cell.is_mergeable()) {
1134 return;
1134 return;
1135 }
1135 }
1136 if (index > 0) {
1136 if (index > 0) {
1137 var upper_cell = this.get_cell(index-1);
1137 var upper_cell = this.get_cell(index-1);
1138 if (!upper_cell.is_mergeable()) {
1138 if (!upper_cell.is_mergeable()) {
1139 return;
1139 return;
1140 }
1140 }
1141 var upper_text = upper_cell.get_text();
1141 var upper_text = upper_cell.get_text();
1142 var text = cell.get_text();
1142 var text = cell.get_text();
1143 if (cell instanceof IPython.CodeCell) {
1143 if (cell instanceof IPython.CodeCell) {
1144 cell.set_text(upper_text+'\n'+text);
1144 cell.set_text(upper_text+'\n'+text);
1145 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1145 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1146 cell.unrender(); // Must unrender before we set_text.
1146 cell.unrender(); // Must unrender before we set_text.
1147 cell.set_text(upper_text+'\n\n'+text);
1147 cell.set_text(upper_text+'\n\n'+text);
1148 if (render) {
1148 if (render) {
1149 // The rendered state of the final cell should match
1149 // The rendered state of the final cell should match
1150 // that of the original selected cell;
1150 // that of the original selected cell;
1151 cell.render();
1151 cell.render();
1152 }
1152 }
1153 }
1153 }
1154 this.delete_cell(index-1);
1154 this.delete_cell(index-1);
1155 this.select(this.find_cell_index(cell));
1155 this.select(this.find_cell_index(cell));
1156 }
1156 }
1157 };
1157 };
1158
1158
1159 /**
1159 /**
1160 * Combine the selected cell into the cell below it.
1160 * Combine the selected cell into the cell below it.
1161 *
1161 *
1162 * @method merge_cell_below
1162 * @method merge_cell_below
1163 */
1163 */
1164 Notebook.prototype.merge_cell_below = function () {
1164 Notebook.prototype.merge_cell_below = function () {
1165 var mdc = IPython.MarkdownCell;
1165 var mdc = IPython.MarkdownCell;
1166 var rc = IPython.RawCell;
1166 var rc = IPython.RawCell;
1167 var index = this.get_selected_index();
1167 var index = this.get_selected_index();
1168 var cell = this.get_cell(index);
1168 var cell = this.get_cell(index);
1169 var render = cell.rendered;
1169 var render = cell.rendered;
1170 if (!cell.is_mergeable()) {
1170 if (!cell.is_mergeable()) {
1171 return;
1171 return;
1172 }
1172 }
1173 if (index < this.ncells()-1) {
1173 if (index < this.ncells()-1) {
1174 var lower_cell = this.get_cell(index+1);
1174 var lower_cell = this.get_cell(index+1);
1175 if (!lower_cell.is_mergeable()) {
1175 if (!lower_cell.is_mergeable()) {
1176 return;
1176 return;
1177 }
1177 }
1178 var lower_text = lower_cell.get_text();
1178 var lower_text = lower_cell.get_text();
1179 var text = cell.get_text();
1179 var text = cell.get_text();
1180 if (cell instanceof IPython.CodeCell) {
1180 if (cell instanceof IPython.CodeCell) {
1181 cell.set_text(text+'\n'+lower_text);
1181 cell.set_text(text+'\n'+lower_text);
1182 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1182 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1183 cell.unrender(); // Must unrender before we set_text.
1183 cell.unrender(); // Must unrender before we set_text.
1184 cell.set_text(text+'\n\n'+lower_text);
1184 cell.set_text(text+'\n\n'+lower_text);
1185 if (render) {
1185 if (render) {
1186 // The rendered state of the final cell should match
1186 // The rendered state of the final cell should match
1187 // that of the original selected cell;
1187 // that of the original selected cell;
1188 cell.render();
1188 cell.render();
1189 }
1189 }
1190 }
1190 }
1191 this.delete_cell(index+1);
1191 this.delete_cell(index+1);
1192 this.select(this.find_cell_index(cell));
1192 this.select(this.find_cell_index(cell));
1193 }
1193 }
1194 };
1194 };
1195
1195
1196
1196
1197 // Cell collapsing and output clearing
1197 // Cell collapsing and output clearing
1198
1198
1199 /**
1199 /**
1200 * Hide a cell's output.
1200 * Hide a cell's output.
1201 *
1201 *
1202 * @method collapse_output
1202 * @method collapse_output
1203 * @param {Number} index A cell's numeric index
1203 * @param {Number} index A cell's numeric index
1204 */
1204 */
1205 Notebook.prototype.collapse_output = function (index) {
1205 Notebook.prototype.collapse_output = function (index) {
1206 var i = this.index_or_selected(index);
1206 var i = this.index_or_selected(index);
1207 var cell = this.get_cell(i);
1207 var cell = this.get_cell(i);
1208 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1208 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1209 cell.collapse_output();
1209 cell.collapse_output();
1210 this.set_dirty(true);
1210 this.set_dirty(true);
1211 }
1211 }
1212 };
1212 };
1213
1213
1214 /**
1214 /**
1215 * Hide each code cell's output area.
1215 * Hide each code cell's output area.
1216 *
1216 *
1217 * @method collapse_all_output
1217 * @method collapse_all_output
1218 */
1218 */
1219 Notebook.prototype.collapse_all_output = function () {
1219 Notebook.prototype.collapse_all_output = function () {
1220 $.map(this.get_cells(), function (cell, i) {
1220 $.map(this.get_cells(), function (cell, i) {
1221 if (cell instanceof IPython.CodeCell) {
1221 if (cell instanceof IPython.CodeCell) {
1222 cell.collapse_output();
1222 cell.collapse_output();
1223 }
1223 }
1224 });
1224 });
1225 // this should not be set if the `collapse` key is removed from nbformat
1225 // this should not be set if the `collapse` key is removed from nbformat
1226 this.set_dirty(true);
1226 this.set_dirty(true);
1227 };
1227 };
1228
1228
1229 /**
1229 /**
1230 * Show a cell's output.
1230 * Show a cell's output.
1231 *
1231 *
1232 * @method expand_output
1232 * @method expand_output
1233 * @param {Number} index A cell's numeric index
1233 * @param {Number} index A cell's numeric index
1234 */
1234 */
1235 Notebook.prototype.expand_output = function (index) {
1235 Notebook.prototype.expand_output = function (index) {
1236 var i = this.index_or_selected(index);
1236 var i = this.index_or_selected(index);
1237 var cell = this.get_cell(i);
1237 var cell = this.get_cell(i);
1238 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1238 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1239 cell.expand_output();
1239 cell.expand_output();
1240 this.set_dirty(true);
1240 this.set_dirty(true);
1241 }
1241 }
1242 };
1242 };
1243
1243
1244 /**
1244 /**
1245 * Expand each code cell's output area, and remove scrollbars.
1245 * Expand each code cell's output area, and remove scrollbars.
1246 *
1246 *
1247 * @method expand_all_output
1247 * @method expand_all_output
1248 */
1248 */
1249 Notebook.prototype.expand_all_output = function () {
1249 Notebook.prototype.expand_all_output = function () {
1250 $.map(this.get_cells(), function (cell, i) {
1250 $.map(this.get_cells(), function (cell, i) {
1251 if (cell instanceof IPython.CodeCell) {
1251 if (cell instanceof IPython.CodeCell) {
1252 cell.expand_output();
1252 cell.expand_output();
1253 }
1253 }
1254 });
1254 });
1255 // this should not be set if the `collapse` key is removed from nbformat
1255 // this should not be set if the `collapse` key is removed from nbformat
1256 this.set_dirty(true);
1256 this.set_dirty(true);
1257 };
1257 };
1258
1258
1259 /**
1259 /**
1260 * Clear the selected CodeCell's output area.
1260 * Clear the selected CodeCell's output area.
1261 *
1261 *
1262 * @method clear_output
1262 * @method clear_output
1263 * @param {Number} index A cell's numeric index
1263 * @param {Number} index A cell's numeric index
1264 */
1264 */
1265 Notebook.prototype.clear_output = function (index) {
1265 Notebook.prototype.clear_output = function (index) {
1266 var i = this.index_or_selected(index);
1266 var i = this.index_or_selected(index);
1267 var cell = this.get_cell(i);
1267 var cell = this.get_cell(i);
1268 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1268 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1269 cell.clear_output();
1269 cell.clear_output();
1270 this.set_dirty(true);
1270 this.set_dirty(true);
1271 }
1271 }
1272 };
1272 };
1273
1273
1274 /**
1274 /**
1275 * Clear each code cell's output area.
1275 * Clear each code cell's output area.
1276 *
1276 *
1277 * @method clear_all_output
1277 * @method clear_all_output
1278 */
1278 */
1279 Notebook.prototype.clear_all_output = function () {
1279 Notebook.prototype.clear_all_output = function () {
1280 $.map(this.get_cells(), function (cell, i) {
1280 $.map(this.get_cells(), function (cell, i) {
1281 if (cell instanceof IPython.CodeCell) {
1281 if (cell instanceof IPython.CodeCell) {
1282 cell.clear_output();
1282 cell.clear_output();
1283 }
1283 }
1284 });
1284 });
1285 this.set_dirty(true);
1285 this.set_dirty(true);
1286 };
1286 };
1287
1287
1288 /**
1288 /**
1289 * Scroll the selected CodeCell's output area.
1289 * Scroll the selected CodeCell's output area.
1290 *
1290 *
1291 * @method scroll_output
1291 * @method scroll_output
1292 * @param {Number} index A cell's numeric index
1292 * @param {Number} index A cell's numeric index
1293 */
1293 */
1294 Notebook.prototype.scroll_output = function (index) {
1294 Notebook.prototype.scroll_output = function (index) {
1295 var i = this.index_or_selected(index);
1295 var i = this.index_or_selected(index);
1296 var cell = this.get_cell(i);
1296 var cell = this.get_cell(i);
1297 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1297 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1298 cell.scroll_output();
1298 cell.scroll_output();
1299 this.set_dirty(true);
1299 this.set_dirty(true);
1300 }
1300 }
1301 };
1301 };
1302
1302
1303 /**
1303 /**
1304 * Expand each code cell's output area, and add a scrollbar for long output.
1304 * Expand each code cell's output area, and add a scrollbar for long output.
1305 *
1305 *
1306 * @method scroll_all_output
1306 * @method scroll_all_output
1307 */
1307 */
1308 Notebook.prototype.scroll_all_output = function () {
1308 Notebook.prototype.scroll_all_output = function () {
1309 $.map(this.get_cells(), function (cell, i) {
1309 $.map(this.get_cells(), function (cell, i) {
1310 if (cell instanceof IPython.CodeCell) {
1310 if (cell instanceof IPython.CodeCell) {
1311 cell.scroll_output();
1311 cell.scroll_output();
1312 }
1312 }
1313 });
1313 });
1314 // this should not be set if the `collapse` key is removed from nbformat
1314 // this should not be set if the `collapse` key is removed from nbformat
1315 this.set_dirty(true);
1315 this.set_dirty(true);
1316 };
1316 };
1317
1317
1318 /** Toggle whether a cell's output is collapsed or expanded.
1318 /** Toggle whether a cell's output is collapsed or expanded.
1319 *
1319 *
1320 * @method toggle_output
1320 * @method toggle_output
1321 * @param {Number} index A cell's numeric index
1321 * @param {Number} index A cell's numeric index
1322 */
1322 */
1323 Notebook.prototype.toggle_output = function (index) {
1323 Notebook.prototype.toggle_output = function (index) {
1324 var i = this.index_or_selected(index);
1324 var i = this.index_or_selected(index);
1325 var cell = this.get_cell(i);
1325 var cell = this.get_cell(i);
1326 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1326 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1327 cell.toggle_output();
1327 cell.toggle_output();
1328 this.set_dirty(true);
1328 this.set_dirty(true);
1329 }
1329 }
1330 };
1330 };
1331
1331
1332 /**
1332 /**
1333 * Hide/show the output of all cells.
1333 * Hide/show the output of all cells.
1334 *
1334 *
1335 * @method toggle_all_output
1335 * @method toggle_all_output
1336 */
1336 */
1337 Notebook.prototype.toggle_all_output = function () {
1337 Notebook.prototype.toggle_all_output = function () {
1338 $.map(this.get_cells(), function (cell, i) {
1338 $.map(this.get_cells(), function (cell, i) {
1339 if (cell instanceof IPython.CodeCell) {
1339 if (cell instanceof IPython.CodeCell) {
1340 cell.toggle_output();
1340 cell.toggle_output();
1341 }
1341 }
1342 });
1342 });
1343 // this should not be set if the `collapse` key is removed from nbformat
1343 // this should not be set if the `collapse` key is removed from nbformat
1344 this.set_dirty(true);
1344 this.set_dirty(true);
1345 };
1345 };
1346
1346
1347 /**
1347 /**
1348 * Toggle a scrollbar for long cell outputs.
1348 * Toggle a scrollbar for long cell outputs.
1349 *
1349 *
1350 * @method toggle_output_scroll
1350 * @method toggle_output_scroll
1351 * @param {Number} index A cell's numeric index
1351 * @param {Number} index A cell's numeric index
1352 */
1352 */
1353 Notebook.prototype.toggle_output_scroll = function (index) {
1353 Notebook.prototype.toggle_output_scroll = function (index) {
1354 var i = this.index_or_selected(index);
1354 var i = this.index_or_selected(index);
1355 var cell = this.get_cell(i);
1355 var cell = this.get_cell(i);
1356 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1356 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1357 cell.toggle_output_scroll();
1357 cell.toggle_output_scroll();
1358 this.set_dirty(true);
1358 this.set_dirty(true);
1359 }
1359 }
1360 };
1360 };
1361
1361
1362 /**
1362 /**
1363 * Toggle the scrolling of long output on all cells.
1363 * Toggle the scrolling of long output on all cells.
1364 *
1364 *
1365 * @method toggle_all_output_scrolling
1365 * @method toggle_all_output_scrolling
1366 */
1366 */
1367 Notebook.prototype.toggle_all_output_scroll = function () {
1367 Notebook.prototype.toggle_all_output_scroll = function () {
1368 $.map(this.get_cells(), function (cell, i) {
1368 $.map(this.get_cells(), function (cell, i) {
1369 if (cell instanceof IPython.CodeCell) {
1369 if (cell instanceof IPython.CodeCell) {
1370 cell.toggle_output_scroll();
1370 cell.toggle_output_scroll();
1371 }
1371 }
1372 });
1372 });
1373 // this should not be set if the `collapse` key is removed from nbformat
1373 // this should not be set if the `collapse` key is removed from nbformat
1374 this.set_dirty(true);
1374 this.set_dirty(true);
1375 };
1375 };
1376
1376
1377 // Other cell functions: line numbers, ...
1377 // Other cell functions: line numbers, ...
1378
1378
1379 /**
1379 /**
1380 * Toggle line numbers in the selected cell's input area.
1380 * Toggle line numbers in the selected cell's input area.
1381 *
1381 *
1382 * @method cell_toggle_line_numbers
1382 * @method cell_toggle_line_numbers
1383 */
1383 */
1384 Notebook.prototype.cell_toggle_line_numbers = function() {
1384 Notebook.prototype.cell_toggle_line_numbers = function() {
1385 this.get_selected_cell().toggle_line_numbers();
1385 this.get_selected_cell().toggle_line_numbers();
1386 };
1386 };
1387
1387
1388 // Session related things
1388 // Session related things
1389
1389
1390 /**
1390 /**
1391 * Start a new session and set it on each code cell.
1391 * Start a new session and set it on each code cell.
1392 *
1392 *
1393 * @method start_session
1393 * @method start_session
1394 */
1394 */
1395 Notebook.prototype.start_session = function () {
1395 Notebook.prototype.start_session = function () {
1396 this.session = new IPython.Session(this, this.options);
1396 this.session = new IPython.Session(this, this.options);
1397 this.session.start($.proxy(this._session_started, this));
1397 this.session.start($.proxy(this._session_started, this));
1398 };
1398 };
1399
1399
1400
1400
1401 /**
1401 /**
1402 * Once a session is started, link the code cells to the kernel and pass the
1402 * Once a session is started, link the code cells to the kernel and pass the
1403 * comm manager to the widget manager
1403 * comm manager to the widget manager
1404 *
1404 *
1405 */
1405 */
1406 Notebook.prototype._session_started = function(){
1406 Notebook.prototype._session_started = function(){
1407 this.kernel = this.session.kernel;
1407 this.kernel = this.session.kernel;
1408 var ncells = this.ncells();
1408 var ncells = this.ncells();
1409 for (var i=0; i<ncells; i++) {
1409 for (var i=0; i<ncells; i++) {
1410 var cell = this.get_cell(i);
1410 var cell = this.get_cell(i);
1411 if (cell instanceof IPython.CodeCell) {
1411 if (cell instanceof IPython.CodeCell) {
1412 cell.set_kernel(this.session.kernel);
1412 cell.set_kernel(this.session.kernel);
1413 }
1413 }
1414 }
1414 }
1415 };
1415 };
1416
1416
1417 /**
1417 /**
1418 * Prompt the user to restart the IPython kernel.
1418 * Prompt the user to restart the IPython kernel.
1419 *
1419 *
1420 * @method restart_kernel
1420 * @method restart_kernel
1421 */
1421 */
1422 Notebook.prototype.restart_kernel = function () {
1422 Notebook.prototype.restart_kernel = function () {
1423 var that = this;
1423 var that = this;
1424 IPython.dialog.modal({
1424 IPython.dialog.modal({
1425 title : "Restart kernel or continue running?",
1425 title : "Restart kernel or continue running?",
1426 body : $("<p/>").text(
1426 body : $("<p/>").text(
1427 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1427 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1428 ),
1428 ),
1429 buttons : {
1429 buttons : {
1430 "Continue running" : {},
1430 "Continue running" : {},
1431 "Restart" : {
1431 "Restart" : {
1432 "class" : "btn-danger",
1432 "class" : "btn-danger",
1433 "click" : function() {
1433 "click" : function() {
1434 that.session.restart_kernel();
1434 that.session.restart_kernel();
1435 }
1435 }
1436 }
1436 }
1437 }
1437 }
1438 });
1438 });
1439 };
1439 };
1440
1440
1441 /**
1441 /**
1442 * Execute or render cell outputs and go into command mode.
1442 * Execute or render cell outputs and go into command mode.
1443 *
1443 *
1444 * @method execute_cell
1444 * @method execute_cell
1445 */
1445 */
1446 Notebook.prototype.execute_cell = function () {
1446 Notebook.prototype.execute_cell = function () {
1447 // mode = shift, ctrl, alt
1447 // mode = shift, ctrl, alt
1448 var cell = this.get_selected_cell();
1448 var cell = this.get_selected_cell();
1449 var cell_index = this.find_cell_index(cell);
1449 var cell_index = this.find_cell_index(cell);
1450
1450
1451 cell.execute();
1451 cell.execute();
1452 cell.focus_cell();
1452 cell.focus_cell();
1453 this.command_mode();
1453 this.command_mode();
1454 this.set_dirty(true);
1454 this.set_dirty(true);
1455 };
1455 };
1456
1456
1457 /**
1457 /**
1458 * Execute or render cell outputs and insert a new cell below.
1458 * Execute or render cell outputs and insert a new cell below.
1459 *
1459 *
1460 * @method execute_cell_and_insert_below
1460 * @method execute_cell_and_insert_below
1461 */
1461 */
1462 Notebook.prototype.execute_cell_and_insert_below = function () {
1462 Notebook.prototype.execute_cell_and_insert_below = function () {
1463 var cell = this.get_selected_cell();
1463 var cell = this.get_selected_cell();
1464 var cell_index = this.find_cell_index(cell);
1464 var cell_index = this.find_cell_index(cell);
1465
1465
1466 cell.execute();
1466 cell.execute();
1467
1467
1468 // If we are at the end always insert a new cell and return
1468 // If we are at the end always insert a new cell and return
1469 if (cell_index === (this.ncells()-1)) {
1469 if (cell_index === (this.ncells()-1)) {
1470 this.insert_cell_below('code');
1470 this.insert_cell_below('code');
1471 this.trigger_edit_mode(cell_index+1);
1471 this.edit_mode(cell_index+1);
1472 this.scroll_to_bottom();
1472 this.scroll_to_bottom();
1473 this.set_dirty(true);
1473 this.set_dirty(true);
1474 return;
1474 return;
1475 }
1475 }
1476
1476
1477 this.insert_cell_below('code');
1477 this.insert_cell_below('code');
1478 this.trigger_edit_mode(cell_index+1);
1478 this.edit_mode(cell_index+1);
1479 this.set_dirty(true);
1479 this.set_dirty(true);
1480 };
1480 };
1481
1481
1482 /**
1482 /**
1483 * Execute or render cell outputs and select the next cell.
1483 * Execute or render cell outputs and select the next cell.
1484 *
1484 *
1485 * @method execute_cell_and_select_below
1485 * @method execute_cell_and_select_below
1486 */
1486 */
1487 Notebook.prototype.execute_cell_and_select_below = function () {
1487 Notebook.prototype.execute_cell_and_select_below = function () {
1488
1488
1489 var cell = this.get_selected_cell();
1489 var cell = this.get_selected_cell();
1490 var cell_index = this.find_cell_index(cell);
1490 var cell_index = this.find_cell_index(cell);
1491
1491
1492 cell.execute();
1492 cell.execute();
1493
1493
1494 // If we are at the end always insert a new cell and return
1494 // If we are at the end always insert a new cell and return
1495 if (cell_index === (this.ncells()-1)) {
1495 if (cell_index === (this.ncells()-1)) {
1496 this.insert_cell_below('code');
1496 this.insert_cell_below('code');
1497 this.trigger_edit_mode(cell_index+1);
1497 this.edit_mode(cell_index+1);
1498 this.scroll_to_bottom();
1498 this.scroll_to_bottom();
1499 this.set_dirty(true);
1499 this.set_dirty(true);
1500 return;
1500 return;
1501 }
1501 }
1502
1502
1503 this.select(cell_index+1);
1503 this.select(cell_index+1);
1504 this.get_cell(cell_index+1).focus_cell();
1504 this.get_cell(cell_index+1).focus_cell();
1505 this.command_mode();
1505 this.command_mode();
1506 this.set_dirty(true);
1506 this.set_dirty(true);
1507 };
1507 };
1508
1508
1509 /**
1509 /**
1510 * Execute all cells below the selected cell.
1510 * Execute all cells below the selected cell.
1511 *
1511 *
1512 * @method execute_cells_below
1512 * @method execute_cells_below
1513 */
1513 */
1514 Notebook.prototype.execute_cells_below = function () {
1514 Notebook.prototype.execute_cells_below = function () {
1515 this.execute_cell_range(this.get_selected_index(), this.ncells());
1515 this.execute_cell_range(this.get_selected_index(), this.ncells());
1516 this.scroll_to_bottom();
1516 this.scroll_to_bottom();
1517 };
1517 };
1518
1518
1519 /**
1519 /**
1520 * Execute all cells above the selected cell.
1520 * Execute all cells above the selected cell.
1521 *
1521 *
1522 * @method execute_cells_above
1522 * @method execute_cells_above
1523 */
1523 */
1524 Notebook.prototype.execute_cells_above = function () {
1524 Notebook.prototype.execute_cells_above = function () {
1525 this.execute_cell_range(0, this.get_selected_index());
1525 this.execute_cell_range(0, this.get_selected_index());
1526 };
1526 };
1527
1527
1528 /**
1528 /**
1529 * Execute all cells.
1529 * Execute all cells.
1530 *
1530 *
1531 * @method execute_all_cells
1531 * @method execute_all_cells
1532 */
1532 */
1533 Notebook.prototype.execute_all_cells = function () {
1533 Notebook.prototype.execute_all_cells = function () {
1534 this.execute_cell_range(0, this.ncells());
1534 this.execute_cell_range(0, this.ncells());
1535 this.scroll_to_bottom();
1535 this.scroll_to_bottom();
1536 };
1536 };
1537
1537
1538 /**
1538 /**
1539 * Execute a contiguous range of cells.
1539 * Execute a contiguous range of cells.
1540 *
1540 *
1541 * @method execute_cell_range
1541 * @method execute_cell_range
1542 * @param {Number} start Index of the first cell to execute (inclusive)
1542 * @param {Number} start Index of the first cell to execute (inclusive)
1543 * @param {Number} end Index of the last cell to execute (exclusive)
1543 * @param {Number} end Index of the last cell to execute (exclusive)
1544 */
1544 */
1545 Notebook.prototype.execute_cell_range = function (start, end) {
1545 Notebook.prototype.execute_cell_range = function (start, end) {
1546 for (var i=start; i<end; i++) {
1546 for (var i=start; i<end; i++) {
1547 this.select(i);
1547 this.select(i);
1548 this.execute_cell();
1548 this.execute_cell();
1549 }
1549 }
1550 };
1550 };
1551
1551
1552 // Persistance and loading
1552 // Persistance and loading
1553
1553
1554 /**
1554 /**
1555 * Getter method for this notebook's name.
1555 * Getter method for this notebook's name.
1556 *
1556 *
1557 * @method get_notebook_name
1557 * @method get_notebook_name
1558 * @return {String} This notebook's name (excluding file extension)
1558 * @return {String} This notebook's name (excluding file extension)
1559 */
1559 */
1560 Notebook.prototype.get_notebook_name = function () {
1560 Notebook.prototype.get_notebook_name = function () {
1561 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1561 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1562 return nbname;
1562 return nbname;
1563 };
1563 };
1564
1564
1565 /**
1565 /**
1566 * Setter method for this notebook's name.
1566 * Setter method for this notebook's name.
1567 *
1567 *
1568 * @method set_notebook_name
1568 * @method set_notebook_name
1569 * @param {String} name A new name for this notebook
1569 * @param {String} name A new name for this notebook
1570 */
1570 */
1571 Notebook.prototype.set_notebook_name = function (name) {
1571 Notebook.prototype.set_notebook_name = function (name) {
1572 this.notebook_name = name;
1572 this.notebook_name = name;
1573 };
1573 };
1574
1574
1575 /**
1575 /**
1576 * Check that a notebook's name is valid.
1576 * Check that a notebook's name is valid.
1577 *
1577 *
1578 * @method test_notebook_name
1578 * @method test_notebook_name
1579 * @param {String} nbname A name for this notebook
1579 * @param {String} nbname A name for this notebook
1580 * @return {Boolean} True if the name is valid, false if invalid
1580 * @return {Boolean} True if the name is valid, false if invalid
1581 */
1581 */
1582 Notebook.prototype.test_notebook_name = function (nbname) {
1582 Notebook.prototype.test_notebook_name = function (nbname) {
1583 nbname = nbname || '';
1583 nbname = nbname || '';
1584 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1584 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1585 return true;
1585 return true;
1586 } else {
1586 } else {
1587 return false;
1587 return false;
1588 }
1588 }
1589 };
1589 };
1590
1590
1591 /**
1591 /**
1592 * Load a notebook from JSON (.ipynb).
1592 * Load a notebook from JSON (.ipynb).
1593 *
1593 *
1594 * This currently handles one worksheet: others are deleted.
1594 * This currently handles one worksheet: others are deleted.
1595 *
1595 *
1596 * @method fromJSON
1596 * @method fromJSON
1597 * @param {Object} data JSON representation of a notebook
1597 * @param {Object} data JSON representation of a notebook
1598 */
1598 */
1599 Notebook.prototype.fromJSON = function (data) {
1599 Notebook.prototype.fromJSON = function (data) {
1600 var content = data.content;
1600 var content = data.content;
1601 var ncells = this.ncells();
1601 var ncells = this.ncells();
1602 var i;
1602 var i;
1603 for (i=0; i<ncells; i++) {
1603 for (i=0; i<ncells; i++) {
1604 // Always delete cell 0 as they get renumbered as they are deleted.
1604 // Always delete cell 0 as they get renumbered as they are deleted.
1605 this.delete_cell(0);
1605 this.delete_cell(0);
1606 }
1606 }
1607 // Save the metadata and name.
1607 // Save the metadata and name.
1608 this.metadata = content.metadata;
1608 this.metadata = content.metadata;
1609 this.notebook_name = data.name;
1609 this.notebook_name = data.name;
1610 // Only handle 1 worksheet for now.
1610 // Only handle 1 worksheet for now.
1611 var worksheet = content.worksheets[0];
1611 var worksheet = content.worksheets[0];
1612 if (worksheet !== undefined) {
1612 if (worksheet !== undefined) {
1613 if (worksheet.metadata) {
1613 if (worksheet.metadata) {
1614 this.worksheet_metadata = worksheet.metadata;
1614 this.worksheet_metadata = worksheet.metadata;
1615 }
1615 }
1616 var new_cells = worksheet.cells;
1616 var new_cells = worksheet.cells;
1617 ncells = new_cells.length;
1617 ncells = new_cells.length;
1618 var cell_data = null;
1618 var cell_data = null;
1619 var new_cell = null;
1619 var new_cell = null;
1620 for (i=0; i<ncells; i++) {
1620 for (i=0; i<ncells; i++) {
1621 cell_data = new_cells[i];
1621 cell_data = new_cells[i];
1622 // VERSIONHACK: plaintext -> raw
1622 // VERSIONHACK: plaintext -> raw
1623 // handle never-released plaintext name for raw cells
1623 // handle never-released plaintext name for raw cells
1624 if (cell_data.cell_type === 'plaintext'){
1624 if (cell_data.cell_type === 'plaintext'){
1625 cell_data.cell_type = 'raw';
1625 cell_data.cell_type = 'raw';
1626 }
1626 }
1627
1627
1628 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1628 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1629 new_cell.fromJSON(cell_data);
1629 new_cell.fromJSON(cell_data);
1630 }
1630 }
1631 }
1631 }
1632 if (content.worksheets.length > 1) {
1632 if (content.worksheets.length > 1) {
1633 IPython.dialog.modal({
1633 IPython.dialog.modal({
1634 title : "Multiple worksheets",
1634 title : "Multiple worksheets",
1635 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1635 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1636 "but this version of IPython can only handle the first. " +
1636 "but this version of IPython can only handle the first. " +
1637 "If you save this notebook, worksheets after the first will be lost.",
1637 "If you save this notebook, worksheets after the first will be lost.",
1638 buttons : {
1638 buttons : {
1639 OK : {
1639 OK : {
1640 class : "btn-danger"
1640 class : "btn-danger"
1641 }
1641 }
1642 }
1642 }
1643 });
1643 });
1644 }
1644 }
1645 };
1645 };
1646
1646
1647 /**
1647 /**
1648 * Dump this notebook into a JSON-friendly object.
1648 * Dump this notebook into a JSON-friendly object.
1649 *
1649 *
1650 * @method toJSON
1650 * @method toJSON
1651 * @return {Object} A JSON-friendly representation of this notebook.
1651 * @return {Object} A JSON-friendly representation of this notebook.
1652 */
1652 */
1653 Notebook.prototype.toJSON = function () {
1653 Notebook.prototype.toJSON = function () {
1654 var cells = this.get_cells();
1654 var cells = this.get_cells();
1655 var ncells = cells.length;
1655 var ncells = cells.length;
1656 var cell_array = new Array(ncells);
1656 var cell_array = new Array(ncells);
1657 for (var i=0; i<ncells; i++) {
1657 for (var i=0; i<ncells; i++) {
1658 cell_array[i] = cells[i].toJSON();
1658 cell_array[i] = cells[i].toJSON();
1659 }
1659 }
1660 var data = {
1660 var data = {
1661 // Only handle 1 worksheet for now.
1661 // Only handle 1 worksheet for now.
1662 worksheets : [{
1662 worksheets : [{
1663 cells: cell_array,
1663 cells: cell_array,
1664 metadata: this.worksheet_metadata
1664 metadata: this.worksheet_metadata
1665 }],
1665 }],
1666 metadata : this.metadata
1666 metadata : this.metadata
1667 };
1667 };
1668 return data;
1668 return data;
1669 };
1669 };
1670
1670
1671 /**
1671 /**
1672 * Start an autosave timer, for periodically saving the notebook.
1672 * Start an autosave timer, for periodically saving the notebook.
1673 *
1673 *
1674 * @method set_autosave_interval
1674 * @method set_autosave_interval
1675 * @param {Integer} interval the autosave interval in milliseconds
1675 * @param {Integer} interval the autosave interval in milliseconds
1676 */
1676 */
1677 Notebook.prototype.set_autosave_interval = function (interval) {
1677 Notebook.prototype.set_autosave_interval = function (interval) {
1678 var that = this;
1678 var that = this;
1679 // clear previous interval, so we don't get simultaneous timers
1679 // clear previous interval, so we don't get simultaneous timers
1680 if (this.autosave_timer) {
1680 if (this.autosave_timer) {
1681 clearInterval(this.autosave_timer);
1681 clearInterval(this.autosave_timer);
1682 }
1682 }
1683
1683
1684 this.autosave_interval = this.minimum_autosave_interval = interval;
1684 this.autosave_interval = this.minimum_autosave_interval = interval;
1685 if (interval) {
1685 if (interval) {
1686 this.autosave_timer = setInterval(function() {
1686 this.autosave_timer = setInterval(function() {
1687 if (that.dirty) {
1687 if (that.dirty) {
1688 that.save_notebook();
1688 that.save_notebook();
1689 }
1689 }
1690 }, interval);
1690 }, interval);
1691 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1691 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1692 } else {
1692 } else {
1693 this.autosave_timer = null;
1693 this.autosave_timer = null;
1694 $([IPython.events]).trigger("autosave_disabled.Notebook");
1694 $([IPython.events]).trigger("autosave_disabled.Notebook");
1695 }
1695 }
1696 };
1696 };
1697
1697
1698 /**
1698 /**
1699 * Save this notebook on the server.
1699 * Save this notebook on the server.
1700 *
1700 *
1701 * @method save_notebook
1701 * @method save_notebook
1702 */
1702 */
1703 Notebook.prototype.save_notebook = function (extra_settings) {
1703 Notebook.prototype.save_notebook = function (extra_settings) {
1704 // Create a JSON model to be sent to the server.
1704 // Create a JSON model to be sent to the server.
1705 var model = {};
1705 var model = {};
1706 model.name = this.notebook_name;
1706 model.name = this.notebook_name;
1707 model.path = this.notebook_path;
1707 model.path = this.notebook_path;
1708 model.content = this.toJSON();
1708 model.content = this.toJSON();
1709 model.content.nbformat = this.nbformat;
1709 model.content.nbformat = this.nbformat;
1710 model.content.nbformat_minor = this.nbformat_minor;
1710 model.content.nbformat_minor = this.nbformat_minor;
1711 // time the ajax call for autosave tuning purposes.
1711 // time the ajax call for autosave tuning purposes.
1712 var start = new Date().getTime();
1712 var start = new Date().getTime();
1713 // We do the call with settings so we can set cache to false.
1713 // We do the call with settings so we can set cache to false.
1714 var settings = {
1714 var settings = {
1715 processData : false,
1715 processData : false,
1716 cache : false,
1716 cache : false,
1717 type : "PUT",
1717 type : "PUT",
1718 data : JSON.stringify(model),
1718 data : JSON.stringify(model),
1719 headers : {'Content-Type': 'application/json'},
1719 headers : {'Content-Type': 'application/json'},
1720 success : $.proxy(this.save_notebook_success, this, start),
1720 success : $.proxy(this.save_notebook_success, this, start),
1721 error : $.proxy(this.save_notebook_error, this)
1721 error : $.proxy(this.save_notebook_error, this)
1722 };
1722 };
1723 if (extra_settings) {
1723 if (extra_settings) {
1724 for (var key in extra_settings) {
1724 for (var key in extra_settings) {
1725 settings[key] = extra_settings[key];
1725 settings[key] = extra_settings[key];
1726 }
1726 }
1727 }
1727 }
1728 $([IPython.events]).trigger('notebook_saving.Notebook');
1728 $([IPython.events]).trigger('notebook_saving.Notebook');
1729 var url = utils.url_join_encode(
1729 var url = utils.url_join_encode(
1730 this.base_url,
1730 this.base_url,
1731 'api/notebooks',
1731 'api/notebooks',
1732 this.notebook_path,
1732 this.notebook_path,
1733 this.notebook_name
1733 this.notebook_name
1734 );
1734 );
1735 $.ajax(url, settings);
1735 $.ajax(url, settings);
1736 };
1736 };
1737
1737
1738 /**
1738 /**
1739 * Success callback for saving a notebook.
1739 * Success callback for saving a notebook.
1740 *
1740 *
1741 * @method save_notebook_success
1741 * @method save_notebook_success
1742 * @param {Integer} start the time when the save request started
1742 * @param {Integer} start the time when the save request started
1743 * @param {Object} data JSON representation of a notebook
1743 * @param {Object} data JSON representation of a notebook
1744 * @param {String} status Description of response status
1744 * @param {String} status Description of response status
1745 * @param {jqXHR} xhr jQuery Ajax object
1745 * @param {jqXHR} xhr jQuery Ajax object
1746 */
1746 */
1747 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1747 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1748 this.set_dirty(false);
1748 this.set_dirty(false);
1749 $([IPython.events]).trigger('notebook_saved.Notebook');
1749 $([IPython.events]).trigger('notebook_saved.Notebook');
1750 this._update_autosave_interval(start);
1750 this._update_autosave_interval(start);
1751 if (this._checkpoint_after_save) {
1751 if (this._checkpoint_after_save) {
1752 this.create_checkpoint();
1752 this.create_checkpoint();
1753 this._checkpoint_after_save = false;
1753 this._checkpoint_after_save = false;
1754 }
1754 }
1755 };
1755 };
1756
1756
1757 /**
1757 /**
1758 * update the autosave interval based on how long the last save took
1758 * update the autosave interval based on how long the last save took
1759 *
1759 *
1760 * @method _update_autosave_interval
1760 * @method _update_autosave_interval
1761 * @param {Integer} timestamp when the save request started
1761 * @param {Integer} timestamp when the save request started
1762 */
1762 */
1763 Notebook.prototype._update_autosave_interval = function (start) {
1763 Notebook.prototype._update_autosave_interval = function (start) {
1764 var duration = (new Date().getTime() - start);
1764 var duration = (new Date().getTime() - start);
1765 if (this.autosave_interval) {
1765 if (this.autosave_interval) {
1766 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1766 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1767 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1767 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1768 // round to 10 seconds, otherwise we will be setting a new interval too often
1768 // round to 10 seconds, otherwise we will be setting a new interval too often
1769 interval = 10000 * Math.round(interval / 10000);
1769 interval = 10000 * Math.round(interval / 10000);
1770 // set new interval, if it's changed
1770 // set new interval, if it's changed
1771 if (interval != this.autosave_interval) {
1771 if (interval != this.autosave_interval) {
1772 this.set_autosave_interval(interval);
1772 this.set_autosave_interval(interval);
1773 }
1773 }
1774 }
1774 }
1775 };
1775 };
1776
1776
1777 /**
1777 /**
1778 * Failure callback for saving a notebook.
1778 * Failure callback for saving a notebook.
1779 *
1779 *
1780 * @method save_notebook_error
1780 * @method save_notebook_error
1781 * @param {jqXHR} xhr jQuery Ajax object
1781 * @param {jqXHR} xhr jQuery Ajax object
1782 * @param {String} status Description of response status
1782 * @param {String} status Description of response status
1783 * @param {String} error HTTP error message
1783 * @param {String} error HTTP error message
1784 */
1784 */
1785 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1785 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1786 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1786 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1787 };
1787 };
1788
1788
1789 Notebook.prototype.new_notebook = function(){
1789 Notebook.prototype.new_notebook = function(){
1790 var path = this.notebook_path;
1790 var path = this.notebook_path;
1791 var base_url = this.base_url;
1791 var base_url = this.base_url;
1792 var settings = {
1792 var settings = {
1793 processData : false,
1793 processData : false,
1794 cache : false,
1794 cache : false,
1795 type : "POST",
1795 type : "POST",
1796 dataType : "json",
1796 dataType : "json",
1797 async : false,
1797 async : false,
1798 success : function (data, status, xhr){
1798 success : function (data, status, xhr){
1799 var notebook_name = data.name;
1799 var notebook_name = data.name;
1800 window.open(
1800 window.open(
1801 utils.url_join_encode(
1801 utils.url_join_encode(
1802 base_url,
1802 base_url,
1803 'notebooks',
1803 'notebooks',
1804 path,
1804 path,
1805 notebook_name
1805 notebook_name
1806 ),
1806 ),
1807 '_blank'
1807 '_blank'
1808 );
1808 );
1809 }
1809 }
1810 };
1810 };
1811 var url = utils.url_join_encode(
1811 var url = utils.url_join_encode(
1812 base_url,
1812 base_url,
1813 'api/notebooks',
1813 'api/notebooks',
1814 path
1814 path
1815 );
1815 );
1816 $.ajax(url,settings);
1816 $.ajax(url,settings);
1817 };
1817 };
1818
1818
1819
1819
1820 Notebook.prototype.copy_notebook = function(){
1820 Notebook.prototype.copy_notebook = function(){
1821 var path = this.notebook_path;
1821 var path = this.notebook_path;
1822 var base_url = this.base_url;
1822 var base_url = this.base_url;
1823 var settings = {
1823 var settings = {
1824 processData : false,
1824 processData : false,
1825 cache : false,
1825 cache : false,
1826 type : "POST",
1826 type : "POST",
1827 dataType : "json",
1827 dataType : "json",
1828 data : JSON.stringify({copy_from : this.notebook_name}),
1828 data : JSON.stringify({copy_from : this.notebook_name}),
1829 async : false,
1829 async : false,
1830 success : function (data, status, xhr) {
1830 success : function (data, status, xhr) {
1831 window.open(utils.url_join_encode(
1831 window.open(utils.url_join_encode(
1832 base_url,
1832 base_url,
1833 'notebooks',
1833 'notebooks',
1834 data.path,
1834 data.path,
1835 data.name
1835 data.name
1836 ), '_blank');
1836 ), '_blank');
1837 }
1837 }
1838 };
1838 };
1839 var url = utils.url_join_encode(
1839 var url = utils.url_join_encode(
1840 base_url,
1840 base_url,
1841 'api/notebooks',
1841 'api/notebooks',
1842 path
1842 path
1843 );
1843 );
1844 $.ajax(url,settings);
1844 $.ajax(url,settings);
1845 };
1845 };
1846
1846
1847 Notebook.prototype.rename = function (nbname) {
1847 Notebook.prototype.rename = function (nbname) {
1848 var that = this;
1848 var that = this;
1849 if (!nbname.match(/\.ipynb$/)) {
1849 if (!nbname.match(/\.ipynb$/)) {
1850 nbname = nbname + ".ipynb";
1850 nbname = nbname + ".ipynb";
1851 }
1851 }
1852 var data = {name: nbname};
1852 var data = {name: nbname};
1853 var settings = {
1853 var settings = {
1854 processData : false,
1854 processData : false,
1855 cache : false,
1855 cache : false,
1856 type : "PATCH",
1856 type : "PATCH",
1857 data : JSON.stringify(data),
1857 data : JSON.stringify(data),
1858 dataType: "json",
1858 dataType: "json",
1859 headers : {'Content-Type': 'application/json'},
1859 headers : {'Content-Type': 'application/json'},
1860 success : $.proxy(that.rename_success, this),
1860 success : $.proxy(that.rename_success, this),
1861 error : $.proxy(that.rename_error, this)
1861 error : $.proxy(that.rename_error, this)
1862 };
1862 };
1863 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1863 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1864 var url = utils.url_join_encode(
1864 var url = utils.url_join_encode(
1865 this.base_url,
1865 this.base_url,
1866 'api/notebooks',
1866 'api/notebooks',
1867 this.notebook_path,
1867 this.notebook_path,
1868 this.notebook_name
1868 this.notebook_name
1869 );
1869 );
1870 $.ajax(url, settings);
1870 $.ajax(url, settings);
1871 };
1871 };
1872
1872
1873 Notebook.prototype.delete = function () {
1873 Notebook.prototype.delete = function () {
1874 var that = this;
1874 var that = this;
1875 var settings = {
1875 var settings = {
1876 processData : false,
1876 processData : false,
1877 cache : false,
1877 cache : false,
1878 type : "DELETE",
1878 type : "DELETE",
1879 dataType: "json",
1879 dataType: "json",
1880 };
1880 };
1881 var url = utils.url_join_encode(
1881 var url = utils.url_join_encode(
1882 this.base_url,
1882 this.base_url,
1883 'api/notebooks',
1883 'api/notebooks',
1884 this.notebook_path,
1884 this.notebook_path,
1885 this.notebook_name
1885 this.notebook_name
1886 );
1886 );
1887 $.ajax(url, settings);
1887 $.ajax(url, settings);
1888 };
1888 };
1889
1889
1890
1890
1891 Notebook.prototype.rename_success = function (json, status, xhr) {
1891 Notebook.prototype.rename_success = function (json, status, xhr) {
1892 var name = this.notebook_name = json.name;
1892 var name = this.notebook_name = json.name;
1893 var path = json.path;
1893 var path = json.path;
1894 this.session.rename_notebook(name, path);
1894 this.session.rename_notebook(name, path);
1895 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1895 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1896 };
1896 };
1897
1897
1898 Notebook.prototype.rename_error = function (xhr, status, error) {
1898 Notebook.prototype.rename_error = function (xhr, status, error) {
1899 var that = this;
1899 var that = this;
1900 var dialog = $('<div/>').append(
1900 var dialog = $('<div/>').append(
1901 $("<p/>").addClass("rename-message")
1901 $("<p/>").addClass("rename-message")
1902 .text('This notebook name already exists.')
1902 .text('This notebook name already exists.')
1903 );
1903 );
1904 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1904 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1905 IPython.dialog.modal({
1905 IPython.dialog.modal({
1906 title: "Notebook Rename Error!",
1906 title: "Notebook Rename Error!",
1907 body: dialog,
1907 body: dialog,
1908 buttons : {
1908 buttons : {
1909 "Cancel": {},
1909 "Cancel": {},
1910 "OK": {
1910 "OK": {
1911 class: "btn-primary",
1911 class: "btn-primary",
1912 click: function () {
1912 click: function () {
1913 IPython.save_widget.rename_notebook();
1913 IPython.save_widget.rename_notebook();
1914 }}
1914 }}
1915 },
1915 },
1916 open : function (event, ui) {
1916 open : function (event, ui) {
1917 var that = $(this);
1917 var that = $(this);
1918 // Upon ENTER, click the OK button.
1918 // Upon ENTER, click the OK button.
1919 that.find('input[type="text"]').keydown(function (event, ui) {
1919 that.find('input[type="text"]').keydown(function (event, ui) {
1920 if (event.which === utils.keycodes.ENTER) {
1920 if (event.which === utils.keycodes.ENTER) {
1921 that.find('.btn-primary').first().click();
1921 that.find('.btn-primary').first().click();
1922 }
1922 }
1923 });
1923 });
1924 that.find('input[type="text"]').focus();
1924 that.find('input[type="text"]').focus();
1925 }
1925 }
1926 });
1926 });
1927 };
1927 };
1928
1928
1929 /**
1929 /**
1930 * Request a notebook's data from the server.
1930 * Request a notebook's data from the server.
1931 *
1931 *
1932 * @method load_notebook
1932 * @method load_notebook
1933 * @param {String} notebook_name and path A notebook to load
1933 * @param {String} notebook_name and path A notebook to load
1934 */
1934 */
1935 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1935 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1936 var that = this;
1936 var that = this;
1937 this.notebook_name = notebook_name;
1937 this.notebook_name = notebook_name;
1938 this.notebook_path = notebook_path;
1938 this.notebook_path = notebook_path;
1939 // We do the call with settings so we can set cache to false.
1939 // We do the call with settings so we can set cache to false.
1940 var settings = {
1940 var settings = {
1941 processData : false,
1941 processData : false,
1942 cache : false,
1942 cache : false,
1943 type : "GET",
1943 type : "GET",
1944 dataType : "json",
1944 dataType : "json",
1945 success : $.proxy(this.load_notebook_success,this),
1945 success : $.proxy(this.load_notebook_success,this),
1946 error : $.proxy(this.load_notebook_error,this),
1946 error : $.proxy(this.load_notebook_error,this),
1947 };
1947 };
1948 $([IPython.events]).trigger('notebook_loading.Notebook');
1948 $([IPython.events]).trigger('notebook_loading.Notebook');
1949 var url = utils.url_join_encode(
1949 var url = utils.url_join_encode(
1950 this.base_url,
1950 this.base_url,
1951 'api/notebooks',
1951 'api/notebooks',
1952 this.notebook_path,
1952 this.notebook_path,
1953 this.notebook_name
1953 this.notebook_name
1954 );
1954 );
1955 $.ajax(url, settings);
1955 $.ajax(url, settings);
1956 };
1956 };
1957
1957
1958 /**
1958 /**
1959 * Success callback for loading a notebook from the server.
1959 * Success callback for loading a notebook from the server.
1960 *
1960 *
1961 * Load notebook data from the JSON response.
1961 * Load notebook data from the JSON response.
1962 *
1962 *
1963 * @method load_notebook_success
1963 * @method load_notebook_success
1964 * @param {Object} data JSON representation of a notebook
1964 * @param {Object} data JSON representation of a notebook
1965 * @param {String} status Description of response status
1965 * @param {String} status Description of response status
1966 * @param {jqXHR} xhr jQuery Ajax object
1966 * @param {jqXHR} xhr jQuery Ajax object
1967 */
1967 */
1968 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1968 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1969 this.fromJSON(data);
1969 this.fromJSON(data);
1970 if (this.ncells() === 0) {
1970 if (this.ncells() === 0) {
1971 this.insert_cell_below('code');
1971 this.insert_cell_below('code');
1972 this.trigger_edit_mode(0);
1972 this.edit_mode(0);
1973 } else {
1973 } else {
1974 this.select(0);
1974 this.select(0);
1975 this.command_mode();
1975 this.command_mode();
1976 }
1976 }
1977 this.set_dirty(false);
1977 this.set_dirty(false);
1978 this.scroll_to_top();
1978 this.scroll_to_top();
1979 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1979 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1980 var msg = "This notebook has been converted from an older " +
1980 var msg = "This notebook has been converted from an older " +
1981 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1981 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1982 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1982 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1983 "newer notebook format will be used and older versions of IPython " +
1983 "newer notebook format will be used and older versions of IPython " +
1984 "may not be able to read it. To keep the older version, close the " +
1984 "may not be able to read it. To keep the older version, close the " +
1985 "notebook without saving it.";
1985 "notebook without saving it.";
1986 IPython.dialog.modal({
1986 IPython.dialog.modal({
1987 title : "Notebook converted",
1987 title : "Notebook converted",
1988 body : msg,
1988 body : msg,
1989 buttons : {
1989 buttons : {
1990 OK : {
1990 OK : {
1991 class : "btn-primary"
1991 class : "btn-primary"
1992 }
1992 }
1993 }
1993 }
1994 });
1994 });
1995 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1995 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1996 var that = this;
1996 var that = this;
1997 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1997 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1998 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1998 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1999 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1999 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2000 this_vs + ". You can still work with this notebook, but some features " +
2000 this_vs + ". You can still work with this notebook, but some features " +
2001 "introduced in later notebook versions may not be available.";
2001 "introduced in later notebook versions may not be available.";
2002
2002
2003 IPython.dialog.modal({
2003 IPython.dialog.modal({
2004 title : "Newer Notebook",
2004 title : "Newer Notebook",
2005 body : msg,
2005 body : msg,
2006 buttons : {
2006 buttons : {
2007 OK : {
2007 OK : {
2008 class : "btn-danger"
2008 class : "btn-danger"
2009 }
2009 }
2010 }
2010 }
2011 });
2011 });
2012
2012
2013 }
2013 }
2014
2014
2015 // Create the session after the notebook is completely loaded to prevent
2015 // Create the session after the notebook is completely loaded to prevent
2016 // code execution upon loading, which is a security risk.
2016 // code execution upon loading, which is a security risk.
2017 if (this.session === null) {
2017 if (this.session === null) {
2018 this.start_session();
2018 this.start_session();
2019 }
2019 }
2020 // load our checkpoint list
2020 // load our checkpoint list
2021 this.list_checkpoints();
2021 this.list_checkpoints();
2022
2022
2023 // load toolbar state
2023 // load toolbar state
2024 if (this.metadata.celltoolbar) {
2024 if (this.metadata.celltoolbar) {
2025 IPython.CellToolbar.global_show();
2025 IPython.CellToolbar.global_show();
2026 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2026 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2027 }
2027 }
2028
2028
2029 $([IPython.events]).trigger('notebook_loaded.Notebook');
2029 $([IPython.events]).trigger('notebook_loaded.Notebook');
2030 };
2030 };
2031
2031
2032 /**
2032 /**
2033 * Failure callback for loading a notebook from the server.
2033 * Failure callback for loading a notebook from the server.
2034 *
2034 *
2035 * @method load_notebook_error
2035 * @method load_notebook_error
2036 * @param {jqXHR} xhr jQuery Ajax object
2036 * @param {jqXHR} xhr jQuery Ajax object
2037 * @param {String} status Description of response status
2037 * @param {String} status Description of response status
2038 * @param {String} error HTTP error message
2038 * @param {String} error HTTP error message
2039 */
2039 */
2040 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2040 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2041 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2041 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2042 var msg;
2042 var msg;
2043 if (xhr.status === 400) {
2043 if (xhr.status === 400) {
2044 msg = error;
2044 msg = error;
2045 } else if (xhr.status === 500) {
2045 } else if (xhr.status === 500) {
2046 msg = "An unknown error occurred while loading this notebook. " +
2046 msg = "An unknown error occurred while loading this notebook. " +
2047 "This version can load notebook formats " +
2047 "This version can load notebook formats " +
2048 "v" + this.nbformat + " or earlier.";
2048 "v" + this.nbformat + " or earlier.";
2049 }
2049 }
2050 IPython.dialog.modal({
2050 IPython.dialog.modal({
2051 title: "Error loading notebook",
2051 title: "Error loading notebook",
2052 body : msg,
2052 body : msg,
2053 buttons : {
2053 buttons : {
2054 "OK": {}
2054 "OK": {}
2055 }
2055 }
2056 });
2056 });
2057 };
2057 };
2058
2058
2059 /********************* checkpoint-related *********************/
2059 /********************* checkpoint-related *********************/
2060
2060
2061 /**
2061 /**
2062 * Save the notebook then immediately create a checkpoint.
2062 * Save the notebook then immediately create a checkpoint.
2063 *
2063 *
2064 * @method save_checkpoint
2064 * @method save_checkpoint
2065 */
2065 */
2066 Notebook.prototype.save_checkpoint = function () {
2066 Notebook.prototype.save_checkpoint = function () {
2067 this._checkpoint_after_save = true;
2067 this._checkpoint_after_save = true;
2068 this.save_notebook();
2068 this.save_notebook();
2069 };
2069 };
2070
2070
2071 /**
2071 /**
2072 * Add a checkpoint for this notebook.
2072 * Add a checkpoint for this notebook.
2073 * for use as a callback from checkpoint creation.
2073 * for use as a callback from checkpoint creation.
2074 *
2074 *
2075 * @method add_checkpoint
2075 * @method add_checkpoint
2076 */
2076 */
2077 Notebook.prototype.add_checkpoint = function (checkpoint) {
2077 Notebook.prototype.add_checkpoint = function (checkpoint) {
2078 var found = false;
2078 var found = false;
2079 for (var i = 0; i < this.checkpoints.length; i++) {
2079 for (var i = 0; i < this.checkpoints.length; i++) {
2080 var existing = this.checkpoints[i];
2080 var existing = this.checkpoints[i];
2081 if (existing.id == checkpoint.id) {
2081 if (existing.id == checkpoint.id) {
2082 found = true;
2082 found = true;
2083 this.checkpoints[i] = checkpoint;
2083 this.checkpoints[i] = checkpoint;
2084 break;
2084 break;
2085 }
2085 }
2086 }
2086 }
2087 if (!found) {
2087 if (!found) {
2088 this.checkpoints.push(checkpoint);
2088 this.checkpoints.push(checkpoint);
2089 }
2089 }
2090 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2090 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2091 };
2091 };
2092
2092
2093 /**
2093 /**
2094 * List checkpoints for this notebook.
2094 * List checkpoints for this notebook.
2095 *
2095 *
2096 * @method list_checkpoints
2096 * @method list_checkpoints
2097 */
2097 */
2098 Notebook.prototype.list_checkpoints = function () {
2098 Notebook.prototype.list_checkpoints = function () {
2099 var url = utils.url_join_encode(
2099 var url = utils.url_join_encode(
2100 this.base_url,
2100 this.base_url,
2101 'api/notebooks',
2101 'api/notebooks',
2102 this.notebook_path,
2102 this.notebook_path,
2103 this.notebook_name,
2103 this.notebook_name,
2104 'checkpoints'
2104 'checkpoints'
2105 );
2105 );
2106 $.get(url).done(
2106 $.get(url).done(
2107 $.proxy(this.list_checkpoints_success, this)
2107 $.proxy(this.list_checkpoints_success, this)
2108 ).fail(
2108 ).fail(
2109 $.proxy(this.list_checkpoints_error, this)
2109 $.proxy(this.list_checkpoints_error, this)
2110 );
2110 );
2111 };
2111 };
2112
2112
2113 /**
2113 /**
2114 * Success callback for listing checkpoints.
2114 * Success callback for listing checkpoints.
2115 *
2115 *
2116 * @method list_checkpoint_success
2116 * @method list_checkpoint_success
2117 * @param {Object} data JSON representation of a checkpoint
2117 * @param {Object} data JSON representation of a checkpoint
2118 * @param {String} status Description of response status
2118 * @param {String} status Description of response status
2119 * @param {jqXHR} xhr jQuery Ajax object
2119 * @param {jqXHR} xhr jQuery Ajax object
2120 */
2120 */
2121 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2121 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2122 data = $.parseJSON(data);
2122 data = $.parseJSON(data);
2123 this.checkpoints = data;
2123 this.checkpoints = data;
2124 if (data.length) {
2124 if (data.length) {
2125 this.last_checkpoint = data[data.length - 1];
2125 this.last_checkpoint = data[data.length - 1];
2126 } else {
2126 } else {
2127 this.last_checkpoint = null;
2127 this.last_checkpoint = null;
2128 }
2128 }
2129 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2129 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2130 };
2130 };
2131
2131
2132 /**
2132 /**
2133 * Failure callback for listing a checkpoint.
2133 * Failure callback for listing a checkpoint.
2134 *
2134 *
2135 * @method list_checkpoint_error
2135 * @method list_checkpoint_error
2136 * @param {jqXHR} xhr jQuery Ajax object
2136 * @param {jqXHR} xhr jQuery Ajax object
2137 * @param {String} status Description of response status
2137 * @param {String} status Description of response status
2138 * @param {String} error_msg HTTP error message
2138 * @param {String} error_msg HTTP error message
2139 */
2139 */
2140 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2140 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2141 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2141 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2142 };
2142 };
2143
2143
2144 /**
2144 /**
2145 * Create a checkpoint of this notebook on the server from the most recent save.
2145 * Create a checkpoint of this notebook on the server from the most recent save.
2146 *
2146 *
2147 * @method create_checkpoint
2147 * @method create_checkpoint
2148 */
2148 */
2149 Notebook.prototype.create_checkpoint = function () {
2149 Notebook.prototype.create_checkpoint = function () {
2150 var url = utils.url_join_encode(
2150 var url = utils.url_join_encode(
2151 this.base_url,
2151 this.base_url,
2152 'api/notebooks',
2152 'api/notebooks',
2153 this.notebook_path,
2153 this.notebook_path,
2154 this.notebook_name,
2154 this.notebook_name,
2155 'checkpoints'
2155 'checkpoints'
2156 );
2156 );
2157 $.post(url).done(
2157 $.post(url).done(
2158 $.proxy(this.create_checkpoint_success, this)
2158 $.proxy(this.create_checkpoint_success, this)
2159 ).fail(
2159 ).fail(
2160 $.proxy(this.create_checkpoint_error, this)
2160 $.proxy(this.create_checkpoint_error, this)
2161 );
2161 );
2162 };
2162 };
2163
2163
2164 /**
2164 /**
2165 * Success callback for creating a checkpoint.
2165 * Success callback for creating a checkpoint.
2166 *
2166 *
2167 * @method create_checkpoint_success
2167 * @method create_checkpoint_success
2168 * @param {Object} data JSON representation of a checkpoint
2168 * @param {Object} data JSON representation of a checkpoint
2169 * @param {String} status Description of response status
2169 * @param {String} status Description of response status
2170 * @param {jqXHR} xhr jQuery Ajax object
2170 * @param {jqXHR} xhr jQuery Ajax object
2171 */
2171 */
2172 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2172 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2173 data = $.parseJSON(data);
2173 data = $.parseJSON(data);
2174 this.add_checkpoint(data);
2174 this.add_checkpoint(data);
2175 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2175 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2176 };
2176 };
2177
2177
2178 /**
2178 /**
2179 * Failure callback for creating a checkpoint.
2179 * Failure callback for creating a checkpoint.
2180 *
2180 *
2181 * @method create_checkpoint_error
2181 * @method create_checkpoint_error
2182 * @param {jqXHR} xhr jQuery Ajax object
2182 * @param {jqXHR} xhr jQuery Ajax object
2183 * @param {String} status Description of response status
2183 * @param {String} status Description of response status
2184 * @param {String} error_msg HTTP error message
2184 * @param {String} error_msg HTTP error message
2185 */
2185 */
2186 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2186 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2187 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2187 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2188 };
2188 };
2189
2189
2190 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2190 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2191 var that = this;
2191 var that = this;
2192 checkpoint = checkpoint || this.last_checkpoint;
2192 checkpoint = checkpoint || this.last_checkpoint;
2193 if ( ! checkpoint ) {
2193 if ( ! checkpoint ) {
2194 console.log("restore dialog, but no checkpoint to restore to!");
2194 console.log("restore dialog, but no checkpoint to restore to!");
2195 return;
2195 return;
2196 }
2196 }
2197 var body = $('<div/>').append(
2197 var body = $('<div/>').append(
2198 $('<p/>').addClass("p-space").text(
2198 $('<p/>').addClass("p-space").text(
2199 "Are you sure you want to revert the notebook to " +
2199 "Are you sure you want to revert the notebook to " +
2200 "the latest checkpoint?"
2200 "the latest checkpoint?"
2201 ).append(
2201 ).append(
2202 $("<strong/>").text(
2202 $("<strong/>").text(
2203 " This cannot be undone."
2203 " This cannot be undone."
2204 )
2204 )
2205 )
2205 )
2206 ).append(
2206 ).append(
2207 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2207 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2208 ).append(
2208 ).append(
2209 $('<p/>').addClass("p-space").text(
2209 $('<p/>').addClass("p-space").text(
2210 Date(checkpoint.last_modified)
2210 Date(checkpoint.last_modified)
2211 ).css("text-align", "center")
2211 ).css("text-align", "center")
2212 );
2212 );
2213
2213
2214 IPython.dialog.modal({
2214 IPython.dialog.modal({
2215 title : "Revert notebook to checkpoint",
2215 title : "Revert notebook to checkpoint",
2216 body : body,
2216 body : body,
2217 buttons : {
2217 buttons : {
2218 Revert : {
2218 Revert : {
2219 class : "btn-danger",
2219 class : "btn-danger",
2220 click : function () {
2220 click : function () {
2221 that.restore_checkpoint(checkpoint.id);
2221 that.restore_checkpoint(checkpoint.id);
2222 }
2222 }
2223 },
2223 },
2224 Cancel : {}
2224 Cancel : {}
2225 }
2225 }
2226 });
2226 });
2227 };
2227 };
2228
2228
2229 /**
2229 /**
2230 * Restore the notebook to a checkpoint state.
2230 * Restore the notebook to a checkpoint state.
2231 *
2231 *
2232 * @method restore_checkpoint
2232 * @method restore_checkpoint
2233 * @param {String} checkpoint ID
2233 * @param {String} checkpoint ID
2234 */
2234 */
2235 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2235 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2236 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2236 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2237 var url = utils.url_join_encode(
2237 var url = utils.url_join_encode(
2238 this.base_url,
2238 this.base_url,
2239 'api/notebooks',
2239 'api/notebooks',
2240 this.notebook_path,
2240 this.notebook_path,
2241 this.notebook_name,
2241 this.notebook_name,
2242 'checkpoints',
2242 'checkpoints',
2243 checkpoint
2243 checkpoint
2244 );
2244 );
2245 $.post(url).done(
2245 $.post(url).done(
2246 $.proxy(this.restore_checkpoint_success, this)
2246 $.proxy(this.restore_checkpoint_success, this)
2247 ).fail(
2247 ).fail(
2248 $.proxy(this.restore_checkpoint_error, this)
2248 $.proxy(this.restore_checkpoint_error, this)
2249 );
2249 );
2250 };
2250 };
2251
2251
2252 /**
2252 /**
2253 * Success callback for restoring a notebook to a checkpoint.
2253 * Success callback for restoring a notebook to a checkpoint.
2254 *
2254 *
2255 * @method restore_checkpoint_success
2255 * @method restore_checkpoint_success
2256 * @param {Object} data (ignored, should be empty)
2256 * @param {Object} data (ignored, should be empty)
2257 * @param {String} status Description of response status
2257 * @param {String} status Description of response status
2258 * @param {jqXHR} xhr jQuery Ajax object
2258 * @param {jqXHR} xhr jQuery Ajax object
2259 */
2259 */
2260 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2260 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2261 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2261 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2262 this.load_notebook(this.notebook_name, this.notebook_path);
2262 this.load_notebook(this.notebook_name, this.notebook_path);
2263 };
2263 };
2264
2264
2265 /**
2265 /**
2266 * Failure callback for restoring a notebook to a checkpoint.
2266 * Failure callback for restoring a notebook to a checkpoint.
2267 *
2267 *
2268 * @method restore_checkpoint_error
2268 * @method restore_checkpoint_error
2269 * @param {jqXHR} xhr jQuery Ajax object
2269 * @param {jqXHR} xhr jQuery Ajax object
2270 * @param {String} status Description of response status
2270 * @param {String} status Description of response status
2271 * @param {String} error_msg HTTP error message
2271 * @param {String} error_msg HTTP error message
2272 */
2272 */
2273 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2273 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2274 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2274 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2275 };
2275 };
2276
2276
2277 /**
2277 /**
2278 * Delete a notebook checkpoint.
2278 * Delete a notebook checkpoint.
2279 *
2279 *
2280 * @method delete_checkpoint
2280 * @method delete_checkpoint
2281 * @param {String} checkpoint ID
2281 * @param {String} checkpoint ID
2282 */
2282 */
2283 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2283 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2284 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2284 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2285 var url = utils.url_join_encode(
2285 var url = utils.url_join_encode(
2286 this.base_url,
2286 this.base_url,
2287 'api/notebooks',
2287 'api/notebooks',
2288 this.notebook_path,
2288 this.notebook_path,
2289 this.notebook_name,
2289 this.notebook_name,
2290 'checkpoints',
2290 'checkpoints',
2291 checkpoint
2291 checkpoint
2292 );
2292 );
2293 $.ajax(url, {
2293 $.ajax(url, {
2294 type: 'DELETE',
2294 type: 'DELETE',
2295 success: $.proxy(this.delete_checkpoint_success, this),
2295 success: $.proxy(this.delete_checkpoint_success, this),
2296 error: $.proxy(this.delete_notebook_error,this)
2296 error: $.proxy(this.delete_notebook_error,this)
2297 });
2297 });
2298 };
2298 };
2299
2299
2300 /**
2300 /**
2301 * Success callback for deleting a notebook checkpoint
2301 * Success callback for deleting a notebook checkpoint
2302 *
2302 *
2303 * @method delete_checkpoint_success
2303 * @method delete_checkpoint_success
2304 * @param {Object} data (ignored, should be empty)
2304 * @param {Object} data (ignored, should be empty)
2305 * @param {String} status Description of response status
2305 * @param {String} status Description of response status
2306 * @param {jqXHR} xhr jQuery Ajax object
2306 * @param {jqXHR} xhr jQuery Ajax object
2307 */
2307 */
2308 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2308 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2309 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2309 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2310 this.load_notebook(this.notebook_name, this.notebook_path);
2310 this.load_notebook(this.notebook_name, this.notebook_path);
2311 };
2311 };
2312
2312
2313 /**
2313 /**
2314 * Failure callback for deleting a notebook checkpoint.
2314 * Failure callback for deleting a notebook checkpoint.
2315 *
2315 *
2316 * @method delete_checkpoint_error
2316 * @method delete_checkpoint_error
2317 * @param {jqXHR} xhr jQuery Ajax object
2317 * @param {jqXHR} xhr jQuery Ajax object
2318 * @param {String} status Description of response status
2318 * @param {String} status Description of response status
2319 * @param {String} error_msg HTTP error message
2319 * @param {String} error_msg HTTP error message
2320 */
2320 */
2321 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2321 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2322 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2322 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2323 };
2323 };
2324
2324
2325
2325
2326 IPython.Notebook = Notebook;
2326 IPython.Notebook = Notebook;
2327
2327
2328
2328
2329 return IPython;
2329 return IPython;
2330
2330
2331 }(IPython));
2331 }(IPython));
2332
2332
General Comments 0
You need to be logged in to leave comments. Login now