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