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