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