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