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