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