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