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