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