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