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