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