##// END OF EJS Templates
Merge pull request #5715 from minrk/log-all-ajax-failures...
Thomas Kluyver -
r16530:e19eb538 merge
parent child Browse files
Show More
@@ -1,513 +1,526 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Utilities
9 // Utilities
10 //============================================================================
10 //============================================================================
11 IPython.namespace('IPython.utils');
11 IPython.namespace('IPython.utils');
12
12
13 IPython.utils = (function (IPython) {
13 IPython.utils = (function (IPython) {
14 "use strict";
14 "use strict";
15
15
16 IPython.load_extensions = function () {
16 IPython.load_extensions = function () {
17 // load one or more IPython notebook extensions with requirejs
17 // load one or more IPython notebook extensions with requirejs
18
18
19 var extensions = [];
19 var extensions = [];
20 var extension_names = arguments;
20 var extension_names = arguments;
21 for (var i = 0; i < extension_names.length; i++) {
21 for (var i = 0; i < extension_names.length; i++) {
22 extensions.push("nbextensions/" + arguments[i]);
22 extensions.push("nbextensions/" + arguments[i]);
23 }
23 }
24
24
25 require(extensions,
25 require(extensions,
26 function () {
26 function () {
27 for (var i = 0; i < arguments.length; i++) {
27 for (var i = 0; i < arguments.length; i++) {
28 var ext = arguments[i];
28 var ext = arguments[i];
29 var ext_name = extension_names[i];
29 var ext_name = extension_names[i];
30 // success callback
30 // success callback
31 console.log("Loaded extension: " + ext_name);
31 console.log("Loaded extension: " + ext_name);
32 if (ext && ext.load_ipython_extension !== undefined) {
32 if (ext && ext.load_ipython_extension !== undefined) {
33 ext.load_ipython_extension();
33 ext.load_ipython_extension();
34 }
34 }
35 }
35 }
36 },
36 },
37 function (err) {
37 function (err) {
38 // failure callback
38 // failure callback
39 console.log("Failed to load extension(s):", err.requireModules, err);
39 console.log("Failed to load extension(s):", err.requireModules, err);
40 }
40 }
41 );
41 );
42 };
42 };
43
43
44 //============================================================================
44 //============================================================================
45 // Cross-browser RegEx Split
45 // Cross-browser RegEx Split
46 //============================================================================
46 //============================================================================
47
47
48 // This code has been MODIFIED from the code licensed below to not replace the
48 // This code has been MODIFIED from the code licensed below to not replace the
49 // default browser split. The license is reproduced here.
49 // default browser split. The license is reproduced here.
50
50
51 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
51 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
52 /*!
52 /*!
53 * Cross-Browser Split 1.1.1
53 * Cross-Browser Split 1.1.1
54 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
54 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
55 * Available under the MIT License
55 * Available under the MIT License
56 * ECMAScript compliant, uniform cross-browser split method
56 * ECMAScript compliant, uniform cross-browser split method
57 */
57 */
58
58
59 /**
59 /**
60 * Splits a string into an array of strings using a regex or string
60 * Splits a string into an array of strings using a regex or string
61 * separator. Matches of the separator are not included in the result array.
61 * separator. Matches of the separator are not included in the result array.
62 * However, if `separator` is a regex that contains capturing groups,
62 * However, if `separator` is a regex that contains capturing groups,
63 * backreferences are spliced into the result each time `separator` is
63 * backreferences are spliced into the result each time `separator` is
64 * matched. Fixes browser bugs compared to the native
64 * matched. Fixes browser bugs compared to the native
65 * `String.prototype.split` and can be used reliably cross-browser.
65 * `String.prototype.split` and can be used reliably cross-browser.
66 * @param {String} str String to split.
66 * @param {String} str String to split.
67 * @param {RegExp|String} separator Regex or string to use for separating
67 * @param {RegExp|String} separator Regex or string to use for separating
68 * the string.
68 * the string.
69 * @param {Number} [limit] Maximum number of items to include in the result
69 * @param {Number} [limit] Maximum number of items to include in the result
70 * array.
70 * array.
71 * @returns {Array} Array of substrings.
71 * @returns {Array} Array of substrings.
72 * @example
72 * @example
73 *
73 *
74 * // Basic use
74 * // Basic use
75 * regex_split('a b c d', ' ');
75 * regex_split('a b c d', ' ');
76 * // -> ['a', 'b', 'c', 'd']
76 * // -> ['a', 'b', 'c', 'd']
77 *
77 *
78 * // With limit
78 * // With limit
79 * regex_split('a b c d', ' ', 2);
79 * regex_split('a b c d', ' ', 2);
80 * // -> ['a', 'b']
80 * // -> ['a', 'b']
81 *
81 *
82 * // Backreferences in result array
82 * // Backreferences in result array
83 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
83 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
84 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
84 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
85 */
85 */
86 var regex_split = function (str, separator, limit) {
86 var regex_split = function (str, separator, limit) {
87 // If `separator` is not a regex, use `split`
87 // If `separator` is not a regex, use `split`
88 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
88 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
89 return split.call(str, separator, limit);
89 return split.call(str, separator, limit);
90 }
90 }
91 var output = [],
91 var output = [],
92 flags = (separator.ignoreCase ? "i" : "") +
92 flags = (separator.ignoreCase ? "i" : "") +
93 (separator.multiline ? "m" : "") +
93 (separator.multiline ? "m" : "") +
94 (separator.extended ? "x" : "") + // Proposed for ES6
94 (separator.extended ? "x" : "") + // Proposed for ES6
95 (separator.sticky ? "y" : ""), // Firefox 3+
95 (separator.sticky ? "y" : ""), // Firefox 3+
96 lastLastIndex = 0,
96 lastLastIndex = 0,
97 // Make `global` and avoid `lastIndex` issues by working with a copy
97 // Make `global` and avoid `lastIndex` issues by working with a copy
98 separator = new RegExp(separator.source, flags + "g"),
98 separator = new RegExp(separator.source, flags + "g"),
99 separator2, match, lastIndex, lastLength;
99 separator2, match, lastIndex, lastLength;
100 str += ""; // Type-convert
100 str += ""; // Type-convert
101
101
102 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
102 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
103 if (!compliantExecNpcg) {
103 if (!compliantExecNpcg) {
104 // Doesn't need flags gy, but they don't hurt
104 // Doesn't need flags gy, but they don't hurt
105 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
105 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
106 }
106 }
107 /* Values for `limit`, per the spec:
107 /* Values for `limit`, per the spec:
108 * If undefined: 4294967295 // Math.pow(2, 32) - 1
108 * If undefined: 4294967295 // Math.pow(2, 32) - 1
109 * If 0, Infinity, or NaN: 0
109 * If 0, Infinity, or NaN: 0
110 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
110 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
111 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
111 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
112 * If other: Type-convert, then use the above rules
112 * If other: Type-convert, then use the above rules
113 */
113 */
114 limit = typeof(limit) === "undefined" ?
114 limit = typeof(limit) === "undefined" ?
115 -1 >>> 0 : // Math.pow(2, 32) - 1
115 -1 >>> 0 : // Math.pow(2, 32) - 1
116 limit >>> 0; // ToUint32(limit)
116 limit >>> 0; // ToUint32(limit)
117 while (match = separator.exec(str)) {
117 while (match = separator.exec(str)) {
118 // `separator.lastIndex` is not reliable cross-browser
118 // `separator.lastIndex` is not reliable cross-browser
119 lastIndex = match.index + match[0].length;
119 lastIndex = match.index + match[0].length;
120 if (lastIndex > lastLastIndex) {
120 if (lastIndex > lastLastIndex) {
121 output.push(str.slice(lastLastIndex, match.index));
121 output.push(str.slice(lastLastIndex, match.index));
122 // Fix browsers whose `exec` methods don't consistently return `undefined` for
122 // Fix browsers whose `exec` methods don't consistently return `undefined` for
123 // nonparticipating capturing groups
123 // nonparticipating capturing groups
124 if (!compliantExecNpcg && match.length > 1) {
124 if (!compliantExecNpcg && match.length > 1) {
125 match[0].replace(separator2, function () {
125 match[0].replace(separator2, function () {
126 for (var i = 1; i < arguments.length - 2; i++) {
126 for (var i = 1; i < arguments.length - 2; i++) {
127 if (typeof(arguments[i]) === "undefined") {
127 if (typeof(arguments[i]) === "undefined") {
128 match[i] = undefined;
128 match[i] = undefined;
129 }
129 }
130 }
130 }
131 });
131 });
132 }
132 }
133 if (match.length > 1 && match.index < str.length) {
133 if (match.length > 1 && match.index < str.length) {
134 Array.prototype.push.apply(output, match.slice(1));
134 Array.prototype.push.apply(output, match.slice(1));
135 }
135 }
136 lastLength = match[0].length;
136 lastLength = match[0].length;
137 lastLastIndex = lastIndex;
137 lastLastIndex = lastIndex;
138 if (output.length >= limit) {
138 if (output.length >= limit) {
139 break;
139 break;
140 }
140 }
141 }
141 }
142 if (separator.lastIndex === match.index) {
142 if (separator.lastIndex === match.index) {
143 separator.lastIndex++; // Avoid an infinite loop
143 separator.lastIndex++; // Avoid an infinite loop
144 }
144 }
145 }
145 }
146 if (lastLastIndex === str.length) {
146 if (lastLastIndex === str.length) {
147 if (lastLength || !separator.test("")) {
147 if (lastLength || !separator.test("")) {
148 output.push("");
148 output.push("");
149 }
149 }
150 } else {
150 } else {
151 output.push(str.slice(lastLastIndex));
151 output.push(str.slice(lastLastIndex));
152 }
152 }
153 return output.length > limit ? output.slice(0, limit) : output;
153 return output.length > limit ? output.slice(0, limit) : output;
154 };
154 };
155
155
156 //============================================================================
156 //============================================================================
157 // End contributed Cross-browser RegEx Split
157 // End contributed Cross-browser RegEx Split
158 //============================================================================
158 //============================================================================
159
159
160
160
161 var uuid = function () {
161 var uuid = function () {
162 // http://www.ietf.org/rfc/rfc4122.txt
162 // http://www.ietf.org/rfc/rfc4122.txt
163 var s = [];
163 var s = [];
164 var hexDigits = "0123456789ABCDEF";
164 var hexDigits = "0123456789ABCDEF";
165 for (var i = 0; i < 32; i++) {
165 for (var i = 0; i < 32; i++) {
166 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
166 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
167 }
167 }
168 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
168 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
169 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
169 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
170
170
171 var uuid = s.join("");
171 var uuid = s.join("");
172 return uuid;
172 return uuid;
173 };
173 };
174
174
175
175
176 //Fix raw text to parse correctly in crazy XML
176 //Fix raw text to parse correctly in crazy XML
177 function xmlencode(string) {
177 function xmlencode(string) {
178 return string.replace(/\&/g,'&'+'amp;')
178 return string.replace(/\&/g,'&'+'amp;')
179 .replace(/</g,'&'+'lt;')
179 .replace(/</g,'&'+'lt;')
180 .replace(/>/g,'&'+'gt;')
180 .replace(/>/g,'&'+'gt;')
181 .replace(/\'/g,'&'+'apos;')
181 .replace(/\'/g,'&'+'apos;')
182 .replace(/\"/g,'&'+'quot;')
182 .replace(/\"/g,'&'+'quot;')
183 .replace(/`/g,'&'+'#96;');
183 .replace(/`/g,'&'+'#96;');
184 }
184 }
185
185
186
186
187 //Map from terminal commands to CSS classes
187 //Map from terminal commands to CSS classes
188 var ansi_colormap = {
188 var ansi_colormap = {
189 "01":"ansibold",
189 "01":"ansibold",
190
190
191 "30":"ansiblack",
191 "30":"ansiblack",
192 "31":"ansired",
192 "31":"ansired",
193 "32":"ansigreen",
193 "32":"ansigreen",
194 "33":"ansiyellow",
194 "33":"ansiyellow",
195 "34":"ansiblue",
195 "34":"ansiblue",
196 "35":"ansipurple",
196 "35":"ansipurple",
197 "36":"ansicyan",
197 "36":"ansicyan",
198 "37":"ansigray",
198 "37":"ansigray",
199
199
200 "40":"ansibgblack",
200 "40":"ansibgblack",
201 "41":"ansibgred",
201 "41":"ansibgred",
202 "42":"ansibggreen",
202 "42":"ansibggreen",
203 "43":"ansibgyellow",
203 "43":"ansibgyellow",
204 "44":"ansibgblue",
204 "44":"ansibgblue",
205 "45":"ansibgpurple",
205 "45":"ansibgpurple",
206 "46":"ansibgcyan",
206 "46":"ansibgcyan",
207 "47":"ansibggray"
207 "47":"ansibggray"
208 };
208 };
209
209
210 function _process_numbers(attrs, numbers) {
210 function _process_numbers(attrs, numbers) {
211 // process ansi escapes
211 // process ansi escapes
212 var n = numbers.shift();
212 var n = numbers.shift();
213 if (ansi_colormap[n]) {
213 if (ansi_colormap[n]) {
214 if ( ! attrs["class"] ) {
214 if ( ! attrs["class"] ) {
215 attrs["class"] = ansi_colormap[n];
215 attrs["class"] = ansi_colormap[n];
216 } else {
216 } else {
217 attrs["class"] += " " + ansi_colormap[n];
217 attrs["class"] += " " + ansi_colormap[n];
218 }
218 }
219 } else if (n == "38" || n == "48") {
219 } else if (n == "38" || n == "48") {
220 // VT100 256 color or 24 bit RGB
220 // VT100 256 color or 24 bit RGB
221 if (numbers.length < 2) {
221 if (numbers.length < 2) {
222 console.log("Not enough fields for VT100 color", numbers);
222 console.log("Not enough fields for VT100 color", numbers);
223 return;
223 return;
224 }
224 }
225
225
226 var index_or_rgb = numbers.shift();
226 var index_or_rgb = numbers.shift();
227 var r,g,b;
227 var r,g,b;
228 if (index_or_rgb == "5") {
228 if (index_or_rgb == "5") {
229 // 256 color
229 // 256 color
230 var idx = parseInt(numbers.shift());
230 var idx = parseInt(numbers.shift());
231 if (idx < 16) {
231 if (idx < 16) {
232 // indexed ANSI
232 // indexed ANSI
233 // ignore bright / non-bright distinction
233 // ignore bright / non-bright distinction
234 idx = idx % 8;
234 idx = idx % 8;
235 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
235 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
236 if ( ! attrs["class"] ) {
236 if ( ! attrs["class"] ) {
237 attrs["class"] = ansiclass;
237 attrs["class"] = ansiclass;
238 } else {
238 } else {
239 attrs["class"] += " " + ansiclass;
239 attrs["class"] += " " + ansiclass;
240 }
240 }
241 return;
241 return;
242 } else if (idx < 232) {
242 } else if (idx < 232) {
243 // 216 color 6x6x6 RGB
243 // 216 color 6x6x6 RGB
244 idx = idx - 16;
244 idx = idx - 16;
245 b = idx % 6;
245 b = idx % 6;
246 g = Math.floor(idx / 6) % 6;
246 g = Math.floor(idx / 6) % 6;
247 r = Math.floor(idx / 36) % 6;
247 r = Math.floor(idx / 36) % 6;
248 // convert to rgb
248 // convert to rgb
249 r = (r * 51);
249 r = (r * 51);
250 g = (g * 51);
250 g = (g * 51);
251 b = (b * 51);
251 b = (b * 51);
252 } else {
252 } else {
253 // grayscale
253 // grayscale
254 idx = idx - 231;
254 idx = idx - 231;
255 // it's 1-24 and should *not* include black or white,
255 // it's 1-24 and should *not* include black or white,
256 // so a 26 point scale
256 // so a 26 point scale
257 r = g = b = Math.floor(idx * 256 / 26);
257 r = g = b = Math.floor(idx * 256 / 26);
258 }
258 }
259 } else if (index_or_rgb == "2") {
259 } else if (index_or_rgb == "2") {
260 // Simple 24 bit RGB
260 // Simple 24 bit RGB
261 if (numbers.length > 3) {
261 if (numbers.length > 3) {
262 console.log("Not enough fields for RGB", numbers);
262 console.log("Not enough fields for RGB", numbers);
263 return;
263 return;
264 }
264 }
265 r = numbers.shift();
265 r = numbers.shift();
266 g = numbers.shift();
266 g = numbers.shift();
267 b = numbers.shift();
267 b = numbers.shift();
268 } else {
268 } else {
269 console.log("unrecognized control", numbers);
269 console.log("unrecognized control", numbers);
270 return;
270 return;
271 }
271 }
272 if (r !== undefined) {
272 if (r !== undefined) {
273 // apply the rgb color
273 // apply the rgb color
274 var line;
274 var line;
275 if (n == "38") {
275 if (n == "38") {
276 line = "color: ";
276 line = "color: ";
277 } else {
277 } else {
278 line = "background-color: ";
278 line = "background-color: ";
279 }
279 }
280 line = line + "rgb(" + r + "," + g + "," + b + ");"
280 line = line + "rgb(" + r + "," + g + "," + b + ");"
281 if ( !attrs["style"] ) {
281 if ( !attrs["style"] ) {
282 attrs["style"] = line;
282 attrs["style"] = line;
283 } else {
283 } else {
284 attrs["style"] += " " + line;
284 attrs["style"] += " " + line;
285 }
285 }
286 }
286 }
287 }
287 }
288 }
288 }
289
289
290 function ansispan(str) {
290 function ansispan(str) {
291 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
291 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
292 // regular ansi escapes (using the table above)
292 // regular ansi escapes (using the table above)
293 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
293 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
294 if (!pattern) {
294 if (!pattern) {
295 // [(01|22|39|)m close spans
295 // [(01|22|39|)m close spans
296 return "</span>";
296 return "</span>";
297 }
297 }
298 // consume sequence of color escapes
298 // consume sequence of color escapes
299 var numbers = pattern.match(/\d+/g);
299 var numbers = pattern.match(/\d+/g);
300 var attrs = {};
300 var attrs = {};
301 while (numbers.length > 0) {
301 while (numbers.length > 0) {
302 _process_numbers(attrs, numbers);
302 _process_numbers(attrs, numbers);
303 }
303 }
304
304
305 var span = "<span ";
305 var span = "<span ";
306 for (var attr in attrs) {
306 for (var attr in attrs) {
307 var value = attrs[attr];
307 var value = attrs[attr];
308 span = span + " " + attr + '="' + attrs[attr] + '"';
308 span = span + " " + attr + '="' + attrs[attr] + '"';
309 }
309 }
310 return span + ">";
310 return span + ">";
311 });
311 });
312 };
312 };
313
313
314 // Transform ANSI color escape codes into HTML <span> tags with css
314 // Transform ANSI color escape codes into HTML <span> tags with css
315 // classes listed in the above ansi_colormap object. The actual color used
315 // classes listed in the above ansi_colormap object. The actual color used
316 // are set in the css file.
316 // are set in the css file.
317 function fixConsole(txt) {
317 function fixConsole(txt) {
318 txt = xmlencode(txt);
318 txt = xmlencode(txt);
319 var re = /\033\[([\dA-Fa-f;]*?)m/;
319 var re = /\033\[([\dA-Fa-f;]*?)m/;
320 var opened = false;
320 var opened = false;
321 var cmds = [];
321 var cmds = [];
322 var opener = "";
322 var opener = "";
323 var closer = "";
323 var closer = "";
324
324
325 // Strip all ANSI codes that are not color related. Matches
325 // Strip all ANSI codes that are not color related. Matches
326 // all ANSI codes that do not end with "m".
326 // all ANSI codes that do not end with "m".
327 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
327 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
328 txt = txt.replace(ignored_re, "");
328 txt = txt.replace(ignored_re, "");
329
329
330 // color ansi codes
330 // color ansi codes
331 txt = ansispan(txt);
331 txt = ansispan(txt);
332 return txt;
332 return txt;
333 }
333 }
334
334
335 // Remove chunks that should be overridden by the effect of
335 // Remove chunks that should be overridden by the effect of
336 // carriage return characters
336 // carriage return characters
337 function fixCarriageReturn(txt) {
337 function fixCarriageReturn(txt) {
338 var tmp = txt;
338 var tmp = txt;
339 do {
339 do {
340 txt = tmp;
340 txt = tmp;
341 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
341 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
342 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
342 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
343 } while (tmp.length < txt.length);
343 } while (tmp.length < txt.length);
344 return txt;
344 return txt;
345 }
345 }
346
346
347 // Locate any URLs and convert them to a anchor tag
347 // Locate any URLs and convert them to a anchor tag
348 function autoLinkUrls(txt) {
348 function autoLinkUrls(txt) {
349 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
349 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
350 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
350 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
351 }
351 }
352
352
353 var points_to_pixels = function (points) {
353 var points_to_pixels = function (points) {
354 // A reasonably good way of converting between points and pixels.
354 // A reasonably good way of converting between points and pixels.
355 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
355 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
356 $(body).append(test);
356 $(body).append(test);
357 var pixel_per_point = test.width()/10000;
357 var pixel_per_point = test.width()/10000;
358 test.remove();
358 test.remove();
359 return Math.floor(points*pixel_per_point);
359 return Math.floor(points*pixel_per_point);
360 };
360 };
361
361
362 var always_new = function (constructor) {
362 var always_new = function (constructor) {
363 // wrapper around contructor to avoid requiring `var a = new constructor()`
363 // wrapper around contructor to avoid requiring `var a = new constructor()`
364 // useful for passing constructors as callbacks,
364 // useful for passing constructors as callbacks,
365 // not for programmer laziness.
365 // not for programmer laziness.
366 // from http://programmers.stackexchange.com/questions/118798
366 // from http://programmers.stackexchange.com/questions/118798
367 return function () {
367 return function () {
368 var obj = Object.create(constructor.prototype);
368 var obj = Object.create(constructor.prototype);
369 constructor.apply(obj, arguments);
369 constructor.apply(obj, arguments);
370 return obj;
370 return obj;
371 };
371 };
372 };
372 };
373
373
374 var url_path_join = function () {
374 var url_path_join = function () {
375 // join a sequence of url components with '/'
375 // join a sequence of url components with '/'
376 var url = '';
376 var url = '';
377 for (var i = 0; i < arguments.length; i++) {
377 for (var i = 0; i < arguments.length; i++) {
378 if (arguments[i] === '') {
378 if (arguments[i] === '') {
379 continue;
379 continue;
380 }
380 }
381 if (url.length > 0 && url[url.length-1] != '/') {
381 if (url.length > 0 && url[url.length-1] != '/') {
382 url = url + '/' + arguments[i];
382 url = url + '/' + arguments[i];
383 } else {
383 } else {
384 url = url + arguments[i];
384 url = url + arguments[i];
385 }
385 }
386 }
386 }
387 url = url.replace(/\/\/+/, '/');
387 url = url.replace(/\/\/+/, '/');
388 return url;
388 return url;
389 };
389 };
390
390
391 var parse_url = function (url) {
391 var parse_url = function (url) {
392 // an `a` element with an href allows attr-access to the parsed segments of a URL
392 // an `a` element with an href allows attr-access to the parsed segments of a URL
393 // a = parse_url("http://localhost:8888/path/name#hash")
393 // a = parse_url("http://localhost:8888/path/name#hash")
394 // a.protocol = "http:"
394 // a.protocol = "http:"
395 // a.host = "localhost:8888"
395 // a.host = "localhost:8888"
396 // a.hostname = "localhost"
396 // a.hostname = "localhost"
397 // a.port = 8888
397 // a.port = 8888
398 // a.pathname = "/path/name"
398 // a.pathname = "/path/name"
399 // a.hash = "#hash"
399 // a.hash = "#hash"
400 var a = document.createElement("a");
400 var a = document.createElement("a");
401 a.href = url;
401 a.href = url;
402 return a;
402 return a;
403 };
403 };
404
404
405 var encode_uri_components = function (uri) {
405 var encode_uri_components = function (uri) {
406 // encode just the components of a multi-segment uri,
406 // encode just the components of a multi-segment uri,
407 // leaving '/' separators
407 // leaving '/' separators
408 return uri.split('/').map(encodeURIComponent).join('/');
408 return uri.split('/').map(encodeURIComponent).join('/');
409 };
409 };
410
410
411 var url_join_encode = function () {
411 var url_join_encode = function () {
412 // join a sequence of url components with '/',
412 // join a sequence of url components with '/',
413 // encoding each component with encodeURIComponent
413 // encoding each component with encodeURIComponent
414 return encode_uri_components(url_path_join.apply(null, arguments));
414 return encode_uri_components(url_path_join.apply(null, arguments));
415 };
415 };
416
416
417
417
418 var splitext = function (filename) {
418 var splitext = function (filename) {
419 // mimic Python os.path.splitext
419 // mimic Python os.path.splitext
420 // Returns ['base', '.ext']
420 // Returns ['base', '.ext']
421 var idx = filename.lastIndexOf('.');
421 var idx = filename.lastIndexOf('.');
422 if (idx > 0) {
422 if (idx > 0) {
423 return [filename.slice(0, idx), filename.slice(idx)];
423 return [filename.slice(0, idx), filename.slice(idx)];
424 } else {
424 } else {
425 return [filename, ''];
425 return [filename, ''];
426 }
426 }
427 };
427 };
428
428
429
429
430 var escape_html = function (text) {
430 var escape_html = function (text) {
431 // escape text to HTML
431 // escape text to HTML
432 return $("<div/>").text(text).html();
432 return $("<div/>").text(text).html();
433 }
433 }
434
434
435
435
436 var get_body_data = function(key) {
436 var get_body_data = function(key) {
437 // get a url-encoded item from body.data and decode it
437 // get a url-encoded item from body.data and decode it
438 // we should never have any encoded URLs anywhere else in code
438 // we should never have any encoded URLs anywhere else in code
439 // until we are building an actual request
439 // until we are building an actual request
440 return decodeURIComponent($('body').data(key));
440 return decodeURIComponent($('body').data(key));
441 };
441 };
442
442
443
443
444 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
444 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
445 var browser = (function() {
445 var browser = (function() {
446 if (typeof navigator === 'undefined') {
446 if (typeof navigator === 'undefined') {
447 // navigator undefined in node
447 // navigator undefined in node
448 return 'None';
448 return 'None';
449 }
449 }
450 var N= navigator.appName, ua= navigator.userAgent, tem;
450 var N= navigator.appName, ua= navigator.userAgent, tem;
451 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
451 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
452 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
452 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
453 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
453 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
454 return M;
454 return M;
455 })();
455 })();
456
456
457 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
457 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
458 var platform = (function () {
458 var platform = (function () {
459 if (typeof navigator === 'undefined') {
459 if (typeof navigator === 'undefined') {
460 // navigator undefined in node
460 // navigator undefined in node
461 return 'None';
461 return 'None';
462 }
462 }
463 var OSName="None";
463 var OSName="None";
464 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
464 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
465 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
465 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
466 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
466 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
467 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
467 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
468 return OSName
468 return OSName
469 })();
469 })();
470
470
471 var is_or_has = function (a, b) {
471 var is_or_has = function (a, b) {
472 // Is b a child of a or a itself?
472 // Is b a child of a or a itself?
473 return a.has(b).length !==0 || a.is(b);
473 return a.has(b).length !==0 || a.is(b);
474 }
474 }
475
475
476 var is_focused = function (e) {
476 var is_focused = function (e) {
477 // Is element e, or one of its children focused?
477 // Is element e, or one of its children focused?
478 e = $(e);
478 e = $(e);
479 var target = $(document.activeElement);
479 var target = $(document.activeElement);
480 if (target.length > 0) {
480 if (target.length > 0) {
481 if (is_or_has(e, target)) {
481 if (is_or_has(e, target)) {
482 return true;
482 return true;
483 } else {
483 } else {
484 return false;
484 return false;
485 }
485 }
486 } else {
486 } else {
487 return false;
487 return false;
488 }
488 }
489 }
489 }
490
491 var log_ajax_error = function (jqXHR, status, error) {
492 // log ajax failures with informative messages
493 var msg = "API request failed (" + jqXHR.status + "): ";
494 console.log(jqXHR);
495 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
496 msg += jqXHR.responseJSON.message;
497 } else {
498 msg += jqXHR.statusText;
499 }
500 console.log(msg);
501 };
490
502
491 return {
503 return {
492 regex_split : regex_split,
504 regex_split : regex_split,
493 uuid : uuid,
505 uuid : uuid,
494 fixConsole : fixConsole,
506 fixConsole : fixConsole,
495 fixCarriageReturn : fixCarriageReturn,
507 fixCarriageReturn : fixCarriageReturn,
496 autoLinkUrls : autoLinkUrls,
508 autoLinkUrls : autoLinkUrls,
497 points_to_pixels : points_to_pixels,
509 points_to_pixels : points_to_pixels,
498 get_body_data : get_body_data,
510 get_body_data : get_body_data,
499 parse_url : parse_url,
511 parse_url : parse_url,
500 url_path_join : url_path_join,
512 url_path_join : url_path_join,
501 url_join_encode : url_join_encode,
513 url_join_encode : url_join_encode,
502 encode_uri_components : encode_uri_components,
514 encode_uri_components : encode_uri_components,
503 splitext : splitext,
515 splitext : splitext,
504 escape_html : escape_html,
516 escape_html : escape_html,
505 always_new : always_new,
517 always_new : always_new,
506 browser : browser,
518 browser : browser,
507 platform: platform,
519 platform: platform,
508 is_or_has : is_or_has,
520 is_or_has : is_or_has,
509 is_focused : is_focused
521 is_focused : is_focused,
522 log_ajax_error : log_ajax_error,
510 };
523 };
511
524
512 }(IPython));
525 }(IPython));
513
526
@@ -1,2413 +1,2416 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
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 this.options = options = options || {};
26 this.options = options = options || {};
27 this.base_url = options.base_url;
27 this.base_url = options.base_url;
28 this.notebook_path = options.notebook_path;
28 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebook_name;
29 this.notebook_name = options.notebook_name;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = 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 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3; // Increment this when changing the nbformat
56 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 this.save_notebook = function() { // don't allow save until notebook_loaded
61 this.save_notebook = function() { // don't allow save until notebook_loaded
62 this.save_notebook_error(null, null, "Load failed, save is disabled");
62 this.save_notebook_error(null, null, "Load failed, save is disabled");
63 };
63 };
64 };
64 };
65
65
66 /**
66 /**
67 * Tweak the notebook's CSS style.
67 * Tweak the notebook's CSS style.
68 *
68 *
69 * @method style
69 * @method style
70 */
70 */
71 Notebook.prototype.style = function () {
71 Notebook.prototype.style = function () {
72 $('div#notebook').addClass('border-box-sizing');
72 $('div#notebook').addClass('border-box-sizing');
73 };
73 };
74
74
75 /**
75 /**
76 * Create an HTML and CSS representation of the notebook.
76 * Create an HTML and CSS representation of the notebook.
77 *
77 *
78 * @method create_elements
78 * @method create_elements
79 */
79 */
80 Notebook.prototype.create_elements = function () {
80 Notebook.prototype.create_elements = function () {
81 var that = this;
81 var that = this;
82 this.element.attr('tabindex','-1');
82 this.element.attr('tabindex','-1');
83 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
83 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
84 // We add this end_space div to the end of the notebook div to:
84 // We add this end_space div to the end of the notebook div to:
85 // i) provide a margin between the last cell and the end of the notebook
85 // i) provide a margin between the last cell and the end of the notebook
86 // ii) to prevent the div from scrolling up when the last cell is being
86 // ii) to prevent the div from scrolling up when the last cell is being
87 // edited, but is too low on the page, which browsers will do automatically.
87 // edited, but is too low on the page, which browsers will do automatically.
88 var end_space = $('<div/>').addClass('end_space');
88 var end_space = $('<div/>').addClass('end_space');
89 end_space.dblclick(function (e) {
89 end_space.dblclick(function (e) {
90 var ncells = that.ncells();
90 var ncells = that.ncells();
91 that.insert_cell_below('code',ncells-1);
91 that.insert_cell_below('code',ncells-1);
92 });
92 });
93 this.element.append(this.container);
93 this.element.append(this.container);
94 this.container.append(end_space);
94 this.container.append(end_space);
95 };
95 };
96
96
97 /**
97 /**
98 * Bind JavaScript events: key presses and custom IPython events.
98 * Bind JavaScript events: key presses and custom IPython events.
99 *
99 *
100 * @method bind_events
100 * @method bind_events
101 */
101 */
102 Notebook.prototype.bind_events = function () {
102 Notebook.prototype.bind_events = function () {
103 var that = this;
103 var that = this;
104
104
105 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
105 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
106 var index = that.find_cell_index(data.cell);
106 var index = that.find_cell_index(data.cell);
107 var new_cell = that.insert_cell_below('code',index);
107 var new_cell = that.insert_cell_below('code',index);
108 new_cell.set_text(data.text);
108 new_cell.set_text(data.text);
109 that.dirty = true;
109 that.dirty = true;
110 });
110 });
111
111
112 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
112 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
113 that.dirty = data.value;
113 that.dirty = data.value;
114 });
114 });
115
115
116 $([IPython.events]).on('trust_changed.Notebook', function (event, data) {
116 $([IPython.events]).on('trust_changed.Notebook', function (event, data) {
117 that.trusted = data.value;
117 that.trusted = data.value;
118 });
118 });
119
119
120 $([IPython.events]).on('select.Cell', function (event, data) {
120 $([IPython.events]).on('select.Cell', function (event, data) {
121 var index = that.find_cell_index(data.cell);
121 var index = that.find_cell_index(data.cell);
122 that.select(index);
122 that.select(index);
123 });
123 });
124
124
125 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
125 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
126 that.handle_edit_mode(data.cell);
126 that.handle_edit_mode(data.cell);
127 });
127 });
128
128
129 $([IPython.events]).on('command_mode.Cell', function (event, data) {
129 $([IPython.events]).on('command_mode.Cell', function (event, data) {
130 that.handle_command_mode(data.cell);
130 that.handle_command_mode(data.cell);
131 });
131 });
132
132
133 $([IPython.events]).on('status_autorestarting.Kernel', function () {
133 $([IPython.events]).on('status_autorestarting.Kernel', function () {
134 IPython.dialog.modal({
134 IPython.dialog.modal({
135 title: "Kernel Restarting",
135 title: "Kernel Restarting",
136 body: "The kernel appears to have died. It will restart automatically.",
136 body: "The kernel appears to have died. It will restart automatically.",
137 buttons: {
137 buttons: {
138 OK : {
138 OK : {
139 class : "btn-primary"
139 class : "btn-primary"
140 }
140 }
141 }
141 }
142 });
142 });
143 });
143 });
144
144
145 var collapse_time = function (time) {
145 var collapse_time = function (time) {
146 var app_height = $('#ipython-main-app').height(); // content height
146 var app_height = $('#ipython-main-app').height(); // content height
147 var splitter_height = $('div#pager_splitter').outerHeight(true);
147 var splitter_height = $('div#pager_splitter').outerHeight(true);
148 var new_height = app_height - splitter_height;
148 var new_height = app_height - splitter_height;
149 that.element.animate({height : new_height + 'px'}, time);
149 that.element.animate({height : new_height + 'px'}, time);
150 };
150 };
151
151
152 this.element.bind('collapse_pager', function (event, extrap) {
152 this.element.bind('collapse_pager', function (event, extrap) {
153 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
153 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
154 collapse_time(time);
154 collapse_time(time);
155 });
155 });
156
156
157 var expand_time = function (time) {
157 var expand_time = function (time) {
158 var app_height = $('#ipython-main-app').height(); // content height
158 var app_height = $('#ipython-main-app').height(); // content height
159 var splitter_height = $('div#pager_splitter').outerHeight(true);
159 var splitter_height = $('div#pager_splitter').outerHeight(true);
160 var pager_height = $('div#pager').outerHeight(true);
160 var pager_height = $('div#pager').outerHeight(true);
161 var new_height = app_height - pager_height - splitter_height;
161 var new_height = app_height - pager_height - splitter_height;
162 that.element.animate({height : new_height + 'px'}, time);
162 that.element.animate({height : new_height + 'px'}, time);
163 };
163 };
164
164
165 this.element.bind('expand_pager', function (event, extrap) {
165 this.element.bind('expand_pager', function (event, extrap) {
166 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
166 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
167 expand_time(time);
167 expand_time(time);
168 });
168 });
169
169
170 // Firefox 22 broke $(window).on("beforeunload")
170 // Firefox 22 broke $(window).on("beforeunload")
171 // I'm not sure why or how.
171 // I'm not sure why or how.
172 window.onbeforeunload = function (e) {
172 window.onbeforeunload = function (e) {
173 // TODO: Make killing the kernel configurable.
173 // TODO: Make killing the kernel configurable.
174 var kill_kernel = false;
174 var kill_kernel = false;
175 if (kill_kernel) {
175 if (kill_kernel) {
176 that.session.kill_kernel();
176 that.session.kill_kernel();
177 }
177 }
178 // if we are autosaving, trigger an autosave on nav-away.
178 // if we are autosaving, trigger an autosave on nav-away.
179 // still warn, because if we don't the autosave may fail.
179 // still warn, because if we don't the autosave may fail.
180 if (that.dirty) {
180 if (that.dirty) {
181 if ( that.autosave_interval ) {
181 if ( that.autosave_interval ) {
182 // schedule autosave in a timeout
182 // schedule autosave in a timeout
183 // this gives you a chance to forcefully discard changes
183 // this gives you a chance to forcefully discard changes
184 // by reloading the page if you *really* want to.
184 // by reloading the page if you *really* want to.
185 // the timer doesn't start until you *dismiss* the dialog.
185 // the timer doesn't start until you *dismiss* the dialog.
186 setTimeout(function () {
186 setTimeout(function () {
187 if (that.dirty) {
187 if (that.dirty) {
188 that.save_notebook();
188 that.save_notebook();
189 }
189 }
190 }, 1000);
190 }, 1000);
191 return "Autosave in progress, latest changes may be lost.";
191 return "Autosave in progress, latest changes may be lost.";
192 } else {
192 } else {
193 return "Unsaved changes will be lost.";
193 return "Unsaved changes will be lost.";
194 }
194 }
195 }
195 }
196 // Null is the *only* return value that will make the browser not
196 // Null is the *only* return value that will make the browser not
197 // pop up the "don't leave" dialog.
197 // pop up the "don't leave" dialog.
198 return null;
198 return null;
199 };
199 };
200 };
200 };
201
201
202 /**
202 /**
203 * Set the dirty flag, and trigger the set_dirty.Notebook event
203 * Set the dirty flag, and trigger the set_dirty.Notebook event
204 *
204 *
205 * @method set_dirty
205 * @method set_dirty
206 */
206 */
207 Notebook.prototype.set_dirty = function (value) {
207 Notebook.prototype.set_dirty = function (value) {
208 if (value === undefined) {
208 if (value === undefined) {
209 value = true;
209 value = true;
210 }
210 }
211 if (this.dirty == value) {
211 if (this.dirty == value) {
212 return;
212 return;
213 }
213 }
214 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
214 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
215 };
215 };
216
216
217 /**
217 /**
218 * Scroll the top of the page to a given cell.
218 * Scroll the top of the page to a given cell.
219 *
219 *
220 * @method scroll_to_cell
220 * @method scroll_to_cell
221 * @param {Number} cell_number An index of the cell to view
221 * @param {Number} cell_number An index of the cell to view
222 * @param {Number} time Animation time in milliseconds
222 * @param {Number} time Animation time in milliseconds
223 * @return {Number} Pixel offset from the top of the container
223 * @return {Number} Pixel offset from the top of the container
224 */
224 */
225 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
225 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
226 var cells = this.get_cells();
226 var cells = this.get_cells();
227 time = time || 0;
227 time = time || 0;
228 cell_number = Math.min(cells.length-1,cell_number);
228 cell_number = Math.min(cells.length-1,cell_number);
229 cell_number = Math.max(0 ,cell_number);
229 cell_number = Math.max(0 ,cell_number);
230 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
230 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
231 this.element.animate({scrollTop:scroll_value}, time);
231 this.element.animate({scrollTop:scroll_value}, time);
232 return scroll_value;
232 return scroll_value;
233 };
233 };
234
234
235 /**
235 /**
236 * Scroll to the bottom of the page.
236 * Scroll to the bottom of the page.
237 *
237 *
238 * @method scroll_to_bottom
238 * @method scroll_to_bottom
239 */
239 */
240 Notebook.prototype.scroll_to_bottom = function () {
240 Notebook.prototype.scroll_to_bottom = function () {
241 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
241 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
242 };
242 };
243
243
244 /**
244 /**
245 * Scroll to the top of the page.
245 * Scroll to the top of the page.
246 *
246 *
247 * @method scroll_to_top
247 * @method scroll_to_top
248 */
248 */
249 Notebook.prototype.scroll_to_top = function () {
249 Notebook.prototype.scroll_to_top = function () {
250 this.element.animate({scrollTop:0}, 0);
250 this.element.animate({scrollTop:0}, 0);
251 };
251 };
252
252
253 // Edit Notebook metadata
253 // Edit Notebook metadata
254
254
255 Notebook.prototype.edit_metadata = function () {
255 Notebook.prototype.edit_metadata = function () {
256 var that = this;
256 var that = this;
257 IPython.dialog.edit_metadata(this.metadata, function (md) {
257 IPython.dialog.edit_metadata(this.metadata, function (md) {
258 that.metadata = md;
258 that.metadata = md;
259 }, 'Notebook');
259 }, 'Notebook');
260 };
260 };
261
261
262 // Cell indexing, retrieval, etc.
262 // Cell indexing, retrieval, etc.
263
263
264 /**
264 /**
265 * Get all cell elements in the notebook.
265 * Get all cell elements in the notebook.
266 *
266 *
267 * @method get_cell_elements
267 * @method get_cell_elements
268 * @return {jQuery} A selector of all cell elements
268 * @return {jQuery} A selector of all cell elements
269 */
269 */
270 Notebook.prototype.get_cell_elements = function () {
270 Notebook.prototype.get_cell_elements = function () {
271 return this.container.children("div.cell");
271 return this.container.children("div.cell");
272 };
272 };
273
273
274 /**
274 /**
275 * Get a particular cell element.
275 * Get a particular cell element.
276 *
276 *
277 * @method get_cell_element
277 * @method get_cell_element
278 * @param {Number} index An index of a cell to select
278 * @param {Number} index An index of a cell to select
279 * @return {jQuery} A selector of the given cell.
279 * @return {jQuery} A selector of the given cell.
280 */
280 */
281 Notebook.prototype.get_cell_element = function (index) {
281 Notebook.prototype.get_cell_element = function (index) {
282 var result = null;
282 var result = null;
283 var e = this.get_cell_elements().eq(index);
283 var e = this.get_cell_elements().eq(index);
284 if (e.length !== 0) {
284 if (e.length !== 0) {
285 result = e;
285 result = e;
286 }
286 }
287 return result;
287 return result;
288 };
288 };
289
289
290 /**
290 /**
291 * Try to get a particular cell by msg_id.
291 * Try to get a particular cell by msg_id.
292 *
292 *
293 * @method get_msg_cell
293 * @method get_msg_cell
294 * @param {String} msg_id A message UUID
294 * @param {String} msg_id A message UUID
295 * @return {Cell} Cell or null if no cell was found.
295 * @return {Cell} Cell or null if no cell was found.
296 */
296 */
297 Notebook.prototype.get_msg_cell = function (msg_id) {
297 Notebook.prototype.get_msg_cell = function (msg_id) {
298 return IPython.CodeCell.msg_cells[msg_id] || null;
298 return IPython.CodeCell.msg_cells[msg_id] || null;
299 };
299 };
300
300
301 /**
301 /**
302 * Count the cells in this notebook.
302 * Count the cells in this notebook.
303 *
303 *
304 * @method ncells
304 * @method ncells
305 * @return {Number} The number of cells in this notebook
305 * @return {Number} The number of cells in this notebook
306 */
306 */
307 Notebook.prototype.ncells = function () {
307 Notebook.prototype.ncells = function () {
308 return this.get_cell_elements().length;
308 return this.get_cell_elements().length;
309 };
309 };
310
310
311 /**
311 /**
312 * Get all Cell objects in this notebook.
312 * Get all Cell objects in this notebook.
313 *
313 *
314 * @method get_cells
314 * @method get_cells
315 * @return {Array} This notebook's Cell objects
315 * @return {Array} This notebook's Cell objects
316 */
316 */
317 // TODO: we are often calling cells as cells()[i], which we should optimize
317 // TODO: we are often calling cells as cells()[i], which we should optimize
318 // to cells(i) or a new method.
318 // to cells(i) or a new method.
319 Notebook.prototype.get_cells = function () {
319 Notebook.prototype.get_cells = function () {
320 return this.get_cell_elements().toArray().map(function (e) {
320 return this.get_cell_elements().toArray().map(function (e) {
321 return $(e).data("cell");
321 return $(e).data("cell");
322 });
322 });
323 };
323 };
324
324
325 /**
325 /**
326 * Get a Cell object from this notebook.
326 * Get a Cell object from this notebook.
327 *
327 *
328 * @method get_cell
328 * @method get_cell
329 * @param {Number} index An index of a cell to retrieve
329 * @param {Number} index An index of a cell to retrieve
330 * @return {Cell} A particular cell
330 * @return {Cell} A particular cell
331 */
331 */
332 Notebook.prototype.get_cell = function (index) {
332 Notebook.prototype.get_cell = function (index) {
333 var result = null;
333 var result = null;
334 var ce = this.get_cell_element(index);
334 var ce = this.get_cell_element(index);
335 if (ce !== null) {
335 if (ce !== null) {
336 result = ce.data('cell');
336 result = ce.data('cell');
337 }
337 }
338 return result;
338 return result;
339 };
339 };
340
340
341 /**
341 /**
342 * Get the cell below a given cell.
342 * Get the cell below a given cell.
343 *
343 *
344 * @method get_next_cell
344 * @method get_next_cell
345 * @param {Cell} cell The provided cell
345 * @param {Cell} cell The provided cell
346 * @return {Cell} The next cell
346 * @return {Cell} The next cell
347 */
347 */
348 Notebook.prototype.get_next_cell = function (cell) {
348 Notebook.prototype.get_next_cell = function (cell) {
349 var result = null;
349 var result = null;
350 var index = this.find_cell_index(cell);
350 var index = this.find_cell_index(cell);
351 if (this.is_valid_cell_index(index+1)) {
351 if (this.is_valid_cell_index(index+1)) {
352 result = this.get_cell(index+1);
352 result = this.get_cell(index+1);
353 }
353 }
354 return result;
354 return result;
355 };
355 };
356
356
357 /**
357 /**
358 * Get the cell above a given cell.
358 * Get the cell above a given cell.
359 *
359 *
360 * @method get_prev_cell
360 * @method get_prev_cell
361 * @param {Cell} cell The provided cell
361 * @param {Cell} cell The provided cell
362 * @return {Cell} The previous cell
362 * @return {Cell} The previous cell
363 */
363 */
364 Notebook.prototype.get_prev_cell = function (cell) {
364 Notebook.prototype.get_prev_cell = function (cell) {
365 // TODO: off-by-one
365 // TODO: off-by-one
366 // nb.get_prev_cell(nb.get_cell(1)) is null
366 // nb.get_prev_cell(nb.get_cell(1)) is null
367 var result = null;
367 var result = null;
368 var index = this.find_cell_index(cell);
368 var index = this.find_cell_index(cell);
369 if (index !== null && index > 1) {
369 if (index !== null && index > 1) {
370 result = this.get_cell(index-1);
370 result = this.get_cell(index-1);
371 }
371 }
372 return result;
372 return result;
373 };
373 };
374
374
375 /**
375 /**
376 * Get the numeric index of a given cell.
376 * Get the numeric index of a given cell.
377 *
377 *
378 * @method find_cell_index
378 * @method find_cell_index
379 * @param {Cell} cell The provided cell
379 * @param {Cell} cell The provided cell
380 * @return {Number} The cell's numeric index
380 * @return {Number} The cell's numeric index
381 */
381 */
382 Notebook.prototype.find_cell_index = function (cell) {
382 Notebook.prototype.find_cell_index = function (cell) {
383 var result = null;
383 var result = null;
384 this.get_cell_elements().filter(function (index) {
384 this.get_cell_elements().filter(function (index) {
385 if ($(this).data("cell") === cell) {
385 if ($(this).data("cell") === cell) {
386 result = index;
386 result = index;
387 }
387 }
388 });
388 });
389 return result;
389 return result;
390 };
390 };
391
391
392 /**
392 /**
393 * Get a given index , or the selected index if none is provided.
393 * Get a given index , or the selected index if none is provided.
394 *
394 *
395 * @method index_or_selected
395 * @method index_or_selected
396 * @param {Number} index A cell's index
396 * @param {Number} index A cell's index
397 * @return {Number} The given index, or selected index if none is provided.
397 * @return {Number} The given index, or selected index if none is provided.
398 */
398 */
399 Notebook.prototype.index_or_selected = function (index) {
399 Notebook.prototype.index_or_selected = function (index) {
400 var i;
400 var i;
401 if (index === undefined || index === null) {
401 if (index === undefined || index === null) {
402 i = this.get_selected_index();
402 i = this.get_selected_index();
403 if (i === null) {
403 if (i === null) {
404 i = 0;
404 i = 0;
405 }
405 }
406 } else {
406 } else {
407 i = index;
407 i = index;
408 }
408 }
409 return i;
409 return i;
410 };
410 };
411
411
412 /**
412 /**
413 * Get the currently selected cell.
413 * Get the currently selected cell.
414 * @method get_selected_cell
414 * @method get_selected_cell
415 * @return {Cell} The selected cell
415 * @return {Cell} The selected cell
416 */
416 */
417 Notebook.prototype.get_selected_cell = function () {
417 Notebook.prototype.get_selected_cell = function () {
418 var index = this.get_selected_index();
418 var index = this.get_selected_index();
419 return this.get_cell(index);
419 return this.get_cell(index);
420 };
420 };
421
421
422 /**
422 /**
423 * Check whether a cell index is valid.
423 * Check whether a cell index is valid.
424 *
424 *
425 * @method is_valid_cell_index
425 * @method is_valid_cell_index
426 * @param {Number} index A cell index
426 * @param {Number} index A cell index
427 * @return True if the index is valid, false otherwise
427 * @return True if the index is valid, false otherwise
428 */
428 */
429 Notebook.prototype.is_valid_cell_index = function (index) {
429 Notebook.prototype.is_valid_cell_index = function (index) {
430 if (index !== null && index >= 0 && index < this.ncells()) {
430 if (index !== null && index >= 0 && index < this.ncells()) {
431 return true;
431 return true;
432 } else {
432 } else {
433 return false;
433 return false;
434 }
434 }
435 };
435 };
436
436
437 /**
437 /**
438 * Get the index of the currently selected cell.
438 * Get the index of the currently selected cell.
439
439
440 * @method get_selected_index
440 * @method get_selected_index
441 * @return {Number} The selected cell's numeric index
441 * @return {Number} The selected cell's numeric index
442 */
442 */
443 Notebook.prototype.get_selected_index = function () {
443 Notebook.prototype.get_selected_index = function () {
444 var result = null;
444 var result = null;
445 this.get_cell_elements().filter(function (index) {
445 this.get_cell_elements().filter(function (index) {
446 if ($(this).data("cell").selected === true) {
446 if ($(this).data("cell").selected === true) {
447 result = index;
447 result = index;
448 }
448 }
449 });
449 });
450 return result;
450 return result;
451 };
451 };
452
452
453
453
454 // Cell selection.
454 // Cell selection.
455
455
456 /**
456 /**
457 * Programmatically select a cell.
457 * Programmatically select a cell.
458 *
458 *
459 * @method select
459 * @method select
460 * @param {Number} index A cell's index
460 * @param {Number} index A cell's index
461 * @return {Notebook} This notebook
461 * @return {Notebook} This notebook
462 */
462 */
463 Notebook.prototype.select = function (index) {
463 Notebook.prototype.select = function (index) {
464 if (this.is_valid_cell_index(index)) {
464 if (this.is_valid_cell_index(index)) {
465 var sindex = this.get_selected_index();
465 var sindex = this.get_selected_index();
466 if (sindex !== null && index !== sindex) {
466 if (sindex !== null && index !== sindex) {
467 // If we are about to select a different cell, make sure we are
467 // If we are about to select a different cell, make sure we are
468 // first in command mode.
468 // first in command mode.
469 if (this.mode !== 'command') {
469 if (this.mode !== 'command') {
470 this.command_mode();
470 this.command_mode();
471 }
471 }
472 this.get_cell(sindex).unselect();
472 this.get_cell(sindex).unselect();
473 }
473 }
474 var cell = this.get_cell(index);
474 var cell = this.get_cell(index);
475 cell.select();
475 cell.select();
476 if (cell.cell_type === 'heading') {
476 if (cell.cell_type === 'heading') {
477 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
477 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
478 {'cell_type':cell.cell_type,level:cell.level}
478 {'cell_type':cell.cell_type,level:cell.level}
479 );
479 );
480 } else {
480 } else {
481 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
481 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
482 {'cell_type':cell.cell_type}
482 {'cell_type':cell.cell_type}
483 );
483 );
484 }
484 }
485 }
485 }
486 return this;
486 return this;
487 };
487 };
488
488
489 /**
489 /**
490 * Programmatically select the next cell.
490 * Programmatically select the next cell.
491 *
491 *
492 * @method select_next
492 * @method select_next
493 * @return {Notebook} This notebook
493 * @return {Notebook} This notebook
494 */
494 */
495 Notebook.prototype.select_next = function () {
495 Notebook.prototype.select_next = function () {
496 var index = this.get_selected_index();
496 var index = this.get_selected_index();
497 this.select(index+1);
497 this.select(index+1);
498 return this;
498 return this;
499 };
499 };
500
500
501 /**
501 /**
502 * Programmatically select the previous cell.
502 * Programmatically select the previous cell.
503 *
503 *
504 * @method select_prev
504 * @method select_prev
505 * @return {Notebook} This notebook
505 * @return {Notebook} This notebook
506 */
506 */
507 Notebook.prototype.select_prev = function () {
507 Notebook.prototype.select_prev = function () {
508 var index = this.get_selected_index();
508 var index = this.get_selected_index();
509 this.select(index-1);
509 this.select(index-1);
510 return this;
510 return this;
511 };
511 };
512
512
513
513
514 // Edit/Command mode
514 // Edit/Command mode
515
515
516 /**
516 /**
517 * Gets the index of the cell that is in edit mode.
517 * Gets the index of the cell that is in edit mode.
518 *
518 *
519 * @method get_edit_index
519 * @method get_edit_index
520 *
520 *
521 * @return index {int}
521 * @return index {int}
522 **/
522 **/
523 Notebook.prototype.get_edit_index = function () {
523 Notebook.prototype.get_edit_index = function () {
524 var result = null;
524 var result = null;
525 this.get_cell_elements().filter(function (index) {
525 this.get_cell_elements().filter(function (index) {
526 if ($(this).data("cell").mode === 'edit') {
526 if ($(this).data("cell").mode === 'edit') {
527 result = index;
527 result = index;
528 }
528 }
529 });
529 });
530 return result;
530 return result;
531 };
531 };
532
532
533 /**
533 /**
534 * Handle when a a cell blurs and the notebook should enter command mode.
534 * Handle when a a cell blurs and the notebook should enter command mode.
535 *
535 *
536 * @method handle_command_mode
536 * @method handle_command_mode
537 * @param [cell] {Cell} Cell to enter command mode on.
537 * @param [cell] {Cell} Cell to enter command mode on.
538 **/
538 **/
539 Notebook.prototype.handle_command_mode = function (cell) {
539 Notebook.prototype.handle_command_mode = function (cell) {
540 if (this.mode !== 'command') {
540 if (this.mode !== 'command') {
541 cell.command_mode();
541 cell.command_mode();
542 this.mode = 'command';
542 this.mode = 'command';
543 $([IPython.events]).trigger('command_mode.Notebook');
543 $([IPython.events]).trigger('command_mode.Notebook');
544 IPython.keyboard_manager.command_mode();
544 IPython.keyboard_manager.command_mode();
545 }
545 }
546 };
546 };
547
547
548 /**
548 /**
549 * Make the notebook enter command mode.
549 * Make the notebook enter command mode.
550 *
550 *
551 * @method command_mode
551 * @method command_mode
552 **/
552 **/
553 Notebook.prototype.command_mode = function () {
553 Notebook.prototype.command_mode = function () {
554 var cell = this.get_cell(this.get_edit_index());
554 var cell = this.get_cell(this.get_edit_index());
555 if (cell && this.mode !== 'command') {
555 if (cell && this.mode !== 'command') {
556 // We don't call cell.command_mode, but rather call cell.focus_cell()
556 // We don't call cell.command_mode, but rather call cell.focus_cell()
557 // which will blur and CM editor and trigger the call to
557 // which will blur and CM editor and trigger the call to
558 // handle_command_mode.
558 // handle_command_mode.
559 cell.focus_cell();
559 cell.focus_cell();
560 }
560 }
561 };
561 };
562
562
563 /**
563 /**
564 * Handle when a cell fires it's edit_mode event.
564 * Handle when a cell fires it's edit_mode event.
565 *
565 *
566 * @method handle_edit_mode
566 * @method handle_edit_mode
567 * @param [cell] {Cell} Cell to enter edit mode on.
567 * @param [cell] {Cell} Cell to enter edit mode on.
568 **/
568 **/
569 Notebook.prototype.handle_edit_mode = function (cell) {
569 Notebook.prototype.handle_edit_mode = function (cell) {
570 if (cell && this.mode !== 'edit') {
570 if (cell && this.mode !== 'edit') {
571 cell.edit_mode();
571 cell.edit_mode();
572 this.mode = 'edit';
572 this.mode = 'edit';
573 $([IPython.events]).trigger('edit_mode.Notebook');
573 $([IPython.events]).trigger('edit_mode.Notebook');
574 IPython.keyboard_manager.edit_mode();
574 IPython.keyboard_manager.edit_mode();
575 }
575 }
576 };
576 };
577
577
578 /**
578 /**
579 * Make a cell enter edit mode.
579 * Make a cell enter edit mode.
580 *
580 *
581 * @method edit_mode
581 * @method edit_mode
582 **/
582 **/
583 Notebook.prototype.edit_mode = function () {
583 Notebook.prototype.edit_mode = function () {
584 var cell = this.get_selected_cell();
584 var cell = this.get_selected_cell();
585 if (cell && this.mode !== 'edit') {
585 if (cell && this.mode !== 'edit') {
586 cell.unrender();
586 cell.unrender();
587 cell.focus_editor();
587 cell.focus_editor();
588 }
588 }
589 };
589 };
590
590
591 /**
591 /**
592 * Focus the currently selected cell.
592 * Focus the currently selected cell.
593 *
593 *
594 * @method focus_cell
594 * @method focus_cell
595 **/
595 **/
596 Notebook.prototype.focus_cell = function () {
596 Notebook.prototype.focus_cell = function () {
597 var cell = this.get_selected_cell();
597 var cell = this.get_selected_cell();
598 if (cell === null) {return;} // No cell is selected
598 if (cell === null) {return;} // No cell is selected
599 cell.focus_cell();
599 cell.focus_cell();
600 };
600 };
601
601
602 // Cell movement
602 // Cell movement
603
603
604 /**
604 /**
605 * Move given (or selected) cell up and select it.
605 * Move given (or selected) cell up and select it.
606 *
606 *
607 * @method move_cell_up
607 * @method move_cell_up
608 * @param [index] {integer} cell index
608 * @param [index] {integer} cell index
609 * @return {Notebook} This notebook
609 * @return {Notebook} This notebook
610 **/
610 **/
611 Notebook.prototype.move_cell_up = function (index) {
611 Notebook.prototype.move_cell_up = function (index) {
612 var i = this.index_or_selected(index);
612 var i = this.index_or_selected(index);
613 if (this.is_valid_cell_index(i) && i > 0) {
613 if (this.is_valid_cell_index(i) && i > 0) {
614 var pivot = this.get_cell_element(i-1);
614 var pivot = this.get_cell_element(i-1);
615 var tomove = this.get_cell_element(i);
615 var tomove = this.get_cell_element(i);
616 if (pivot !== null && tomove !== null) {
616 if (pivot !== null && tomove !== null) {
617 tomove.detach();
617 tomove.detach();
618 pivot.before(tomove);
618 pivot.before(tomove);
619 this.select(i-1);
619 this.select(i-1);
620 var cell = this.get_selected_cell();
620 var cell = this.get_selected_cell();
621 cell.focus_cell();
621 cell.focus_cell();
622 }
622 }
623 this.set_dirty(true);
623 this.set_dirty(true);
624 }
624 }
625 return this;
625 return this;
626 };
626 };
627
627
628
628
629 /**
629 /**
630 * Move given (or selected) cell down and select it
630 * Move given (or selected) cell down and select it
631 *
631 *
632 * @method move_cell_down
632 * @method move_cell_down
633 * @param [index] {integer} cell index
633 * @param [index] {integer} cell index
634 * @return {Notebook} This notebook
634 * @return {Notebook} This notebook
635 **/
635 **/
636 Notebook.prototype.move_cell_down = function (index) {
636 Notebook.prototype.move_cell_down = function (index) {
637 var i = this.index_or_selected(index);
637 var i = this.index_or_selected(index);
638 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
638 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
639 var pivot = this.get_cell_element(i+1);
639 var pivot = this.get_cell_element(i+1);
640 var tomove = this.get_cell_element(i);
640 var tomove = this.get_cell_element(i);
641 if (pivot !== null && tomove !== null) {
641 if (pivot !== null && tomove !== null) {
642 tomove.detach();
642 tomove.detach();
643 pivot.after(tomove);
643 pivot.after(tomove);
644 this.select(i+1);
644 this.select(i+1);
645 var cell = this.get_selected_cell();
645 var cell = this.get_selected_cell();
646 cell.focus_cell();
646 cell.focus_cell();
647 }
647 }
648 }
648 }
649 this.set_dirty();
649 this.set_dirty();
650 return this;
650 return this;
651 };
651 };
652
652
653
653
654 // Insertion, deletion.
654 // Insertion, deletion.
655
655
656 /**
656 /**
657 * Delete a cell from the notebook.
657 * Delete a cell from the notebook.
658 *
658 *
659 * @method delete_cell
659 * @method delete_cell
660 * @param [index] A cell's numeric index
660 * @param [index] A cell's numeric index
661 * @return {Notebook} This notebook
661 * @return {Notebook} This notebook
662 */
662 */
663 Notebook.prototype.delete_cell = function (index) {
663 Notebook.prototype.delete_cell = function (index) {
664 var i = this.index_or_selected(index);
664 var i = this.index_or_selected(index);
665 var cell = this.get_selected_cell();
665 var cell = this.get_selected_cell();
666 this.undelete_backup = cell.toJSON();
666 this.undelete_backup = cell.toJSON();
667 $('#undelete_cell').removeClass('disabled');
667 $('#undelete_cell').removeClass('disabled');
668 if (this.is_valid_cell_index(i)) {
668 if (this.is_valid_cell_index(i)) {
669 var old_ncells = this.ncells();
669 var old_ncells = this.ncells();
670 var ce = this.get_cell_element(i);
670 var ce = this.get_cell_element(i);
671 ce.remove();
671 ce.remove();
672 if (i === 0) {
672 if (i === 0) {
673 // Always make sure we have at least one cell.
673 // Always make sure we have at least one cell.
674 if (old_ncells === 1) {
674 if (old_ncells === 1) {
675 this.insert_cell_below('code');
675 this.insert_cell_below('code');
676 }
676 }
677 this.select(0);
677 this.select(0);
678 this.undelete_index = 0;
678 this.undelete_index = 0;
679 this.undelete_below = false;
679 this.undelete_below = false;
680 } else if (i === old_ncells-1 && i !== 0) {
680 } else if (i === old_ncells-1 && i !== 0) {
681 this.select(i-1);
681 this.select(i-1);
682 this.undelete_index = i - 1;
682 this.undelete_index = i - 1;
683 this.undelete_below = true;
683 this.undelete_below = true;
684 } else {
684 } else {
685 this.select(i);
685 this.select(i);
686 this.undelete_index = i;
686 this.undelete_index = i;
687 this.undelete_below = false;
687 this.undelete_below = false;
688 }
688 }
689 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
689 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
690 this.set_dirty(true);
690 this.set_dirty(true);
691 }
691 }
692 return this;
692 return this;
693 };
693 };
694
694
695 /**
695 /**
696 * Restore the most recently deleted cell.
696 * Restore the most recently deleted cell.
697 *
697 *
698 * @method undelete
698 * @method undelete
699 */
699 */
700 Notebook.prototype.undelete_cell = function() {
700 Notebook.prototype.undelete_cell = function() {
701 if (this.undelete_backup !== null && this.undelete_index !== null) {
701 if (this.undelete_backup !== null && this.undelete_index !== null) {
702 var current_index = this.get_selected_index();
702 var current_index = this.get_selected_index();
703 if (this.undelete_index < current_index) {
703 if (this.undelete_index < current_index) {
704 current_index = current_index + 1;
704 current_index = current_index + 1;
705 }
705 }
706 if (this.undelete_index >= this.ncells()) {
706 if (this.undelete_index >= this.ncells()) {
707 this.select(this.ncells() - 1);
707 this.select(this.ncells() - 1);
708 }
708 }
709 else {
709 else {
710 this.select(this.undelete_index);
710 this.select(this.undelete_index);
711 }
711 }
712 var cell_data = this.undelete_backup;
712 var cell_data = this.undelete_backup;
713 var new_cell = null;
713 var new_cell = null;
714 if (this.undelete_below) {
714 if (this.undelete_below) {
715 new_cell = this.insert_cell_below(cell_data.cell_type);
715 new_cell = this.insert_cell_below(cell_data.cell_type);
716 } else {
716 } else {
717 new_cell = this.insert_cell_above(cell_data.cell_type);
717 new_cell = this.insert_cell_above(cell_data.cell_type);
718 }
718 }
719 new_cell.fromJSON(cell_data);
719 new_cell.fromJSON(cell_data);
720 if (this.undelete_below) {
720 if (this.undelete_below) {
721 this.select(current_index+1);
721 this.select(current_index+1);
722 } else {
722 } else {
723 this.select(current_index);
723 this.select(current_index);
724 }
724 }
725 this.undelete_backup = null;
725 this.undelete_backup = null;
726 this.undelete_index = null;
726 this.undelete_index = null;
727 }
727 }
728 $('#undelete_cell').addClass('disabled');
728 $('#undelete_cell').addClass('disabled');
729 };
729 };
730
730
731 /**
731 /**
732 * Insert a cell so that after insertion the cell is at given index.
732 * Insert a cell so that after insertion the cell is at given index.
733 *
733 *
734 * Similar to insert_above, but index parameter is mandatory
734 * Similar to insert_above, but index parameter is mandatory
735 *
735 *
736 * Index will be brought back into the accissible range [0,n]
736 * Index will be brought back into the accissible range [0,n]
737 *
737 *
738 * @method insert_cell_at_index
738 * @method insert_cell_at_index
739 * @param type {string} in ['code','markdown','heading']
739 * @param type {string} in ['code','markdown','heading']
740 * @param [index] {int} a valid index where to inser cell
740 * @param [index] {int} a valid index where to inser cell
741 *
741 *
742 * @return cell {cell|null} created cell or null
742 * @return cell {cell|null} created cell or null
743 **/
743 **/
744 Notebook.prototype.insert_cell_at_index = function(type, index){
744 Notebook.prototype.insert_cell_at_index = function(type, index){
745
745
746 var ncells = this.ncells();
746 var ncells = this.ncells();
747 index = Math.min(index,ncells);
747 index = Math.min(index,ncells);
748 index = Math.max(index,0);
748 index = Math.max(index,0);
749 var cell = null;
749 var cell = null;
750
750
751 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
751 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
752 if (type === 'code') {
752 if (type === 'code') {
753 cell = new IPython.CodeCell(this.kernel);
753 cell = new IPython.CodeCell(this.kernel);
754 cell.set_input_prompt();
754 cell.set_input_prompt();
755 } else if (type === 'markdown') {
755 } else if (type === 'markdown') {
756 cell = new IPython.MarkdownCell();
756 cell = new IPython.MarkdownCell();
757 } else if (type === 'raw') {
757 } else if (type === 'raw') {
758 cell = new IPython.RawCell();
758 cell = new IPython.RawCell();
759 } else if (type === 'heading') {
759 } else if (type === 'heading') {
760 cell = new IPython.HeadingCell();
760 cell = new IPython.HeadingCell();
761 }
761 }
762
762
763 if(this._insert_element_at_index(cell.element,index)) {
763 if(this._insert_element_at_index(cell.element,index)) {
764 cell.render();
764 cell.render();
765 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
765 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
766 cell.refresh();
766 cell.refresh();
767 // We used to select the cell after we refresh it, but there
767 // We used to select the cell after we refresh it, but there
768 // are now cases were this method is called where select is
768 // are now cases were this method is called where select is
769 // not appropriate. The selection logic should be handled by the
769 // not appropriate. The selection logic should be handled by the
770 // caller of the the top level insert_cell methods.
770 // caller of the the top level insert_cell methods.
771 this.set_dirty(true);
771 this.set_dirty(true);
772 }
772 }
773 }
773 }
774 return cell;
774 return cell;
775
775
776 };
776 };
777
777
778 /**
778 /**
779 * Insert an element at given cell index.
779 * Insert an element at given cell index.
780 *
780 *
781 * @method _insert_element_at_index
781 * @method _insert_element_at_index
782 * @param element {dom element} a cell element
782 * @param element {dom element} a cell element
783 * @param [index] {int} a valid index where to inser cell
783 * @param [index] {int} a valid index where to inser cell
784 * @private
784 * @private
785 *
785 *
786 * return true if everything whent fine.
786 * return true if everything whent fine.
787 **/
787 **/
788 Notebook.prototype._insert_element_at_index = function(element, index){
788 Notebook.prototype._insert_element_at_index = function(element, index){
789 if (element === undefined){
789 if (element === undefined){
790 return false;
790 return false;
791 }
791 }
792
792
793 var ncells = this.ncells();
793 var ncells = this.ncells();
794
794
795 if (ncells === 0) {
795 if (ncells === 0) {
796 // special case append if empty
796 // special case append if empty
797 this.element.find('div.end_space').before(element);
797 this.element.find('div.end_space').before(element);
798 } else if ( ncells === index ) {
798 } else if ( ncells === index ) {
799 // special case append it the end, but not empty
799 // special case append it the end, but not empty
800 this.get_cell_element(index-1).after(element);
800 this.get_cell_element(index-1).after(element);
801 } else if (this.is_valid_cell_index(index)) {
801 } else if (this.is_valid_cell_index(index)) {
802 // otherwise always somewhere to append to
802 // otherwise always somewhere to append to
803 this.get_cell_element(index).before(element);
803 this.get_cell_element(index).before(element);
804 } else {
804 } else {
805 return false;
805 return false;
806 }
806 }
807
807
808 if (this.undelete_index !== null && index <= this.undelete_index) {
808 if (this.undelete_index !== null && index <= this.undelete_index) {
809 this.undelete_index = this.undelete_index + 1;
809 this.undelete_index = this.undelete_index + 1;
810 this.set_dirty(true);
810 this.set_dirty(true);
811 }
811 }
812 return true;
812 return true;
813 };
813 };
814
814
815 /**
815 /**
816 * Insert a cell of given type above given index, or at top
816 * Insert a cell of given type above given index, or at top
817 * of notebook if index smaller than 0.
817 * of notebook if index smaller than 0.
818 *
818 *
819 * default index value is the one of currently selected cell
819 * default index value is the one of currently selected cell
820 *
820 *
821 * @method insert_cell_above
821 * @method insert_cell_above
822 * @param type {string} cell type
822 * @param type {string} cell type
823 * @param [index] {integer}
823 * @param [index] {integer}
824 *
824 *
825 * @return handle to created cell or null
825 * @return handle to created cell or null
826 **/
826 **/
827 Notebook.prototype.insert_cell_above = function (type, index) {
827 Notebook.prototype.insert_cell_above = function (type, index) {
828 index = this.index_or_selected(index);
828 index = this.index_or_selected(index);
829 return this.insert_cell_at_index(type, index);
829 return this.insert_cell_at_index(type, index);
830 };
830 };
831
831
832 /**
832 /**
833 * Insert a cell of given type below given index, or at bottom
833 * Insert a cell of given type below given index, or at bottom
834 * of notebook if index greater thatn number of cell
834 * of notebook if index greater thatn number of cell
835 *
835 *
836 * default index value is the one of currently selected cell
836 * default index value is the one of currently selected cell
837 *
837 *
838 * @method insert_cell_below
838 * @method insert_cell_below
839 * @param type {string} cell type
839 * @param type {string} cell type
840 * @param [index] {integer}
840 * @param [index] {integer}
841 *
841 *
842 * @return handle to created cell or null
842 * @return handle to created cell or null
843 *
843 *
844 **/
844 **/
845 Notebook.prototype.insert_cell_below = function (type, index) {
845 Notebook.prototype.insert_cell_below = function (type, index) {
846 index = this.index_or_selected(index);
846 index = this.index_or_selected(index);
847 return this.insert_cell_at_index(type, index+1);
847 return this.insert_cell_at_index(type, index+1);
848 };
848 };
849
849
850
850
851 /**
851 /**
852 * Insert cell at end of notebook
852 * Insert cell at end of notebook
853 *
853 *
854 * @method insert_cell_at_bottom
854 * @method insert_cell_at_bottom
855 * @param {String} type cell type
855 * @param {String} type cell type
856 *
856 *
857 * @return the added cell; or null
857 * @return the added cell; or null
858 **/
858 **/
859 Notebook.prototype.insert_cell_at_bottom = function (type){
859 Notebook.prototype.insert_cell_at_bottom = function (type){
860 var len = this.ncells();
860 var len = this.ncells();
861 return this.insert_cell_below(type,len-1);
861 return this.insert_cell_below(type,len-1);
862 };
862 };
863
863
864 /**
864 /**
865 * Turn a cell into a code cell.
865 * Turn a cell into a code cell.
866 *
866 *
867 * @method to_code
867 * @method to_code
868 * @param {Number} [index] A cell's index
868 * @param {Number} [index] A cell's index
869 */
869 */
870 Notebook.prototype.to_code = function (index) {
870 Notebook.prototype.to_code = function (index) {
871 var i = this.index_or_selected(index);
871 var i = this.index_or_selected(index);
872 if (this.is_valid_cell_index(i)) {
872 if (this.is_valid_cell_index(i)) {
873 var source_element = this.get_cell_element(i);
873 var source_element = this.get_cell_element(i);
874 var source_cell = source_element.data("cell");
874 var source_cell = source_element.data("cell");
875 if (!(source_cell instanceof IPython.CodeCell)) {
875 if (!(source_cell instanceof IPython.CodeCell)) {
876 var target_cell = this.insert_cell_below('code',i);
876 var target_cell = this.insert_cell_below('code',i);
877 var text = source_cell.get_text();
877 var text = source_cell.get_text();
878 if (text === source_cell.placeholder) {
878 if (text === source_cell.placeholder) {
879 text = '';
879 text = '';
880 }
880 }
881 target_cell.set_text(text);
881 target_cell.set_text(text);
882 // make this value the starting point, so that we can only undo
882 // make this value the starting point, so that we can only undo
883 // to this state, instead of a blank cell
883 // to this state, instead of a blank cell
884 target_cell.code_mirror.clearHistory();
884 target_cell.code_mirror.clearHistory();
885 source_element.remove();
885 source_element.remove();
886 this.select(i);
886 this.select(i);
887 this.set_dirty(true);
887 this.set_dirty(true);
888 }
888 }
889 }
889 }
890 };
890 };
891
891
892 /**
892 /**
893 * Turn a cell into a Markdown cell.
893 * Turn a cell into a Markdown cell.
894 *
894 *
895 * @method to_markdown
895 * @method to_markdown
896 * @param {Number} [index] A cell's index
896 * @param {Number} [index] A cell's index
897 */
897 */
898 Notebook.prototype.to_markdown = function (index) {
898 Notebook.prototype.to_markdown = function (index) {
899 var i = this.index_or_selected(index);
899 var i = this.index_or_selected(index);
900 if (this.is_valid_cell_index(i)) {
900 if (this.is_valid_cell_index(i)) {
901 var source_element = this.get_cell_element(i);
901 var source_element = this.get_cell_element(i);
902 var source_cell = source_element.data("cell");
902 var source_cell = source_element.data("cell");
903 if (!(source_cell instanceof IPython.MarkdownCell)) {
903 if (!(source_cell instanceof IPython.MarkdownCell)) {
904 var target_cell = this.insert_cell_below('markdown',i);
904 var target_cell = this.insert_cell_below('markdown',i);
905 var text = source_cell.get_text();
905 var text = source_cell.get_text();
906 if (text === source_cell.placeholder) {
906 if (text === source_cell.placeholder) {
907 text = '';
907 text = '';
908 }
908 }
909 // We must show the editor before setting its contents
909 // We must show the editor before setting its contents
910 target_cell.unrender();
910 target_cell.unrender();
911 target_cell.set_text(text);
911 target_cell.set_text(text);
912 // make this value the starting point, so that we can only undo
912 // make this value the starting point, so that we can only undo
913 // to this state, instead of a blank cell
913 // to this state, instead of a blank cell
914 target_cell.code_mirror.clearHistory();
914 target_cell.code_mirror.clearHistory();
915 source_element.remove();
915 source_element.remove();
916 this.select(i);
916 this.select(i);
917 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
917 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
918 target_cell.render();
918 target_cell.render();
919 }
919 }
920 this.set_dirty(true);
920 this.set_dirty(true);
921 }
921 }
922 }
922 }
923 };
923 };
924
924
925 /**
925 /**
926 * Turn a cell into a raw text cell.
926 * Turn a cell into a raw text cell.
927 *
927 *
928 * @method to_raw
928 * @method to_raw
929 * @param {Number} [index] A cell's index
929 * @param {Number} [index] A cell's index
930 */
930 */
931 Notebook.prototype.to_raw = function (index) {
931 Notebook.prototype.to_raw = function (index) {
932 var i = this.index_or_selected(index);
932 var i = this.index_or_selected(index);
933 if (this.is_valid_cell_index(i)) {
933 if (this.is_valid_cell_index(i)) {
934 var source_element = this.get_cell_element(i);
934 var source_element = this.get_cell_element(i);
935 var source_cell = source_element.data("cell");
935 var source_cell = source_element.data("cell");
936 var target_cell = null;
936 var target_cell = null;
937 if (!(source_cell instanceof IPython.RawCell)) {
937 if (!(source_cell instanceof IPython.RawCell)) {
938 target_cell = this.insert_cell_below('raw',i);
938 target_cell = this.insert_cell_below('raw',i);
939 var text = source_cell.get_text();
939 var text = source_cell.get_text();
940 if (text === source_cell.placeholder) {
940 if (text === source_cell.placeholder) {
941 text = '';
941 text = '';
942 }
942 }
943 // We must show the editor before setting its contents
943 // We must show the editor before setting its contents
944 target_cell.unrender();
944 target_cell.unrender();
945 target_cell.set_text(text);
945 target_cell.set_text(text);
946 // make this value the starting point, so that we can only undo
946 // make this value the starting point, so that we can only undo
947 // to this state, instead of a blank cell
947 // to this state, instead of a blank cell
948 target_cell.code_mirror.clearHistory();
948 target_cell.code_mirror.clearHistory();
949 source_element.remove();
949 source_element.remove();
950 this.select(i);
950 this.select(i);
951 this.set_dirty(true);
951 this.set_dirty(true);
952 }
952 }
953 }
953 }
954 };
954 };
955
955
956 /**
956 /**
957 * Turn a cell into a heading cell.
957 * Turn a cell into a heading cell.
958 *
958 *
959 * @method to_heading
959 * @method to_heading
960 * @param {Number} [index] A cell's index
960 * @param {Number} [index] A cell's index
961 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
961 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
962 */
962 */
963 Notebook.prototype.to_heading = function (index, level) {
963 Notebook.prototype.to_heading = function (index, level) {
964 level = level || 1;
964 level = level || 1;
965 var i = this.index_or_selected(index);
965 var i = this.index_or_selected(index);
966 if (this.is_valid_cell_index(i)) {
966 if (this.is_valid_cell_index(i)) {
967 var source_element = this.get_cell_element(i);
967 var source_element = this.get_cell_element(i);
968 var source_cell = source_element.data("cell");
968 var source_cell = source_element.data("cell");
969 var target_cell = null;
969 var target_cell = null;
970 if (source_cell instanceof IPython.HeadingCell) {
970 if (source_cell instanceof IPython.HeadingCell) {
971 source_cell.set_level(level);
971 source_cell.set_level(level);
972 } else {
972 } else {
973 target_cell = this.insert_cell_below('heading',i);
973 target_cell = this.insert_cell_below('heading',i);
974 var text = source_cell.get_text();
974 var text = source_cell.get_text();
975 if (text === source_cell.placeholder) {
975 if (text === source_cell.placeholder) {
976 text = '';
976 text = '';
977 }
977 }
978 // We must show the editor before setting its contents
978 // We must show the editor before setting its contents
979 target_cell.set_level(level);
979 target_cell.set_level(level);
980 target_cell.unrender();
980 target_cell.unrender();
981 target_cell.set_text(text);
981 target_cell.set_text(text);
982 // make this value the starting point, so that we can only undo
982 // make this value the starting point, so that we can only undo
983 // to this state, instead of a blank cell
983 // to this state, instead of a blank cell
984 target_cell.code_mirror.clearHistory();
984 target_cell.code_mirror.clearHistory();
985 source_element.remove();
985 source_element.remove();
986 this.select(i);
986 this.select(i);
987 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
987 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
988 target_cell.render();
988 target_cell.render();
989 }
989 }
990 }
990 }
991 this.set_dirty(true);
991 this.set_dirty(true);
992 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
992 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
993 {'cell_type':'heading',level:level}
993 {'cell_type':'heading',level:level}
994 );
994 );
995 }
995 }
996 };
996 };
997
997
998
998
999 // Cut/Copy/Paste
999 // Cut/Copy/Paste
1000
1000
1001 /**
1001 /**
1002 * Enable UI elements for pasting cells.
1002 * Enable UI elements for pasting cells.
1003 *
1003 *
1004 * @method enable_paste
1004 * @method enable_paste
1005 */
1005 */
1006 Notebook.prototype.enable_paste = function () {
1006 Notebook.prototype.enable_paste = function () {
1007 var that = this;
1007 var that = this;
1008 if (!this.paste_enabled) {
1008 if (!this.paste_enabled) {
1009 $('#paste_cell_replace').removeClass('disabled')
1009 $('#paste_cell_replace').removeClass('disabled')
1010 .on('click', function () {that.paste_cell_replace();});
1010 .on('click', function () {that.paste_cell_replace();});
1011 $('#paste_cell_above').removeClass('disabled')
1011 $('#paste_cell_above').removeClass('disabled')
1012 .on('click', function () {that.paste_cell_above();});
1012 .on('click', function () {that.paste_cell_above();});
1013 $('#paste_cell_below').removeClass('disabled')
1013 $('#paste_cell_below').removeClass('disabled')
1014 .on('click', function () {that.paste_cell_below();});
1014 .on('click', function () {that.paste_cell_below();});
1015 this.paste_enabled = true;
1015 this.paste_enabled = true;
1016 }
1016 }
1017 };
1017 };
1018
1018
1019 /**
1019 /**
1020 * Disable UI elements for pasting cells.
1020 * Disable UI elements for pasting cells.
1021 *
1021 *
1022 * @method disable_paste
1022 * @method disable_paste
1023 */
1023 */
1024 Notebook.prototype.disable_paste = function () {
1024 Notebook.prototype.disable_paste = function () {
1025 if (this.paste_enabled) {
1025 if (this.paste_enabled) {
1026 $('#paste_cell_replace').addClass('disabled').off('click');
1026 $('#paste_cell_replace').addClass('disabled').off('click');
1027 $('#paste_cell_above').addClass('disabled').off('click');
1027 $('#paste_cell_above').addClass('disabled').off('click');
1028 $('#paste_cell_below').addClass('disabled').off('click');
1028 $('#paste_cell_below').addClass('disabled').off('click');
1029 this.paste_enabled = false;
1029 this.paste_enabled = false;
1030 }
1030 }
1031 };
1031 };
1032
1032
1033 /**
1033 /**
1034 * Cut a cell.
1034 * Cut a cell.
1035 *
1035 *
1036 * @method cut_cell
1036 * @method cut_cell
1037 */
1037 */
1038 Notebook.prototype.cut_cell = function () {
1038 Notebook.prototype.cut_cell = function () {
1039 this.copy_cell();
1039 this.copy_cell();
1040 this.delete_cell();
1040 this.delete_cell();
1041 };
1041 };
1042
1042
1043 /**
1043 /**
1044 * Copy a cell.
1044 * Copy a cell.
1045 *
1045 *
1046 * @method copy_cell
1046 * @method copy_cell
1047 */
1047 */
1048 Notebook.prototype.copy_cell = function () {
1048 Notebook.prototype.copy_cell = function () {
1049 var cell = this.get_selected_cell();
1049 var cell = this.get_selected_cell();
1050 this.clipboard = cell.toJSON();
1050 this.clipboard = cell.toJSON();
1051 this.enable_paste();
1051 this.enable_paste();
1052 };
1052 };
1053
1053
1054 /**
1054 /**
1055 * Replace the selected cell with a cell in the clipboard.
1055 * Replace the selected cell with a cell in the clipboard.
1056 *
1056 *
1057 * @method paste_cell_replace
1057 * @method paste_cell_replace
1058 */
1058 */
1059 Notebook.prototype.paste_cell_replace = function () {
1059 Notebook.prototype.paste_cell_replace = function () {
1060 if (this.clipboard !== null && this.paste_enabled) {
1060 if (this.clipboard !== null && this.paste_enabled) {
1061 var cell_data = this.clipboard;
1061 var cell_data = this.clipboard;
1062 var new_cell = this.insert_cell_above(cell_data.cell_type);
1062 var new_cell = this.insert_cell_above(cell_data.cell_type);
1063 new_cell.fromJSON(cell_data);
1063 new_cell.fromJSON(cell_data);
1064 var old_cell = this.get_next_cell(new_cell);
1064 var old_cell = this.get_next_cell(new_cell);
1065 this.delete_cell(this.find_cell_index(old_cell));
1065 this.delete_cell(this.find_cell_index(old_cell));
1066 this.select(this.find_cell_index(new_cell));
1066 this.select(this.find_cell_index(new_cell));
1067 }
1067 }
1068 };
1068 };
1069
1069
1070 /**
1070 /**
1071 * Paste a cell from the clipboard above the selected cell.
1071 * Paste a cell from the clipboard above the selected cell.
1072 *
1072 *
1073 * @method paste_cell_above
1073 * @method paste_cell_above
1074 */
1074 */
1075 Notebook.prototype.paste_cell_above = function () {
1075 Notebook.prototype.paste_cell_above = function () {
1076 if (this.clipboard !== null && this.paste_enabled) {
1076 if (this.clipboard !== null && this.paste_enabled) {
1077 var cell_data = this.clipboard;
1077 var cell_data = this.clipboard;
1078 var new_cell = this.insert_cell_above(cell_data.cell_type);
1078 var new_cell = this.insert_cell_above(cell_data.cell_type);
1079 new_cell.fromJSON(cell_data);
1079 new_cell.fromJSON(cell_data);
1080 new_cell.focus_cell();
1080 new_cell.focus_cell();
1081 }
1081 }
1082 };
1082 };
1083
1083
1084 /**
1084 /**
1085 * Paste a cell from the clipboard below the selected cell.
1085 * Paste a cell from the clipboard below the selected cell.
1086 *
1086 *
1087 * @method paste_cell_below
1087 * @method paste_cell_below
1088 */
1088 */
1089 Notebook.prototype.paste_cell_below = function () {
1089 Notebook.prototype.paste_cell_below = function () {
1090 if (this.clipboard !== null && this.paste_enabled) {
1090 if (this.clipboard !== null && this.paste_enabled) {
1091 var cell_data = this.clipboard;
1091 var cell_data = this.clipboard;
1092 var new_cell = this.insert_cell_below(cell_data.cell_type);
1092 var new_cell = this.insert_cell_below(cell_data.cell_type);
1093 new_cell.fromJSON(cell_data);
1093 new_cell.fromJSON(cell_data);
1094 new_cell.focus_cell();
1094 new_cell.focus_cell();
1095 }
1095 }
1096 };
1096 };
1097
1097
1098 // Split/merge
1098 // Split/merge
1099
1099
1100 /**
1100 /**
1101 * Split the selected cell into two, at the cursor.
1101 * Split the selected cell into two, at the cursor.
1102 *
1102 *
1103 * @method split_cell
1103 * @method split_cell
1104 */
1104 */
1105 Notebook.prototype.split_cell = function () {
1105 Notebook.prototype.split_cell = function () {
1106 var mdc = IPython.MarkdownCell;
1106 var mdc = IPython.MarkdownCell;
1107 var rc = IPython.RawCell;
1107 var rc = IPython.RawCell;
1108 var cell = this.get_selected_cell();
1108 var cell = this.get_selected_cell();
1109 if (cell.is_splittable()) {
1109 if (cell.is_splittable()) {
1110 var texta = cell.get_pre_cursor();
1110 var texta = cell.get_pre_cursor();
1111 var textb = cell.get_post_cursor();
1111 var textb = cell.get_post_cursor();
1112 if (cell instanceof IPython.CodeCell) {
1112 if (cell instanceof IPython.CodeCell) {
1113 // In this case the operations keep the notebook in its existing mode
1113 // In this case the operations keep the notebook in its existing mode
1114 // so we don't need to do any post-op mode changes.
1114 // so we don't need to do any post-op mode changes.
1115 cell.set_text(textb);
1115 cell.set_text(textb);
1116 var new_cell = this.insert_cell_above('code');
1116 var new_cell = this.insert_cell_above('code');
1117 new_cell.set_text(texta);
1117 new_cell.set_text(texta);
1118 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1118 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1119 // We know cell is !rendered so we can use set_text.
1119 // We know cell is !rendered so we can use set_text.
1120 cell.set_text(textb);
1120 cell.set_text(textb);
1121 var new_cell = this.insert_cell_above(cell.cell_type);
1121 var new_cell = this.insert_cell_above(cell.cell_type);
1122 // Unrender the new cell so we can call set_text.
1122 // Unrender the new cell so we can call set_text.
1123 new_cell.unrender();
1123 new_cell.unrender();
1124 new_cell.set_text(texta);
1124 new_cell.set_text(texta);
1125 }
1125 }
1126 }
1126 }
1127 };
1127 };
1128
1128
1129 /**
1129 /**
1130 * Combine the selected cell into the cell above it.
1130 * Combine the selected cell into the cell above it.
1131 *
1131 *
1132 * @method merge_cell_above
1132 * @method merge_cell_above
1133 */
1133 */
1134 Notebook.prototype.merge_cell_above = function () {
1134 Notebook.prototype.merge_cell_above = function () {
1135 var mdc = IPython.MarkdownCell;
1135 var mdc = IPython.MarkdownCell;
1136 var rc = IPython.RawCell;
1136 var rc = IPython.RawCell;
1137 var index = this.get_selected_index();
1137 var index = this.get_selected_index();
1138 var cell = this.get_cell(index);
1138 var cell = this.get_cell(index);
1139 var render = cell.rendered;
1139 var render = cell.rendered;
1140 if (!cell.is_mergeable()) {
1140 if (!cell.is_mergeable()) {
1141 return;
1141 return;
1142 }
1142 }
1143 if (index > 0) {
1143 if (index > 0) {
1144 var upper_cell = this.get_cell(index-1);
1144 var upper_cell = this.get_cell(index-1);
1145 if (!upper_cell.is_mergeable()) {
1145 if (!upper_cell.is_mergeable()) {
1146 return;
1146 return;
1147 }
1147 }
1148 var upper_text = upper_cell.get_text();
1148 var upper_text = upper_cell.get_text();
1149 var text = cell.get_text();
1149 var text = cell.get_text();
1150 if (cell instanceof IPython.CodeCell) {
1150 if (cell instanceof IPython.CodeCell) {
1151 cell.set_text(upper_text+'\n'+text);
1151 cell.set_text(upper_text+'\n'+text);
1152 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1152 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1153 cell.unrender(); // Must unrender before we set_text.
1153 cell.unrender(); // Must unrender before we set_text.
1154 cell.set_text(upper_text+'\n\n'+text);
1154 cell.set_text(upper_text+'\n\n'+text);
1155 if (render) {
1155 if (render) {
1156 // The rendered state of the final cell should match
1156 // The rendered state of the final cell should match
1157 // that of the original selected cell;
1157 // that of the original selected cell;
1158 cell.render();
1158 cell.render();
1159 }
1159 }
1160 }
1160 }
1161 this.delete_cell(index-1);
1161 this.delete_cell(index-1);
1162 this.select(this.find_cell_index(cell));
1162 this.select(this.find_cell_index(cell));
1163 }
1163 }
1164 };
1164 };
1165
1165
1166 /**
1166 /**
1167 * Combine the selected cell into the cell below it.
1167 * Combine the selected cell into the cell below it.
1168 *
1168 *
1169 * @method merge_cell_below
1169 * @method merge_cell_below
1170 */
1170 */
1171 Notebook.prototype.merge_cell_below = function () {
1171 Notebook.prototype.merge_cell_below = function () {
1172 var mdc = IPython.MarkdownCell;
1172 var mdc = IPython.MarkdownCell;
1173 var rc = IPython.RawCell;
1173 var rc = IPython.RawCell;
1174 var index = this.get_selected_index();
1174 var index = this.get_selected_index();
1175 var cell = this.get_cell(index);
1175 var cell = this.get_cell(index);
1176 var render = cell.rendered;
1176 var render = cell.rendered;
1177 if (!cell.is_mergeable()) {
1177 if (!cell.is_mergeable()) {
1178 return;
1178 return;
1179 }
1179 }
1180 if (index < this.ncells()-1) {
1180 if (index < this.ncells()-1) {
1181 var lower_cell = this.get_cell(index+1);
1181 var lower_cell = this.get_cell(index+1);
1182 if (!lower_cell.is_mergeable()) {
1182 if (!lower_cell.is_mergeable()) {
1183 return;
1183 return;
1184 }
1184 }
1185 var lower_text = lower_cell.get_text();
1185 var lower_text = lower_cell.get_text();
1186 var text = cell.get_text();
1186 var text = cell.get_text();
1187 if (cell instanceof IPython.CodeCell) {
1187 if (cell instanceof IPython.CodeCell) {
1188 cell.set_text(text+'\n'+lower_text);
1188 cell.set_text(text+'\n'+lower_text);
1189 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1189 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1190 cell.unrender(); // Must unrender before we set_text.
1190 cell.unrender(); // Must unrender before we set_text.
1191 cell.set_text(text+'\n\n'+lower_text);
1191 cell.set_text(text+'\n\n'+lower_text);
1192 if (render) {
1192 if (render) {
1193 // The rendered state of the final cell should match
1193 // The rendered state of the final cell should match
1194 // that of the original selected cell;
1194 // that of the original selected cell;
1195 cell.render();
1195 cell.render();
1196 }
1196 }
1197 }
1197 }
1198 this.delete_cell(index+1);
1198 this.delete_cell(index+1);
1199 this.select(this.find_cell_index(cell));
1199 this.select(this.find_cell_index(cell));
1200 }
1200 }
1201 };
1201 };
1202
1202
1203
1203
1204 // Cell collapsing and output clearing
1204 // Cell collapsing and output clearing
1205
1205
1206 /**
1206 /**
1207 * Hide a cell's output.
1207 * Hide a cell's output.
1208 *
1208 *
1209 * @method collapse_output
1209 * @method collapse_output
1210 * @param {Number} index A cell's numeric index
1210 * @param {Number} index A cell's numeric index
1211 */
1211 */
1212 Notebook.prototype.collapse_output = function (index) {
1212 Notebook.prototype.collapse_output = function (index) {
1213 var i = this.index_or_selected(index);
1213 var i = this.index_or_selected(index);
1214 var cell = this.get_cell(i);
1214 var cell = this.get_cell(i);
1215 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1215 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1216 cell.collapse_output();
1216 cell.collapse_output();
1217 this.set_dirty(true);
1217 this.set_dirty(true);
1218 }
1218 }
1219 };
1219 };
1220
1220
1221 /**
1221 /**
1222 * Hide each code cell's output area.
1222 * Hide each code cell's output area.
1223 *
1223 *
1224 * @method collapse_all_output
1224 * @method collapse_all_output
1225 */
1225 */
1226 Notebook.prototype.collapse_all_output = function () {
1226 Notebook.prototype.collapse_all_output = function () {
1227 $.map(this.get_cells(), function (cell, i) {
1227 $.map(this.get_cells(), function (cell, i) {
1228 if (cell instanceof IPython.CodeCell) {
1228 if (cell instanceof IPython.CodeCell) {
1229 cell.collapse_output();
1229 cell.collapse_output();
1230 }
1230 }
1231 });
1231 });
1232 // this should not be set if the `collapse` key is removed from nbformat
1232 // this should not be set if the `collapse` key is removed from nbformat
1233 this.set_dirty(true);
1233 this.set_dirty(true);
1234 };
1234 };
1235
1235
1236 /**
1236 /**
1237 * Show a cell's output.
1237 * Show a cell's output.
1238 *
1238 *
1239 * @method expand_output
1239 * @method expand_output
1240 * @param {Number} index A cell's numeric index
1240 * @param {Number} index A cell's numeric index
1241 */
1241 */
1242 Notebook.prototype.expand_output = function (index) {
1242 Notebook.prototype.expand_output = function (index) {
1243 var i = this.index_or_selected(index);
1243 var i = this.index_or_selected(index);
1244 var cell = this.get_cell(i);
1244 var cell = this.get_cell(i);
1245 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1245 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1246 cell.expand_output();
1246 cell.expand_output();
1247 this.set_dirty(true);
1247 this.set_dirty(true);
1248 }
1248 }
1249 };
1249 };
1250
1250
1251 /**
1251 /**
1252 * Expand each code cell's output area, and remove scrollbars.
1252 * Expand each code cell's output area, and remove scrollbars.
1253 *
1253 *
1254 * @method expand_all_output
1254 * @method expand_all_output
1255 */
1255 */
1256 Notebook.prototype.expand_all_output = function () {
1256 Notebook.prototype.expand_all_output = function () {
1257 $.map(this.get_cells(), function (cell, i) {
1257 $.map(this.get_cells(), function (cell, i) {
1258 if (cell instanceof IPython.CodeCell) {
1258 if (cell instanceof IPython.CodeCell) {
1259 cell.expand_output();
1259 cell.expand_output();
1260 }
1260 }
1261 });
1261 });
1262 // this should not be set if the `collapse` key is removed from nbformat
1262 // this should not be set if the `collapse` key is removed from nbformat
1263 this.set_dirty(true);
1263 this.set_dirty(true);
1264 };
1264 };
1265
1265
1266 /**
1266 /**
1267 * Clear the selected CodeCell's output area.
1267 * Clear the selected CodeCell's output area.
1268 *
1268 *
1269 * @method clear_output
1269 * @method clear_output
1270 * @param {Number} index A cell's numeric index
1270 * @param {Number} index A cell's numeric index
1271 */
1271 */
1272 Notebook.prototype.clear_output = function (index) {
1272 Notebook.prototype.clear_output = function (index) {
1273 var i = this.index_or_selected(index);
1273 var i = this.index_or_selected(index);
1274 var cell = this.get_cell(i);
1274 var cell = this.get_cell(i);
1275 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1275 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1276 cell.clear_output();
1276 cell.clear_output();
1277 this.set_dirty(true);
1277 this.set_dirty(true);
1278 }
1278 }
1279 };
1279 };
1280
1280
1281 /**
1281 /**
1282 * Clear each code cell's output area.
1282 * Clear each code cell's output area.
1283 *
1283 *
1284 * @method clear_all_output
1284 * @method clear_all_output
1285 */
1285 */
1286 Notebook.prototype.clear_all_output = function () {
1286 Notebook.prototype.clear_all_output = function () {
1287 $.map(this.get_cells(), function (cell, i) {
1287 $.map(this.get_cells(), function (cell, i) {
1288 if (cell instanceof IPython.CodeCell) {
1288 if (cell instanceof IPython.CodeCell) {
1289 cell.clear_output();
1289 cell.clear_output();
1290 }
1290 }
1291 });
1291 });
1292 this.set_dirty(true);
1292 this.set_dirty(true);
1293 };
1293 };
1294
1294
1295 /**
1295 /**
1296 * Scroll the selected CodeCell's output area.
1296 * Scroll the selected CodeCell's output area.
1297 *
1297 *
1298 * @method scroll_output
1298 * @method scroll_output
1299 * @param {Number} index A cell's numeric index
1299 * @param {Number} index A cell's numeric index
1300 */
1300 */
1301 Notebook.prototype.scroll_output = function (index) {
1301 Notebook.prototype.scroll_output = function (index) {
1302 var i = this.index_or_selected(index);
1302 var i = this.index_or_selected(index);
1303 var cell = this.get_cell(i);
1303 var cell = this.get_cell(i);
1304 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1304 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1305 cell.scroll_output();
1305 cell.scroll_output();
1306 this.set_dirty(true);
1306 this.set_dirty(true);
1307 }
1307 }
1308 };
1308 };
1309
1309
1310 /**
1310 /**
1311 * Expand each code cell's output area, and add a scrollbar for long output.
1311 * Expand each code cell's output area, and add a scrollbar for long output.
1312 *
1312 *
1313 * @method scroll_all_output
1313 * @method scroll_all_output
1314 */
1314 */
1315 Notebook.prototype.scroll_all_output = function () {
1315 Notebook.prototype.scroll_all_output = function () {
1316 $.map(this.get_cells(), function (cell, i) {
1316 $.map(this.get_cells(), function (cell, i) {
1317 if (cell instanceof IPython.CodeCell) {
1317 if (cell instanceof IPython.CodeCell) {
1318 cell.scroll_output();
1318 cell.scroll_output();
1319 }
1319 }
1320 });
1320 });
1321 // this should not be set if the `collapse` key is removed from nbformat
1321 // this should not be set if the `collapse` key is removed from nbformat
1322 this.set_dirty(true);
1322 this.set_dirty(true);
1323 };
1323 };
1324
1324
1325 /** Toggle whether a cell's output is collapsed or expanded.
1325 /** Toggle whether a cell's output is collapsed or expanded.
1326 *
1326 *
1327 * @method toggle_output
1327 * @method toggle_output
1328 * @param {Number} index A cell's numeric index
1328 * @param {Number} index A cell's numeric index
1329 */
1329 */
1330 Notebook.prototype.toggle_output = function (index) {
1330 Notebook.prototype.toggle_output = function (index) {
1331 var i = this.index_or_selected(index);
1331 var i = this.index_or_selected(index);
1332 var cell = this.get_cell(i);
1332 var cell = this.get_cell(i);
1333 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1333 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1334 cell.toggle_output();
1334 cell.toggle_output();
1335 this.set_dirty(true);
1335 this.set_dirty(true);
1336 }
1336 }
1337 };
1337 };
1338
1338
1339 /**
1339 /**
1340 * Hide/show the output of all cells.
1340 * Hide/show the output of all cells.
1341 *
1341 *
1342 * @method toggle_all_output
1342 * @method toggle_all_output
1343 */
1343 */
1344 Notebook.prototype.toggle_all_output = function () {
1344 Notebook.prototype.toggle_all_output = function () {
1345 $.map(this.get_cells(), function (cell, i) {
1345 $.map(this.get_cells(), function (cell, i) {
1346 if (cell instanceof IPython.CodeCell) {
1346 if (cell instanceof IPython.CodeCell) {
1347 cell.toggle_output();
1347 cell.toggle_output();
1348 }
1348 }
1349 });
1349 });
1350 // this should not be set if the `collapse` key is removed from nbformat
1350 // this should not be set if the `collapse` key is removed from nbformat
1351 this.set_dirty(true);
1351 this.set_dirty(true);
1352 };
1352 };
1353
1353
1354 /**
1354 /**
1355 * Toggle a scrollbar for long cell outputs.
1355 * Toggle a scrollbar for long cell outputs.
1356 *
1356 *
1357 * @method toggle_output_scroll
1357 * @method toggle_output_scroll
1358 * @param {Number} index A cell's numeric index
1358 * @param {Number} index A cell's numeric index
1359 */
1359 */
1360 Notebook.prototype.toggle_output_scroll = function (index) {
1360 Notebook.prototype.toggle_output_scroll = function (index) {
1361 var i = this.index_or_selected(index);
1361 var i = this.index_or_selected(index);
1362 var cell = this.get_cell(i);
1362 var cell = this.get_cell(i);
1363 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1363 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1364 cell.toggle_output_scroll();
1364 cell.toggle_output_scroll();
1365 this.set_dirty(true);
1365 this.set_dirty(true);
1366 }
1366 }
1367 };
1367 };
1368
1368
1369 /**
1369 /**
1370 * Toggle the scrolling of long output on all cells.
1370 * Toggle the scrolling of long output on all cells.
1371 *
1371 *
1372 * @method toggle_all_output_scrolling
1372 * @method toggle_all_output_scrolling
1373 */
1373 */
1374 Notebook.prototype.toggle_all_output_scroll = function () {
1374 Notebook.prototype.toggle_all_output_scroll = function () {
1375 $.map(this.get_cells(), function (cell, i) {
1375 $.map(this.get_cells(), function (cell, i) {
1376 if (cell instanceof IPython.CodeCell) {
1376 if (cell instanceof IPython.CodeCell) {
1377 cell.toggle_output_scroll();
1377 cell.toggle_output_scroll();
1378 }
1378 }
1379 });
1379 });
1380 // this should not be set if the `collapse` key is removed from nbformat
1380 // this should not be set if the `collapse` key is removed from nbformat
1381 this.set_dirty(true);
1381 this.set_dirty(true);
1382 };
1382 };
1383
1383
1384 // Other cell functions: line numbers, ...
1384 // Other cell functions: line numbers, ...
1385
1385
1386 /**
1386 /**
1387 * Toggle line numbers in the selected cell's input area.
1387 * Toggle line numbers in the selected cell's input area.
1388 *
1388 *
1389 * @method cell_toggle_line_numbers
1389 * @method cell_toggle_line_numbers
1390 */
1390 */
1391 Notebook.prototype.cell_toggle_line_numbers = function() {
1391 Notebook.prototype.cell_toggle_line_numbers = function() {
1392 this.get_selected_cell().toggle_line_numbers();
1392 this.get_selected_cell().toggle_line_numbers();
1393 };
1393 };
1394
1394
1395 // Session related things
1395 // Session related things
1396
1396
1397 /**
1397 /**
1398 * Start a new session and set it on each code cell.
1398 * Start a new session and set it on each code cell.
1399 *
1399 *
1400 * @method start_session
1400 * @method start_session
1401 */
1401 */
1402 Notebook.prototype.start_session = function () {
1402 Notebook.prototype.start_session = function () {
1403 this.session = new IPython.Session(this, this.options);
1403 this.session = new IPython.Session(this, this.options);
1404 this.session.start($.proxy(this._session_started, this));
1404 this.session.start($.proxy(this._session_started, this));
1405 };
1405 };
1406
1406
1407
1407
1408 /**
1408 /**
1409 * Once a session is started, link the code cells to the kernel and pass the
1409 * Once a session is started, link the code cells to the kernel and pass the
1410 * comm manager to the widget manager
1410 * comm manager to the widget manager
1411 *
1411 *
1412 */
1412 */
1413 Notebook.prototype._session_started = function(){
1413 Notebook.prototype._session_started = function(){
1414 this.kernel = this.session.kernel;
1414 this.kernel = this.session.kernel;
1415 var ncells = this.ncells();
1415 var ncells = this.ncells();
1416 for (var i=0; i<ncells; i++) {
1416 for (var i=0; i<ncells; i++) {
1417 var cell = this.get_cell(i);
1417 var cell = this.get_cell(i);
1418 if (cell instanceof IPython.CodeCell) {
1418 if (cell instanceof IPython.CodeCell) {
1419 cell.set_kernel(this.session.kernel);
1419 cell.set_kernel(this.session.kernel);
1420 }
1420 }
1421 }
1421 }
1422 };
1422 };
1423
1423
1424 /**
1424 /**
1425 * Prompt the user to restart the IPython kernel.
1425 * Prompt the user to restart the IPython kernel.
1426 *
1426 *
1427 * @method restart_kernel
1427 * @method restart_kernel
1428 */
1428 */
1429 Notebook.prototype.restart_kernel = function () {
1429 Notebook.prototype.restart_kernel = function () {
1430 var that = this;
1430 var that = this;
1431 IPython.dialog.modal({
1431 IPython.dialog.modal({
1432 title : "Restart kernel or continue running?",
1432 title : "Restart kernel or continue running?",
1433 body : $("<p/>").text(
1433 body : $("<p/>").text(
1434 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1434 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1435 ),
1435 ),
1436 buttons : {
1436 buttons : {
1437 "Continue running" : {},
1437 "Continue running" : {},
1438 "Restart" : {
1438 "Restart" : {
1439 "class" : "btn-danger",
1439 "class" : "btn-danger",
1440 "click" : function() {
1440 "click" : function() {
1441 that.session.restart_kernel();
1441 that.session.restart_kernel();
1442 }
1442 }
1443 }
1443 }
1444 }
1444 }
1445 });
1445 });
1446 };
1446 };
1447
1447
1448 /**
1448 /**
1449 * Execute or render cell outputs and go into command mode.
1449 * Execute or render cell outputs and go into command mode.
1450 *
1450 *
1451 * @method execute_cell
1451 * @method execute_cell
1452 */
1452 */
1453 Notebook.prototype.execute_cell = function () {
1453 Notebook.prototype.execute_cell = function () {
1454 // mode = shift, ctrl, alt
1454 // mode = shift, ctrl, alt
1455 var cell = this.get_selected_cell();
1455 var cell = this.get_selected_cell();
1456 var cell_index = this.find_cell_index(cell);
1456 var cell_index = this.find_cell_index(cell);
1457
1457
1458 cell.execute();
1458 cell.execute();
1459 this.command_mode();
1459 this.command_mode();
1460 this.set_dirty(true);
1460 this.set_dirty(true);
1461 };
1461 };
1462
1462
1463 /**
1463 /**
1464 * Execute or render cell outputs and insert a new cell below.
1464 * Execute or render cell outputs and insert a new cell below.
1465 *
1465 *
1466 * @method execute_cell_and_insert_below
1466 * @method execute_cell_and_insert_below
1467 */
1467 */
1468 Notebook.prototype.execute_cell_and_insert_below = function () {
1468 Notebook.prototype.execute_cell_and_insert_below = function () {
1469 var cell = this.get_selected_cell();
1469 var cell = this.get_selected_cell();
1470 var cell_index = this.find_cell_index(cell);
1470 var cell_index = this.find_cell_index(cell);
1471
1471
1472 cell.execute();
1472 cell.execute();
1473
1473
1474 // If we are at the end always insert a new cell and return
1474 // If we are at the end always insert a new cell and return
1475 if (cell_index === (this.ncells()-1)) {
1475 if (cell_index === (this.ncells()-1)) {
1476 this.command_mode();
1476 this.command_mode();
1477 this.insert_cell_below('code');
1477 this.insert_cell_below('code');
1478 this.select(cell_index+1);
1478 this.select(cell_index+1);
1479 this.edit_mode();
1479 this.edit_mode();
1480 this.scroll_to_bottom();
1480 this.scroll_to_bottom();
1481 this.set_dirty(true);
1481 this.set_dirty(true);
1482 return;
1482 return;
1483 }
1483 }
1484
1484
1485 this.command_mode();
1485 this.command_mode();
1486 this.insert_cell_below('code');
1486 this.insert_cell_below('code');
1487 this.select(cell_index+1);
1487 this.select(cell_index+1);
1488 this.edit_mode();
1488 this.edit_mode();
1489 this.set_dirty(true);
1489 this.set_dirty(true);
1490 };
1490 };
1491
1491
1492 /**
1492 /**
1493 * Execute or render cell outputs and select the next cell.
1493 * Execute or render cell outputs and select the next cell.
1494 *
1494 *
1495 * @method execute_cell_and_select_below
1495 * @method execute_cell_and_select_below
1496 */
1496 */
1497 Notebook.prototype.execute_cell_and_select_below = function () {
1497 Notebook.prototype.execute_cell_and_select_below = function () {
1498
1498
1499 var cell = this.get_selected_cell();
1499 var cell = this.get_selected_cell();
1500 var cell_index = this.find_cell_index(cell);
1500 var cell_index = this.find_cell_index(cell);
1501
1501
1502 cell.execute();
1502 cell.execute();
1503
1503
1504 // If we are at the end always insert a new cell and return
1504 // If we are at the end always insert a new cell and return
1505 if (cell_index === (this.ncells()-1)) {
1505 if (cell_index === (this.ncells()-1)) {
1506 this.command_mode();
1506 this.command_mode();
1507 this.insert_cell_below('code');
1507 this.insert_cell_below('code');
1508 this.select(cell_index+1);
1508 this.select(cell_index+1);
1509 this.edit_mode();
1509 this.edit_mode();
1510 this.scroll_to_bottom();
1510 this.scroll_to_bottom();
1511 this.set_dirty(true);
1511 this.set_dirty(true);
1512 return;
1512 return;
1513 }
1513 }
1514
1514
1515 this.command_mode();
1515 this.command_mode();
1516 this.select(cell_index+1);
1516 this.select(cell_index+1);
1517 this.focus_cell();
1517 this.focus_cell();
1518 this.set_dirty(true);
1518 this.set_dirty(true);
1519 };
1519 };
1520
1520
1521 /**
1521 /**
1522 * Execute all cells below the selected cell.
1522 * Execute all cells below the selected cell.
1523 *
1523 *
1524 * @method execute_cells_below
1524 * @method execute_cells_below
1525 */
1525 */
1526 Notebook.prototype.execute_cells_below = function () {
1526 Notebook.prototype.execute_cells_below = function () {
1527 this.execute_cell_range(this.get_selected_index(), this.ncells());
1527 this.execute_cell_range(this.get_selected_index(), this.ncells());
1528 this.scroll_to_bottom();
1528 this.scroll_to_bottom();
1529 };
1529 };
1530
1530
1531 /**
1531 /**
1532 * Execute all cells above the selected cell.
1532 * Execute all cells above the selected cell.
1533 *
1533 *
1534 * @method execute_cells_above
1534 * @method execute_cells_above
1535 */
1535 */
1536 Notebook.prototype.execute_cells_above = function () {
1536 Notebook.prototype.execute_cells_above = function () {
1537 this.execute_cell_range(0, this.get_selected_index());
1537 this.execute_cell_range(0, this.get_selected_index());
1538 };
1538 };
1539
1539
1540 /**
1540 /**
1541 * Execute all cells.
1541 * Execute all cells.
1542 *
1542 *
1543 * @method execute_all_cells
1543 * @method execute_all_cells
1544 */
1544 */
1545 Notebook.prototype.execute_all_cells = function () {
1545 Notebook.prototype.execute_all_cells = function () {
1546 this.execute_cell_range(0, this.ncells());
1546 this.execute_cell_range(0, this.ncells());
1547 this.scroll_to_bottom();
1547 this.scroll_to_bottom();
1548 };
1548 };
1549
1549
1550 /**
1550 /**
1551 * Execute a contiguous range of cells.
1551 * Execute a contiguous range of cells.
1552 *
1552 *
1553 * @method execute_cell_range
1553 * @method execute_cell_range
1554 * @param {Number} start Index of the first cell to execute (inclusive)
1554 * @param {Number} start Index of the first cell to execute (inclusive)
1555 * @param {Number} end Index of the last cell to execute (exclusive)
1555 * @param {Number} end Index of the last cell to execute (exclusive)
1556 */
1556 */
1557 Notebook.prototype.execute_cell_range = function (start, end) {
1557 Notebook.prototype.execute_cell_range = function (start, end) {
1558 this.command_mode();
1558 this.command_mode();
1559 for (var i=start; i<end; i++) {
1559 for (var i=start; i<end; i++) {
1560 this.select(i);
1560 this.select(i);
1561 this.execute_cell();
1561 this.execute_cell();
1562 }
1562 }
1563 };
1563 };
1564
1564
1565 // Persistance and loading
1565 // Persistance and loading
1566
1566
1567 /**
1567 /**
1568 * Getter method for this notebook's name.
1568 * Getter method for this notebook's name.
1569 *
1569 *
1570 * @method get_notebook_name
1570 * @method get_notebook_name
1571 * @return {String} This notebook's name (excluding file extension)
1571 * @return {String} This notebook's name (excluding file extension)
1572 */
1572 */
1573 Notebook.prototype.get_notebook_name = function () {
1573 Notebook.prototype.get_notebook_name = function () {
1574 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1574 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1575 return nbname;
1575 return nbname;
1576 };
1576 };
1577
1577
1578 /**
1578 /**
1579 * Setter method for this notebook's name.
1579 * Setter method for this notebook's name.
1580 *
1580 *
1581 * @method set_notebook_name
1581 * @method set_notebook_name
1582 * @param {String} name A new name for this notebook
1582 * @param {String} name A new name for this notebook
1583 */
1583 */
1584 Notebook.prototype.set_notebook_name = function (name) {
1584 Notebook.prototype.set_notebook_name = function (name) {
1585 this.notebook_name = name;
1585 this.notebook_name = name;
1586 };
1586 };
1587
1587
1588 /**
1588 /**
1589 * Check that a notebook's name is valid.
1589 * Check that a notebook's name is valid.
1590 *
1590 *
1591 * @method test_notebook_name
1591 * @method test_notebook_name
1592 * @param {String} nbname A name for this notebook
1592 * @param {String} nbname A name for this notebook
1593 * @return {Boolean} True if the name is valid, false if invalid
1593 * @return {Boolean} True if the name is valid, false if invalid
1594 */
1594 */
1595 Notebook.prototype.test_notebook_name = function (nbname) {
1595 Notebook.prototype.test_notebook_name = function (nbname) {
1596 nbname = nbname || '';
1596 nbname = nbname || '';
1597 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1597 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1598 return true;
1598 return true;
1599 } else {
1599 } else {
1600 return false;
1600 return false;
1601 }
1601 }
1602 };
1602 };
1603
1603
1604 /**
1604 /**
1605 * Load a notebook from JSON (.ipynb).
1605 * Load a notebook from JSON (.ipynb).
1606 *
1606 *
1607 * This currently handles one worksheet: others are deleted.
1607 * This currently handles one worksheet: others are deleted.
1608 *
1608 *
1609 * @method fromJSON
1609 * @method fromJSON
1610 * @param {Object} data JSON representation of a notebook
1610 * @param {Object} data JSON representation of a notebook
1611 */
1611 */
1612 Notebook.prototype.fromJSON = function (data) {
1612 Notebook.prototype.fromJSON = function (data) {
1613 var content = data.content;
1613 var content = data.content;
1614 var ncells = this.ncells();
1614 var ncells = this.ncells();
1615 var i;
1615 var i;
1616 for (i=0; i<ncells; i++) {
1616 for (i=0; i<ncells; i++) {
1617 // Always delete cell 0 as they get renumbered as they are deleted.
1617 // Always delete cell 0 as they get renumbered as they are deleted.
1618 this.delete_cell(0);
1618 this.delete_cell(0);
1619 }
1619 }
1620 // Save the metadata and name.
1620 // Save the metadata and name.
1621 this.metadata = content.metadata;
1621 this.metadata = content.metadata;
1622 this.notebook_name = data.name;
1622 this.notebook_name = data.name;
1623 var trusted = true;
1623 var trusted = true;
1624 // Only handle 1 worksheet for now.
1624 // Only handle 1 worksheet for now.
1625 var worksheet = content.worksheets[0];
1625 var worksheet = content.worksheets[0];
1626 if (worksheet !== undefined) {
1626 if (worksheet !== undefined) {
1627 if (worksheet.metadata) {
1627 if (worksheet.metadata) {
1628 this.worksheet_metadata = worksheet.metadata;
1628 this.worksheet_metadata = worksheet.metadata;
1629 }
1629 }
1630 var new_cells = worksheet.cells;
1630 var new_cells = worksheet.cells;
1631 ncells = new_cells.length;
1631 ncells = new_cells.length;
1632 var cell_data = null;
1632 var cell_data = null;
1633 var new_cell = null;
1633 var new_cell = null;
1634 for (i=0; i<ncells; i++) {
1634 for (i=0; i<ncells; i++) {
1635 cell_data = new_cells[i];
1635 cell_data = new_cells[i];
1636 // VERSIONHACK: plaintext -> raw
1636 // VERSIONHACK: plaintext -> raw
1637 // handle never-released plaintext name for raw cells
1637 // handle never-released plaintext name for raw cells
1638 if (cell_data.cell_type === 'plaintext'){
1638 if (cell_data.cell_type === 'plaintext'){
1639 cell_data.cell_type = 'raw';
1639 cell_data.cell_type = 'raw';
1640 }
1640 }
1641
1641
1642 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1642 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1643 new_cell.fromJSON(cell_data);
1643 new_cell.fromJSON(cell_data);
1644 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1644 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1645 trusted = false;
1645 trusted = false;
1646 }
1646 }
1647 }
1647 }
1648 }
1648 }
1649 if (trusted != this.trusted) {
1649 if (trusted != this.trusted) {
1650 this.trusted = trusted;
1650 this.trusted = trusted;
1651 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1651 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1652 }
1652 }
1653 if (content.worksheets.length > 1) {
1653 if (content.worksheets.length > 1) {
1654 IPython.dialog.modal({
1654 IPython.dialog.modal({
1655 title : "Multiple worksheets",
1655 title : "Multiple worksheets",
1656 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1656 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1657 "but this version of IPython can only handle the first. " +
1657 "but this version of IPython can only handle the first. " +
1658 "If you save this notebook, worksheets after the first will be lost.",
1658 "If you save this notebook, worksheets after the first will be lost.",
1659 buttons : {
1659 buttons : {
1660 OK : {
1660 OK : {
1661 class : "btn-danger"
1661 class : "btn-danger"
1662 }
1662 }
1663 }
1663 }
1664 });
1664 });
1665 }
1665 }
1666 };
1666 };
1667
1667
1668 /**
1668 /**
1669 * Dump this notebook into a JSON-friendly object.
1669 * Dump this notebook into a JSON-friendly object.
1670 *
1670 *
1671 * @method toJSON
1671 * @method toJSON
1672 * @return {Object} A JSON-friendly representation of this notebook.
1672 * @return {Object} A JSON-friendly representation of this notebook.
1673 */
1673 */
1674 Notebook.prototype.toJSON = function () {
1674 Notebook.prototype.toJSON = function () {
1675 var cells = this.get_cells();
1675 var cells = this.get_cells();
1676 var ncells = cells.length;
1676 var ncells = cells.length;
1677 var cell_array = new Array(ncells);
1677 var cell_array = new Array(ncells);
1678 var trusted = true;
1678 var trusted = true;
1679 for (var i=0; i<ncells; i++) {
1679 for (var i=0; i<ncells; i++) {
1680 var cell = cells[i];
1680 var cell = cells[i];
1681 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1681 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1682 trusted = false;
1682 trusted = false;
1683 }
1683 }
1684 cell_array[i] = cell.toJSON();
1684 cell_array[i] = cell.toJSON();
1685 }
1685 }
1686 var data = {
1686 var data = {
1687 // Only handle 1 worksheet for now.
1687 // Only handle 1 worksheet for now.
1688 worksheets : [{
1688 worksheets : [{
1689 cells: cell_array,
1689 cells: cell_array,
1690 metadata: this.worksheet_metadata
1690 metadata: this.worksheet_metadata
1691 }],
1691 }],
1692 metadata : this.metadata
1692 metadata : this.metadata
1693 };
1693 };
1694 if (trusted != this.trusted) {
1694 if (trusted != this.trusted) {
1695 this.trusted = trusted;
1695 this.trusted = trusted;
1696 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1696 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1697 }
1697 }
1698 return data;
1698 return data;
1699 };
1699 };
1700
1700
1701 /**
1701 /**
1702 * Start an autosave timer, for periodically saving the notebook.
1702 * Start an autosave timer, for periodically saving the notebook.
1703 *
1703 *
1704 * @method set_autosave_interval
1704 * @method set_autosave_interval
1705 * @param {Integer} interval the autosave interval in milliseconds
1705 * @param {Integer} interval the autosave interval in milliseconds
1706 */
1706 */
1707 Notebook.prototype.set_autosave_interval = function (interval) {
1707 Notebook.prototype.set_autosave_interval = function (interval) {
1708 var that = this;
1708 var that = this;
1709 // clear previous interval, so we don't get simultaneous timers
1709 // clear previous interval, so we don't get simultaneous timers
1710 if (this.autosave_timer) {
1710 if (this.autosave_timer) {
1711 clearInterval(this.autosave_timer);
1711 clearInterval(this.autosave_timer);
1712 }
1712 }
1713
1713
1714 this.autosave_interval = this.minimum_autosave_interval = interval;
1714 this.autosave_interval = this.minimum_autosave_interval = interval;
1715 if (interval) {
1715 if (interval) {
1716 this.autosave_timer = setInterval(function() {
1716 this.autosave_timer = setInterval(function() {
1717 if (that.dirty) {
1717 if (that.dirty) {
1718 that.save_notebook();
1718 that.save_notebook();
1719 }
1719 }
1720 }, interval);
1720 }, interval);
1721 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1721 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1722 } else {
1722 } else {
1723 this.autosave_timer = null;
1723 this.autosave_timer = null;
1724 $([IPython.events]).trigger("autosave_disabled.Notebook");
1724 $([IPython.events]).trigger("autosave_disabled.Notebook");
1725 }
1725 }
1726 };
1726 };
1727
1727
1728 /**
1728 /**
1729 * Save this notebook on the server. This becomes a notebook instance's
1729 * Save this notebook on the server. This becomes a notebook instance's
1730 * .save_notebook method *after* the entire notebook has been loaded.
1730 * .save_notebook method *after* the entire notebook has been loaded.
1731 *
1731 *
1732 * @method save_notebook
1732 * @method save_notebook
1733 */
1733 */
1734 Notebook.prototype.save_notebook = function (extra_settings) {
1734 Notebook.prototype.save_notebook = function (extra_settings) {
1735 // Create a JSON model to be sent to the server.
1735 // Create a JSON model to be sent to the server.
1736 var model = {};
1736 var model = {};
1737 model.name = this.notebook_name;
1737 model.name = this.notebook_name;
1738 model.path = this.notebook_path;
1738 model.path = this.notebook_path;
1739 model.content = this.toJSON();
1739 model.content = this.toJSON();
1740 model.content.nbformat = this.nbformat;
1740 model.content.nbformat = this.nbformat;
1741 model.content.nbformat_minor = this.nbformat_minor;
1741 model.content.nbformat_minor = this.nbformat_minor;
1742 // time the ajax call for autosave tuning purposes.
1742 // time the ajax call for autosave tuning purposes.
1743 var start = new Date().getTime();
1743 var start = new Date().getTime();
1744 // We do the call with settings so we can set cache to false.
1744 // We do the call with settings so we can set cache to false.
1745 var settings = {
1745 var settings = {
1746 processData : false,
1746 processData : false,
1747 cache : false,
1747 cache : false,
1748 type : "PUT",
1748 type : "PUT",
1749 data : JSON.stringify(model),
1749 data : JSON.stringify(model),
1750 headers : {'Content-Type': 'application/json'},
1750 headers : {'Content-Type': 'application/json'},
1751 success : $.proxy(this.save_notebook_success, this, start),
1751 success : $.proxy(this.save_notebook_success, this, start),
1752 error : $.proxy(this.save_notebook_error, this)
1752 error : $.proxy(this.save_notebook_error, this)
1753 };
1753 };
1754 if (extra_settings) {
1754 if (extra_settings) {
1755 for (var key in extra_settings) {
1755 for (var key in extra_settings) {
1756 settings[key] = extra_settings[key];
1756 settings[key] = extra_settings[key];
1757 }
1757 }
1758 }
1758 }
1759 $([IPython.events]).trigger('notebook_saving.Notebook');
1759 $([IPython.events]).trigger('notebook_saving.Notebook');
1760 var url = utils.url_join_encode(
1760 var url = utils.url_join_encode(
1761 this.base_url,
1761 this.base_url,
1762 'api/notebooks',
1762 'api/notebooks',
1763 this.notebook_path,
1763 this.notebook_path,
1764 this.notebook_name
1764 this.notebook_name
1765 );
1765 );
1766 $.ajax(url, settings);
1766 $.ajax(url, settings);
1767 };
1767 };
1768
1768
1769 /**
1769 /**
1770 * Success callback for saving a notebook.
1770 * Success callback for saving a notebook.
1771 *
1771 *
1772 * @method save_notebook_success
1772 * @method save_notebook_success
1773 * @param {Integer} start the time when the save request started
1773 * @param {Integer} start the time when the save request started
1774 * @param {Object} data JSON representation of a notebook
1774 * @param {Object} data JSON representation of a notebook
1775 * @param {String} status Description of response status
1775 * @param {String} status Description of response status
1776 * @param {jqXHR} xhr jQuery Ajax object
1776 * @param {jqXHR} xhr jQuery Ajax object
1777 */
1777 */
1778 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1778 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1779 this.set_dirty(false);
1779 this.set_dirty(false);
1780 $([IPython.events]).trigger('notebook_saved.Notebook');
1780 $([IPython.events]).trigger('notebook_saved.Notebook');
1781 this._update_autosave_interval(start);
1781 this._update_autosave_interval(start);
1782 if (this._checkpoint_after_save) {
1782 if (this._checkpoint_after_save) {
1783 this.create_checkpoint();
1783 this.create_checkpoint();
1784 this._checkpoint_after_save = false;
1784 this._checkpoint_after_save = false;
1785 }
1785 }
1786 };
1786 };
1787
1787
1788 /**
1788 /**
1789 * update the autosave interval based on how long the last save took
1789 * update the autosave interval based on how long the last save took
1790 *
1790 *
1791 * @method _update_autosave_interval
1791 * @method _update_autosave_interval
1792 * @param {Integer} timestamp when the save request started
1792 * @param {Integer} timestamp when the save request started
1793 */
1793 */
1794 Notebook.prototype._update_autosave_interval = function (start) {
1794 Notebook.prototype._update_autosave_interval = function (start) {
1795 var duration = (new Date().getTime() - start);
1795 var duration = (new Date().getTime() - start);
1796 if (this.autosave_interval) {
1796 if (this.autosave_interval) {
1797 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1797 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1798 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1798 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1799 // round to 10 seconds, otherwise we will be setting a new interval too often
1799 // round to 10 seconds, otherwise we will be setting a new interval too often
1800 interval = 10000 * Math.round(interval / 10000);
1800 interval = 10000 * Math.round(interval / 10000);
1801 // set new interval, if it's changed
1801 // set new interval, if it's changed
1802 if (interval != this.autosave_interval) {
1802 if (interval != this.autosave_interval) {
1803 this.set_autosave_interval(interval);
1803 this.set_autosave_interval(interval);
1804 }
1804 }
1805 }
1805 }
1806 };
1806 };
1807
1807
1808 /**
1808 /**
1809 * Failure callback for saving a notebook.
1809 * Failure callback for saving a notebook.
1810 *
1810 *
1811 * @method save_notebook_error
1811 * @method save_notebook_error
1812 * @param {jqXHR} xhr jQuery Ajax object
1812 * @param {jqXHR} xhr jQuery Ajax object
1813 * @param {String} status Description of response status
1813 * @param {String} status Description of response status
1814 * @param {String} error HTTP error message
1814 * @param {String} error HTTP error message
1815 */
1815 */
1816 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1816 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1817 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1817 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1818 };
1818 };
1819
1819
1820 /**
1820 /**
1821 * Explicitly trust the output of this notebook.
1821 * Explicitly trust the output of this notebook.
1822 *
1822 *
1823 * @method trust_notebook
1823 * @method trust_notebook
1824 */
1824 */
1825 Notebook.prototype.trust_notebook = function (extra_settings) {
1825 Notebook.prototype.trust_notebook = function (extra_settings) {
1826 var body = $("<div>").append($("<p>")
1826 var body = $("<div>").append($("<p>")
1827 .text("A trusted IPython notebook may execute hidden malicious code ")
1827 .text("A trusted IPython notebook may execute hidden malicious code ")
1828 .append($("<strong>")
1828 .append($("<strong>")
1829 .append(
1829 .append(
1830 $("<em>").text("when you open it")
1830 $("<em>").text("when you open it")
1831 )
1831 )
1832 ).append(".").append(
1832 ).append(".").append(
1833 " Selecting trust will immediately reload this notebook in a trusted state."
1833 " Selecting trust will immediately reload this notebook in a trusted state."
1834 ).append(
1834 ).append(
1835 " For more information, see the "
1835 " For more information, see the "
1836 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1836 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1837 .text("IPython security documentation")
1837 .text("IPython security documentation")
1838 ).append(".")
1838 ).append(".")
1839 );
1839 );
1840
1840
1841 var nb = this;
1841 var nb = this;
1842 IPython.dialog.modal({
1842 IPython.dialog.modal({
1843 title: "Trust this notebook?",
1843 title: "Trust this notebook?",
1844 body: body,
1844 body: body,
1845
1845
1846 buttons: {
1846 buttons: {
1847 Cancel : {},
1847 Cancel : {},
1848 Trust : {
1848 Trust : {
1849 class : "btn-danger",
1849 class : "btn-danger",
1850 click : function () {
1850 click : function () {
1851 var cells = nb.get_cells();
1851 var cells = nb.get_cells();
1852 for (var i = 0; i < cells.length; i++) {
1852 for (var i = 0; i < cells.length; i++) {
1853 var cell = cells[i];
1853 var cell = cells[i];
1854 if (cell.cell_type == 'code') {
1854 if (cell.cell_type == 'code') {
1855 cell.output_area.trusted = true;
1855 cell.output_area.trusted = true;
1856 }
1856 }
1857 }
1857 }
1858 $([IPython.events]).on('notebook_saved.Notebook', function () {
1858 $([IPython.events]).on('notebook_saved.Notebook', function () {
1859 window.location.reload();
1859 window.location.reload();
1860 });
1860 });
1861 nb.save_notebook();
1861 nb.save_notebook();
1862 }
1862 }
1863 }
1863 }
1864 }
1864 }
1865 });
1865 });
1866 };
1866 };
1867
1867
1868 Notebook.prototype.new_notebook = function(){
1868 Notebook.prototype.new_notebook = function(){
1869 var path = this.notebook_path;
1869 var path = this.notebook_path;
1870 var base_url = this.base_url;
1870 var base_url = this.base_url;
1871 var settings = {
1871 var settings = {
1872 processData : false,
1872 processData : false,
1873 cache : false,
1873 cache : false,
1874 type : "POST",
1874 type : "POST",
1875 dataType : "json",
1875 dataType : "json",
1876 async : false,
1876 async : false,
1877 success : function (data, status, xhr){
1877 success : function (data, status, xhr){
1878 var notebook_name = data.name;
1878 var notebook_name = data.name;
1879 window.open(
1879 window.open(
1880 utils.url_join_encode(
1880 utils.url_join_encode(
1881 base_url,
1881 base_url,
1882 'notebooks',
1882 'notebooks',
1883 path,
1883 path,
1884 notebook_name
1884 notebook_name
1885 ),
1885 ),
1886 '_blank'
1886 '_blank'
1887 );
1887 );
1888 }
1888 },
1889 error : utils.log_ajax_error,
1889 };
1890 };
1890 var url = utils.url_join_encode(
1891 var url = utils.url_join_encode(
1891 base_url,
1892 base_url,
1892 'api/notebooks',
1893 'api/notebooks',
1893 path
1894 path
1894 );
1895 );
1895 $.ajax(url,settings);
1896 $.ajax(url,settings);
1896 };
1897 };
1897
1898
1898
1899
1899 Notebook.prototype.copy_notebook = function(){
1900 Notebook.prototype.copy_notebook = function(){
1900 var path = this.notebook_path;
1901 var path = this.notebook_path;
1901 var base_url = this.base_url;
1902 var base_url = this.base_url;
1902 var settings = {
1903 var settings = {
1903 processData : false,
1904 processData : false,
1904 cache : false,
1905 cache : false,
1905 type : "POST",
1906 type : "POST",
1906 dataType : "json",
1907 dataType : "json",
1907 data : JSON.stringify({copy_from : this.notebook_name}),
1908 data : JSON.stringify({copy_from : this.notebook_name}),
1908 async : false,
1909 async : false,
1909 success : function (data, status, xhr) {
1910 success : function (data, status, xhr) {
1910 window.open(utils.url_join_encode(
1911 window.open(utils.url_join_encode(
1911 base_url,
1912 base_url,
1912 'notebooks',
1913 'notebooks',
1913 data.path,
1914 data.path,
1914 data.name
1915 data.name
1915 ), '_blank');
1916 ), '_blank');
1916 }
1917 },
1918 error : utils.log_ajax_error,
1917 };
1919 };
1918 var url = utils.url_join_encode(
1920 var url = utils.url_join_encode(
1919 base_url,
1921 base_url,
1920 'api/notebooks',
1922 'api/notebooks',
1921 path
1923 path
1922 );
1924 );
1923 $.ajax(url,settings);
1925 $.ajax(url,settings);
1924 };
1926 };
1925
1927
1926 Notebook.prototype.rename = function (nbname) {
1928 Notebook.prototype.rename = function (nbname) {
1927 var that = this;
1929 var that = this;
1928 if (!nbname.match(/\.ipynb$/)) {
1930 if (!nbname.match(/\.ipynb$/)) {
1929 nbname = nbname + ".ipynb";
1931 nbname = nbname + ".ipynb";
1930 }
1932 }
1931 var data = {name: nbname};
1933 var data = {name: nbname};
1932 var settings = {
1934 var settings = {
1933 processData : false,
1935 processData : false,
1934 cache : false,
1936 cache : false,
1935 type : "PATCH",
1937 type : "PATCH",
1936 data : JSON.stringify(data),
1938 data : JSON.stringify(data),
1937 dataType: "json",
1939 dataType: "json",
1938 headers : {'Content-Type': 'application/json'},
1940 headers : {'Content-Type': 'application/json'},
1939 success : $.proxy(that.rename_success, this),
1941 success : $.proxy(that.rename_success, this),
1940 error : $.proxy(that.rename_error, this)
1942 error : $.proxy(that.rename_error, this)
1941 };
1943 };
1942 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1944 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1943 var url = utils.url_join_encode(
1945 var url = utils.url_join_encode(
1944 this.base_url,
1946 this.base_url,
1945 'api/notebooks',
1947 'api/notebooks',
1946 this.notebook_path,
1948 this.notebook_path,
1947 this.notebook_name
1949 this.notebook_name
1948 );
1950 );
1949 $.ajax(url, settings);
1951 $.ajax(url, settings);
1950 };
1952 };
1951
1953
1952 Notebook.prototype.delete = function () {
1954 Notebook.prototype.delete = function () {
1953 var that = this;
1955 var that = this;
1954 var settings = {
1956 var settings = {
1955 processData : false,
1957 processData : false,
1956 cache : false,
1958 cache : false,
1957 type : "DELETE",
1959 type : "DELETE",
1958 dataType: "json",
1960 dataType: "json",
1961 error : utils.log_ajax_error,
1959 };
1962 };
1960 var url = utils.url_join_encode(
1963 var url = utils.url_join_encode(
1961 this.base_url,
1964 this.base_url,
1962 'api/notebooks',
1965 'api/notebooks',
1963 this.notebook_path,
1966 this.notebook_path,
1964 this.notebook_name
1967 this.notebook_name
1965 );
1968 );
1966 $.ajax(url, settings);
1969 $.ajax(url, settings);
1967 };
1970 };
1968
1971
1969
1972
1970 Notebook.prototype.rename_success = function (json, status, xhr) {
1973 Notebook.prototype.rename_success = function (json, status, xhr) {
1971 var name = this.notebook_name = json.name;
1974 var name = this.notebook_name = json.name;
1972 var path = json.path;
1975 var path = json.path;
1973 this.session.rename_notebook(name, path);
1976 this.session.rename_notebook(name, path);
1974 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1977 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1975 };
1978 };
1976
1979
1977 Notebook.prototype.rename_error = function (xhr, status, error) {
1980 Notebook.prototype.rename_error = function (xhr, status, error) {
1978 var that = this;
1981 var that = this;
1979 var dialog = $('<div/>').append(
1982 var dialog = $('<div/>').append(
1980 $("<p/>").addClass("rename-message")
1983 $("<p/>").addClass("rename-message")
1981 .text('This notebook name already exists.')
1984 .text('This notebook name already exists.')
1982 );
1985 );
1983 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1986 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1984 IPython.dialog.modal({
1987 IPython.dialog.modal({
1985 title: "Notebook Rename Error!",
1988 title: "Notebook Rename Error!",
1986 body: dialog,
1989 body: dialog,
1987 buttons : {
1990 buttons : {
1988 "Cancel": {},
1991 "Cancel": {},
1989 "OK": {
1992 "OK": {
1990 class: "btn-primary",
1993 class: "btn-primary",
1991 click: function () {
1994 click: function () {
1992 IPython.save_widget.rename_notebook();
1995 IPython.save_widget.rename_notebook();
1993 }}
1996 }}
1994 },
1997 },
1995 open : function (event, ui) {
1998 open : function (event, ui) {
1996 var that = $(this);
1999 var that = $(this);
1997 // Upon ENTER, click the OK button.
2000 // Upon ENTER, click the OK button.
1998 that.find('input[type="text"]').keydown(function (event, ui) {
2001 that.find('input[type="text"]').keydown(function (event, ui) {
1999 if (event.which === IPython.keyboard.keycodes.enter) {
2002 if (event.which === IPython.keyboard.keycodes.enter) {
2000 that.find('.btn-primary').first().click();
2003 that.find('.btn-primary').first().click();
2001 }
2004 }
2002 });
2005 });
2003 that.find('input[type="text"]').focus();
2006 that.find('input[type="text"]').focus();
2004 }
2007 }
2005 });
2008 });
2006 };
2009 };
2007
2010
2008 /**
2011 /**
2009 * Request a notebook's data from the server.
2012 * Request a notebook's data from the server.
2010 *
2013 *
2011 * @method load_notebook
2014 * @method load_notebook
2012 * @param {String} notebook_name and path A notebook to load
2015 * @param {String} notebook_name and path A notebook to load
2013 */
2016 */
2014 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2017 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2015 var that = this;
2018 var that = this;
2016 this.notebook_name = notebook_name;
2019 this.notebook_name = notebook_name;
2017 this.notebook_path = notebook_path;
2020 this.notebook_path = notebook_path;
2018 // We do the call with settings so we can set cache to false.
2021 // We do the call with settings so we can set cache to false.
2019 var settings = {
2022 var settings = {
2020 processData : false,
2023 processData : false,
2021 cache : false,
2024 cache : false,
2022 type : "GET",
2025 type : "GET",
2023 dataType : "json",
2026 dataType : "json",
2024 success : $.proxy(this.load_notebook_success,this),
2027 success : $.proxy(this.load_notebook_success,this),
2025 error : $.proxy(this.load_notebook_error,this),
2028 error : $.proxy(this.load_notebook_error,this),
2026 };
2029 };
2027 $([IPython.events]).trigger('notebook_loading.Notebook');
2030 $([IPython.events]).trigger('notebook_loading.Notebook');
2028 var url = utils.url_join_encode(
2031 var url = utils.url_join_encode(
2029 this.base_url,
2032 this.base_url,
2030 'api/notebooks',
2033 'api/notebooks',
2031 this.notebook_path,
2034 this.notebook_path,
2032 this.notebook_name
2035 this.notebook_name
2033 );
2036 );
2034 $.ajax(url, settings);
2037 $.ajax(url, settings);
2035 };
2038 };
2036
2039
2037 /**
2040 /**
2038 * Success callback for loading a notebook from the server.
2041 * Success callback for loading a notebook from the server.
2039 *
2042 *
2040 * Load notebook data from the JSON response.
2043 * Load notebook data from the JSON response.
2041 *
2044 *
2042 * @method load_notebook_success
2045 * @method load_notebook_success
2043 * @param {Object} data JSON representation of a notebook
2046 * @param {Object} data JSON representation of a notebook
2044 * @param {String} status Description of response status
2047 * @param {String} status Description of response status
2045 * @param {jqXHR} xhr jQuery Ajax object
2048 * @param {jqXHR} xhr jQuery Ajax object
2046 */
2049 */
2047 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2050 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2048 this.fromJSON(data);
2051 this.fromJSON(data);
2049 if (this.ncells() === 0) {
2052 if (this.ncells() === 0) {
2050 this.insert_cell_below('code');
2053 this.insert_cell_below('code');
2051 this.edit_mode(0);
2054 this.edit_mode(0);
2052 } else {
2055 } else {
2053 this.select(0);
2056 this.select(0);
2054 this.handle_command_mode(this.get_cell(0));
2057 this.handle_command_mode(this.get_cell(0));
2055 }
2058 }
2056 this.set_dirty(false);
2059 this.set_dirty(false);
2057 this.scroll_to_top();
2060 this.scroll_to_top();
2058 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2061 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2059 var msg = "This notebook has been converted from an older " +
2062 var msg = "This notebook has been converted from an older " +
2060 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2063 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2061 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2064 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2062 "newer notebook format will be used and older versions of IPython " +
2065 "newer notebook format will be used and older versions of IPython " +
2063 "may not be able to read it. To keep the older version, close the " +
2066 "may not be able to read it. To keep the older version, close the " +
2064 "notebook without saving it.";
2067 "notebook without saving it.";
2065 IPython.dialog.modal({
2068 IPython.dialog.modal({
2066 title : "Notebook converted",
2069 title : "Notebook converted",
2067 body : msg,
2070 body : msg,
2068 buttons : {
2071 buttons : {
2069 OK : {
2072 OK : {
2070 class : "btn-primary"
2073 class : "btn-primary"
2071 }
2074 }
2072 }
2075 }
2073 });
2076 });
2074 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2077 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2075 var that = this;
2078 var that = this;
2076 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2079 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2077 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2080 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2078 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2081 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2079 this_vs + ". You can still work with this notebook, but some features " +
2082 this_vs + ". You can still work with this notebook, but some features " +
2080 "introduced in later notebook versions may not be available.";
2083 "introduced in later notebook versions may not be available.";
2081
2084
2082 IPython.dialog.modal({
2085 IPython.dialog.modal({
2083 title : "Newer Notebook",
2086 title : "Newer Notebook",
2084 body : msg,
2087 body : msg,
2085 buttons : {
2088 buttons : {
2086 OK : {
2089 OK : {
2087 class : "btn-danger"
2090 class : "btn-danger"
2088 }
2091 }
2089 }
2092 }
2090 });
2093 });
2091
2094
2092 }
2095 }
2093
2096
2094 // Create the session after the notebook is completely loaded to prevent
2097 // Create the session after the notebook is completely loaded to prevent
2095 // code execution upon loading, which is a security risk.
2098 // code execution upon loading, which is a security risk.
2096 if (this.session === null) {
2099 if (this.session === null) {
2097 this.start_session();
2100 this.start_session();
2098 }
2101 }
2099 // load our checkpoint list
2102 // load our checkpoint list
2100 this.list_checkpoints();
2103 this.list_checkpoints();
2101
2104
2102 // load toolbar state
2105 // load toolbar state
2103 if (this.metadata.celltoolbar) {
2106 if (this.metadata.celltoolbar) {
2104 IPython.CellToolbar.global_show();
2107 IPython.CellToolbar.global_show();
2105 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2108 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2106 }
2109 }
2107
2110
2108 // now that we're fully loaded, it is safe to restore save functionality
2111 // now that we're fully loaded, it is safe to restore save functionality
2109 delete(this.save_notebook);
2112 delete(this.save_notebook);
2110 $([IPython.events]).trigger('notebook_loaded.Notebook');
2113 $([IPython.events]).trigger('notebook_loaded.Notebook');
2111 };
2114 };
2112
2115
2113 /**
2116 /**
2114 * Failure callback for loading a notebook from the server.
2117 * Failure callback for loading a notebook from the server.
2115 *
2118 *
2116 * @method load_notebook_error
2119 * @method load_notebook_error
2117 * @param {jqXHR} xhr jQuery Ajax object
2120 * @param {jqXHR} xhr jQuery Ajax object
2118 * @param {String} status Description of response status
2121 * @param {String} status Description of response status
2119 * @param {String} error HTTP error message
2122 * @param {String} error HTTP error message
2120 */
2123 */
2121 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2124 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2122 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2125 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2123 var msg;
2126 var msg;
2124 if (xhr.status === 400) {
2127 if (xhr.status === 400) {
2125 msg = error;
2128 msg = error;
2126 } else if (xhr.status === 500) {
2129 } else if (xhr.status === 500) {
2127 msg = "An unknown error occurred while loading this notebook. " +
2130 msg = "An unknown error occurred while loading this notebook. " +
2128 "This version can load notebook formats " +
2131 "This version can load notebook formats " +
2129 "v" + this.nbformat + " or earlier.";
2132 "v" + this.nbformat + " or earlier.";
2130 }
2133 }
2131 IPython.dialog.modal({
2134 IPython.dialog.modal({
2132 title: "Error loading notebook",
2135 title: "Error loading notebook",
2133 body : msg,
2136 body : msg,
2134 buttons : {
2137 buttons : {
2135 "OK": {}
2138 "OK": {}
2136 }
2139 }
2137 });
2140 });
2138 };
2141 };
2139
2142
2140 /********************* checkpoint-related *********************/
2143 /********************* checkpoint-related *********************/
2141
2144
2142 /**
2145 /**
2143 * Save the notebook then immediately create a checkpoint.
2146 * Save the notebook then immediately create a checkpoint.
2144 *
2147 *
2145 * @method save_checkpoint
2148 * @method save_checkpoint
2146 */
2149 */
2147 Notebook.prototype.save_checkpoint = function () {
2150 Notebook.prototype.save_checkpoint = function () {
2148 this._checkpoint_after_save = true;
2151 this._checkpoint_after_save = true;
2149 this.save_notebook();
2152 this.save_notebook();
2150 };
2153 };
2151
2154
2152 /**
2155 /**
2153 * Add a checkpoint for this notebook.
2156 * Add a checkpoint for this notebook.
2154 * for use as a callback from checkpoint creation.
2157 * for use as a callback from checkpoint creation.
2155 *
2158 *
2156 * @method add_checkpoint
2159 * @method add_checkpoint
2157 */
2160 */
2158 Notebook.prototype.add_checkpoint = function (checkpoint) {
2161 Notebook.prototype.add_checkpoint = function (checkpoint) {
2159 var found = false;
2162 var found = false;
2160 for (var i = 0; i < this.checkpoints.length; i++) {
2163 for (var i = 0; i < this.checkpoints.length; i++) {
2161 var existing = this.checkpoints[i];
2164 var existing = this.checkpoints[i];
2162 if (existing.id == checkpoint.id) {
2165 if (existing.id == checkpoint.id) {
2163 found = true;
2166 found = true;
2164 this.checkpoints[i] = checkpoint;
2167 this.checkpoints[i] = checkpoint;
2165 break;
2168 break;
2166 }
2169 }
2167 }
2170 }
2168 if (!found) {
2171 if (!found) {
2169 this.checkpoints.push(checkpoint);
2172 this.checkpoints.push(checkpoint);
2170 }
2173 }
2171 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2174 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2172 };
2175 };
2173
2176
2174 /**
2177 /**
2175 * List checkpoints for this notebook.
2178 * List checkpoints for this notebook.
2176 *
2179 *
2177 * @method list_checkpoints
2180 * @method list_checkpoints
2178 */
2181 */
2179 Notebook.prototype.list_checkpoints = function () {
2182 Notebook.prototype.list_checkpoints = function () {
2180 var url = utils.url_join_encode(
2183 var url = utils.url_join_encode(
2181 this.base_url,
2184 this.base_url,
2182 'api/notebooks',
2185 'api/notebooks',
2183 this.notebook_path,
2186 this.notebook_path,
2184 this.notebook_name,
2187 this.notebook_name,
2185 'checkpoints'
2188 'checkpoints'
2186 );
2189 );
2187 $.get(url).done(
2190 $.get(url).done(
2188 $.proxy(this.list_checkpoints_success, this)
2191 $.proxy(this.list_checkpoints_success, this)
2189 ).fail(
2192 ).fail(
2190 $.proxy(this.list_checkpoints_error, this)
2193 $.proxy(this.list_checkpoints_error, this)
2191 );
2194 );
2192 };
2195 };
2193
2196
2194 /**
2197 /**
2195 * Success callback for listing checkpoints.
2198 * Success callback for listing checkpoints.
2196 *
2199 *
2197 * @method list_checkpoint_success
2200 * @method list_checkpoint_success
2198 * @param {Object} data JSON representation of a checkpoint
2201 * @param {Object} data JSON representation of a checkpoint
2199 * @param {String} status Description of response status
2202 * @param {String} status Description of response status
2200 * @param {jqXHR} xhr jQuery Ajax object
2203 * @param {jqXHR} xhr jQuery Ajax object
2201 */
2204 */
2202 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2205 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2203 data = $.parseJSON(data);
2206 data = $.parseJSON(data);
2204 this.checkpoints = data;
2207 this.checkpoints = data;
2205 if (data.length) {
2208 if (data.length) {
2206 this.last_checkpoint = data[data.length - 1];
2209 this.last_checkpoint = data[data.length - 1];
2207 } else {
2210 } else {
2208 this.last_checkpoint = null;
2211 this.last_checkpoint = null;
2209 }
2212 }
2210 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2213 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2211 };
2214 };
2212
2215
2213 /**
2216 /**
2214 * Failure callback for listing a checkpoint.
2217 * Failure callback for listing a checkpoint.
2215 *
2218 *
2216 * @method list_checkpoint_error
2219 * @method list_checkpoint_error
2217 * @param {jqXHR} xhr jQuery Ajax object
2220 * @param {jqXHR} xhr jQuery Ajax object
2218 * @param {String} status Description of response status
2221 * @param {String} status Description of response status
2219 * @param {String} error_msg HTTP error message
2222 * @param {String} error_msg HTTP error message
2220 */
2223 */
2221 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2224 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2222 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2225 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2223 };
2226 };
2224
2227
2225 /**
2228 /**
2226 * Create a checkpoint of this notebook on the server from the most recent save.
2229 * Create a checkpoint of this notebook on the server from the most recent save.
2227 *
2230 *
2228 * @method create_checkpoint
2231 * @method create_checkpoint
2229 */
2232 */
2230 Notebook.prototype.create_checkpoint = function () {
2233 Notebook.prototype.create_checkpoint = function () {
2231 var url = utils.url_join_encode(
2234 var url = utils.url_join_encode(
2232 this.base_url,
2235 this.base_url,
2233 'api/notebooks',
2236 'api/notebooks',
2234 this.notebook_path,
2237 this.notebook_path,
2235 this.notebook_name,
2238 this.notebook_name,
2236 'checkpoints'
2239 'checkpoints'
2237 );
2240 );
2238 $.post(url).done(
2241 $.post(url).done(
2239 $.proxy(this.create_checkpoint_success, this)
2242 $.proxy(this.create_checkpoint_success, this)
2240 ).fail(
2243 ).fail(
2241 $.proxy(this.create_checkpoint_error, this)
2244 $.proxy(this.create_checkpoint_error, this)
2242 );
2245 );
2243 };
2246 };
2244
2247
2245 /**
2248 /**
2246 * Success callback for creating a checkpoint.
2249 * Success callback for creating a checkpoint.
2247 *
2250 *
2248 * @method create_checkpoint_success
2251 * @method create_checkpoint_success
2249 * @param {Object} data JSON representation of a checkpoint
2252 * @param {Object} data JSON representation of a checkpoint
2250 * @param {String} status Description of response status
2253 * @param {String} status Description of response status
2251 * @param {jqXHR} xhr jQuery Ajax object
2254 * @param {jqXHR} xhr jQuery Ajax object
2252 */
2255 */
2253 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2256 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2254 data = $.parseJSON(data);
2257 data = $.parseJSON(data);
2255 this.add_checkpoint(data);
2258 this.add_checkpoint(data);
2256 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2259 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2257 };
2260 };
2258
2261
2259 /**
2262 /**
2260 * Failure callback for creating a checkpoint.
2263 * Failure callback for creating a checkpoint.
2261 *
2264 *
2262 * @method create_checkpoint_error
2265 * @method create_checkpoint_error
2263 * @param {jqXHR} xhr jQuery Ajax object
2266 * @param {jqXHR} xhr jQuery Ajax object
2264 * @param {String} status Description of response status
2267 * @param {String} status Description of response status
2265 * @param {String} error_msg HTTP error message
2268 * @param {String} error_msg HTTP error message
2266 */
2269 */
2267 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2270 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2268 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2271 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2269 };
2272 };
2270
2273
2271 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2274 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2272 var that = this;
2275 var that = this;
2273 checkpoint = checkpoint || this.last_checkpoint;
2276 checkpoint = checkpoint || this.last_checkpoint;
2274 if ( ! checkpoint ) {
2277 if ( ! checkpoint ) {
2275 console.log("restore dialog, but no checkpoint to restore to!");
2278 console.log("restore dialog, but no checkpoint to restore to!");
2276 return;
2279 return;
2277 }
2280 }
2278 var body = $('<div/>').append(
2281 var body = $('<div/>').append(
2279 $('<p/>').addClass("p-space").text(
2282 $('<p/>').addClass("p-space").text(
2280 "Are you sure you want to revert the notebook to " +
2283 "Are you sure you want to revert the notebook to " +
2281 "the latest checkpoint?"
2284 "the latest checkpoint?"
2282 ).append(
2285 ).append(
2283 $("<strong/>").text(
2286 $("<strong/>").text(
2284 " This cannot be undone."
2287 " This cannot be undone."
2285 )
2288 )
2286 )
2289 )
2287 ).append(
2290 ).append(
2288 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2291 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2289 ).append(
2292 ).append(
2290 $('<p/>').addClass("p-space").text(
2293 $('<p/>').addClass("p-space").text(
2291 Date(checkpoint.last_modified)
2294 Date(checkpoint.last_modified)
2292 ).css("text-align", "center")
2295 ).css("text-align", "center")
2293 );
2296 );
2294
2297
2295 IPython.dialog.modal({
2298 IPython.dialog.modal({
2296 title : "Revert notebook to checkpoint",
2299 title : "Revert notebook to checkpoint",
2297 body : body,
2300 body : body,
2298 buttons : {
2301 buttons : {
2299 Revert : {
2302 Revert : {
2300 class : "btn-danger",
2303 class : "btn-danger",
2301 click : function () {
2304 click : function () {
2302 that.restore_checkpoint(checkpoint.id);
2305 that.restore_checkpoint(checkpoint.id);
2303 }
2306 }
2304 },
2307 },
2305 Cancel : {}
2308 Cancel : {}
2306 }
2309 }
2307 });
2310 });
2308 };
2311 };
2309
2312
2310 /**
2313 /**
2311 * Restore the notebook to a checkpoint state.
2314 * Restore the notebook to a checkpoint state.
2312 *
2315 *
2313 * @method restore_checkpoint
2316 * @method restore_checkpoint
2314 * @param {String} checkpoint ID
2317 * @param {String} checkpoint ID
2315 */
2318 */
2316 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2319 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2317 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2320 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2318 var url = utils.url_join_encode(
2321 var url = utils.url_join_encode(
2319 this.base_url,
2322 this.base_url,
2320 'api/notebooks',
2323 'api/notebooks',
2321 this.notebook_path,
2324 this.notebook_path,
2322 this.notebook_name,
2325 this.notebook_name,
2323 'checkpoints',
2326 'checkpoints',
2324 checkpoint
2327 checkpoint
2325 );
2328 );
2326 $.post(url).done(
2329 $.post(url).done(
2327 $.proxy(this.restore_checkpoint_success, this)
2330 $.proxy(this.restore_checkpoint_success, this)
2328 ).fail(
2331 ).fail(
2329 $.proxy(this.restore_checkpoint_error, this)
2332 $.proxy(this.restore_checkpoint_error, this)
2330 );
2333 );
2331 };
2334 };
2332
2335
2333 /**
2336 /**
2334 * Success callback for restoring a notebook to a checkpoint.
2337 * Success callback for restoring a notebook to a checkpoint.
2335 *
2338 *
2336 * @method restore_checkpoint_success
2339 * @method restore_checkpoint_success
2337 * @param {Object} data (ignored, should be empty)
2340 * @param {Object} data (ignored, should be empty)
2338 * @param {String} status Description of response status
2341 * @param {String} status Description of response status
2339 * @param {jqXHR} xhr jQuery Ajax object
2342 * @param {jqXHR} xhr jQuery Ajax object
2340 */
2343 */
2341 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2344 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2342 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2345 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2343 this.load_notebook(this.notebook_name, this.notebook_path);
2346 this.load_notebook(this.notebook_name, this.notebook_path);
2344 };
2347 };
2345
2348
2346 /**
2349 /**
2347 * Failure callback for restoring a notebook to a checkpoint.
2350 * Failure callback for restoring a notebook to a checkpoint.
2348 *
2351 *
2349 * @method restore_checkpoint_error
2352 * @method restore_checkpoint_error
2350 * @param {jqXHR} xhr jQuery Ajax object
2353 * @param {jqXHR} xhr jQuery Ajax object
2351 * @param {String} status Description of response status
2354 * @param {String} status Description of response status
2352 * @param {String} error_msg HTTP error message
2355 * @param {String} error_msg HTTP error message
2353 */
2356 */
2354 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2357 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2355 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2358 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2356 };
2359 };
2357
2360
2358 /**
2361 /**
2359 * Delete a notebook checkpoint.
2362 * Delete a notebook checkpoint.
2360 *
2363 *
2361 * @method delete_checkpoint
2364 * @method delete_checkpoint
2362 * @param {String} checkpoint ID
2365 * @param {String} checkpoint ID
2363 */
2366 */
2364 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2367 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2365 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2368 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2366 var url = utils.url_join_encode(
2369 var url = utils.url_join_encode(
2367 this.base_url,
2370 this.base_url,
2368 'api/notebooks',
2371 'api/notebooks',
2369 this.notebook_path,
2372 this.notebook_path,
2370 this.notebook_name,
2373 this.notebook_name,
2371 'checkpoints',
2374 'checkpoints',
2372 checkpoint
2375 checkpoint
2373 );
2376 );
2374 $.ajax(url, {
2377 $.ajax(url, {
2375 type: 'DELETE',
2378 type: 'DELETE',
2376 success: $.proxy(this.delete_checkpoint_success, this),
2379 success: $.proxy(this.delete_checkpoint_success, this),
2377 error: $.proxy(this.delete_notebook_error,this)
2380 error: $.proxy(this.delete_checkpoint_error, this)
2378 });
2381 });
2379 };
2382 };
2380
2383
2381 /**
2384 /**
2382 * Success callback for deleting a notebook checkpoint
2385 * Success callback for deleting a notebook checkpoint
2383 *
2386 *
2384 * @method delete_checkpoint_success
2387 * @method delete_checkpoint_success
2385 * @param {Object} data (ignored, should be empty)
2388 * @param {Object} data (ignored, should be empty)
2386 * @param {String} status Description of response status
2389 * @param {String} status Description of response status
2387 * @param {jqXHR} xhr jQuery Ajax object
2390 * @param {jqXHR} xhr jQuery Ajax object
2388 */
2391 */
2389 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2392 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2390 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2393 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2391 this.load_notebook(this.notebook_name, this.notebook_path);
2394 this.load_notebook(this.notebook_name, this.notebook_path);
2392 };
2395 };
2393
2396
2394 /**
2397 /**
2395 * Failure callback for deleting a notebook checkpoint.
2398 * Failure callback for deleting a notebook checkpoint.
2396 *
2399 *
2397 * @method delete_checkpoint_error
2400 * @method delete_checkpoint_error
2398 * @param {jqXHR} xhr jQuery Ajax object
2401 * @param {jqXHR} xhr jQuery Ajax object
2399 * @param {String} status Description of response status
2402 * @param {String} status Description of response status
2400 * @param {String} error_msg HTTP error message
2403 * @param {String} error_msg HTTP error message
2401 */
2404 */
2402 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2405 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2403 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2406 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2404 };
2407 };
2405
2408
2406
2409
2407 IPython.Notebook = Notebook;
2410 IPython.Notebook = Notebook;
2408
2411
2409
2412
2410 return IPython;
2413 return IPython;
2411
2414
2412 }(IPython));
2415 }(IPython));
2413
2416
@@ -1,603 +1,604 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 "use strict";
19 "use strict";
20
20
21 var utils = IPython.utils;
21 var utils = IPython.utils;
22
22
23 // Initialization and connection.
23 // Initialization and connection.
24 /**
24 /**
25 * A Kernel Class to communicate with the Python kernel
25 * A Kernel Class to communicate with the Python kernel
26 * @Class Kernel
26 * @Class Kernel
27 */
27 */
28 var Kernel = function (kernel_service_url) {
28 var Kernel = function (kernel_service_url) {
29 this.kernel_id = null;
29 this.kernel_id = null;
30 this.shell_channel = null;
30 this.shell_channel = null;
31 this.iopub_channel = null;
31 this.iopub_channel = null;
32 this.stdin_channel = null;
32 this.stdin_channel = null;
33 this.kernel_service_url = kernel_service_url;
33 this.kernel_service_url = kernel_service_url;
34 this.running = false;
34 this.running = false;
35 this.username = "username";
35 this.username = "username";
36 this.session_id = utils.uuid();
36 this.session_id = utils.uuid();
37 this._msg_callbacks = {};
37 this._msg_callbacks = {};
38 this.post = $.post;
38 this.post = $.post;
39
39
40 if (typeof(WebSocket) !== 'undefined') {
40 if (typeof(WebSocket) !== 'undefined') {
41 this.WebSocket = WebSocket;
41 this.WebSocket = WebSocket;
42 } else if (typeof(MozWebSocket) !== 'undefined') {
42 } else if (typeof(MozWebSocket) !== 'undefined') {
43 this.WebSocket = MozWebSocket;
43 this.WebSocket = MozWebSocket;
44 } else {
44 } else {
45 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.');
45 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.');
46 }
46 }
47
47
48 this.bind_events();
48 this.bind_events();
49 this.init_iopub_handlers();
49 this.init_iopub_handlers();
50 this.comm_manager = new IPython.CommManager(this);
50 this.comm_manager = new IPython.CommManager(this);
51 this.widget_manager = new IPython.WidgetManager(this.comm_manager);
51 this.widget_manager = new IPython.WidgetManager(this.comm_manager);
52 };
52 };
53
53
54
54
55 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
55 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
56 var msg = {
56 var msg = {
57 header : {
57 header : {
58 msg_id : utils.uuid(),
58 msg_id : utils.uuid(),
59 username : this.username,
59 username : this.username,
60 session : this.session_id,
60 session : this.session_id,
61 msg_type : msg_type
61 msg_type : msg_type
62 },
62 },
63 metadata : metadata || {},
63 metadata : metadata || {},
64 content : content,
64 content : content,
65 parent_header : {}
65 parent_header : {}
66 };
66 };
67 return msg;
67 return msg;
68 };
68 };
69
69
70 Kernel.prototype.bind_events = function () {
70 Kernel.prototype.bind_events = function () {
71 var that = this;
71 var that = this;
72 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
72 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
73 that.send_input_reply(data);
73 that.send_input_reply(data);
74 });
74 });
75 };
75 };
76
76
77 // Initialize the iopub handlers
77 // Initialize the iopub handlers
78
78
79 Kernel.prototype.init_iopub_handlers = function () {
79 Kernel.prototype.init_iopub_handlers = function () {
80 var output_types = ['stream', 'display_data', 'pyout', 'pyerr'];
80 var output_types = ['stream', 'display_data', 'pyout', 'pyerr'];
81 this._iopub_handlers = {};
81 this._iopub_handlers = {};
82 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
82 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
83 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
83 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
84
84
85 for (var i=0; i < output_types.length; i++) {
85 for (var i=0; i < output_types.length; i++) {
86 this.register_iopub_handler(output_types[i], $.proxy(this._handle_output_message, this));
86 this.register_iopub_handler(output_types[i], $.proxy(this._handle_output_message, this));
87 }
87 }
88 };
88 };
89
89
90 /**
90 /**
91 * Start the Python kernel
91 * Start the Python kernel
92 * @method start
92 * @method start
93 */
93 */
94 Kernel.prototype.start = function (params) {
94 Kernel.prototype.start = function (params) {
95 params = params || {};
95 params = params || {};
96 if (!this.running) {
96 if (!this.running) {
97 var qs = $.param(params);
97 var qs = $.param(params);
98 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
98 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
99 $.proxy(this._kernel_started, this),
99 $.proxy(this._kernel_started, this),
100 'json'
100 'json'
101 );
101 );
102 }
102 }
103 };
103 };
104
104
105 /**
105 /**
106 * Restart the python kernel.
106 * Restart the python kernel.
107 *
107 *
108 * Emit a 'status_restarting.Kernel' event with
108 * Emit a 'status_restarting.Kernel' event with
109 * the current object as parameter
109 * the current object as parameter
110 *
110 *
111 * @method restart
111 * @method restart
112 */
112 */
113 Kernel.prototype.restart = function () {
113 Kernel.prototype.restart = function () {
114 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
114 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
115 if (this.running) {
115 if (this.running) {
116 this.stop_channels();
116 this.stop_channels();
117 this.post(utils.url_join_encode(this.kernel_url, "restart"),
117 this.post(utils.url_join_encode(this.kernel_url, "restart"),
118 $.proxy(this._kernel_started, this),
118 $.proxy(this._kernel_started, this),
119 'json'
119 'json'
120 );
120 );
121 }
121 }
122 };
122 };
123
123
124
124
125 Kernel.prototype._kernel_started = function (json) {
125 Kernel.prototype._kernel_started = function (json) {
126 console.log("Kernel started: ", json.id);
126 console.log("Kernel started: ", json.id);
127 this.running = true;
127 this.running = true;
128 this.kernel_id = json.id;
128 this.kernel_id = json.id;
129 // trailing 's' in https will become wss for secure web sockets
129 // trailing 's' in https will become wss for secure web sockets
130 this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
130 this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
131 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
131 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
132 this.start_channels();
132 this.start_channels();
133 };
133 };
134
134
135
135
136 Kernel.prototype._websocket_closed = function(ws_url, early) {
136 Kernel.prototype._websocket_closed = function(ws_url, early) {
137 this.stop_channels();
137 this.stop_channels();
138 $([IPython.events]).trigger('websocket_closed.Kernel',
138 $([IPython.events]).trigger('websocket_closed.Kernel',
139 {ws_url: ws_url, kernel: this, early: early}
139 {ws_url: ws_url, kernel: this, early: early}
140 );
140 );
141 };
141 };
142
142
143 /**
143 /**
144 * Start the `shell`and `iopub` channels.
144 * Start the `shell`and `iopub` channels.
145 * Will stop and restart them if they already exist.
145 * Will stop and restart them if they already exist.
146 *
146 *
147 * @method start_channels
147 * @method start_channels
148 */
148 */
149 Kernel.prototype.start_channels = function () {
149 Kernel.prototype.start_channels = function () {
150 var that = this;
150 var that = this;
151 this.stop_channels();
151 this.stop_channels();
152 var ws_host_url = this.ws_host + this.kernel_url;
152 var ws_host_url = this.ws_host + this.kernel_url;
153 console.log("Starting WebSockets:", ws_host_url);
153 console.log("Starting WebSockets:", ws_host_url);
154 this.shell_channel = new this.WebSocket(
154 this.shell_channel = new this.WebSocket(
155 this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
155 this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
156 );
156 );
157 this.stdin_channel = new this.WebSocket(
157 this.stdin_channel = new this.WebSocket(
158 this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
158 this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
159 );
159 );
160 this.iopub_channel = new this.WebSocket(
160 this.iopub_channel = new this.WebSocket(
161 this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
161 this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
162 );
162 );
163
163
164 var already_called_onclose = false; // only alert once
164 var already_called_onclose = false; // only alert once
165 var ws_closed_early = function(evt){
165 var ws_closed_early = function(evt){
166 if (already_called_onclose){
166 if (already_called_onclose){
167 return;
167 return;
168 }
168 }
169 already_called_onclose = true;
169 already_called_onclose = true;
170 if ( ! evt.wasClean ){
170 if ( ! evt.wasClean ){
171 that._websocket_closed(ws_host_url, true);
171 that._websocket_closed(ws_host_url, true);
172 }
172 }
173 };
173 };
174 var ws_closed_late = function(evt){
174 var ws_closed_late = function(evt){
175 if (already_called_onclose){
175 if (already_called_onclose){
176 return;
176 return;
177 }
177 }
178 already_called_onclose = true;
178 already_called_onclose = true;
179 if ( ! evt.wasClean ){
179 if ( ! evt.wasClean ){
180 that._websocket_closed(ws_host_url, false);
180 that._websocket_closed(ws_host_url, false);
181 }
181 }
182 };
182 };
183 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
183 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
184 for (var i=0; i < channels.length; i++) {
184 for (var i=0; i < channels.length; i++) {
185 channels[i].onopen = $.proxy(this._ws_opened, this);
185 channels[i].onopen = $.proxy(this._ws_opened, this);
186 channels[i].onclose = ws_closed_early;
186 channels[i].onclose = ws_closed_early;
187 }
187 }
188 // switch from early-close to late-close message after 1s
188 // switch from early-close to late-close message after 1s
189 setTimeout(function() {
189 setTimeout(function() {
190 for (var i=0; i < channels.length; i++) {
190 for (var i=0; i < channels.length; i++) {
191 if (channels[i] !== null) {
191 if (channels[i] !== null) {
192 channels[i].onclose = ws_closed_late;
192 channels[i].onclose = ws_closed_late;
193 }
193 }
194 }
194 }
195 }, 1000);
195 }, 1000);
196 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
196 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
197 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
197 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
198 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
198 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
199 };
199 };
200
200
201 /**
201 /**
202 * Handle a websocket entering the open state
202 * Handle a websocket entering the open state
203 * sends session and cookie authentication info as first message.
203 * sends session and cookie authentication info as first message.
204 * Once all sockets are open, signal the Kernel.status_started event.
204 * Once all sockets are open, signal the Kernel.status_started event.
205 * @method _ws_opened
205 * @method _ws_opened
206 */
206 */
207 Kernel.prototype._ws_opened = function (evt) {
207 Kernel.prototype._ws_opened = function (evt) {
208 // send the session id so the Session object Python-side
208 // send the session id so the Session object Python-side
209 // has the same identity
209 // has the same identity
210 evt.target.send(this.session_id + ':' + document.cookie);
210 evt.target.send(this.session_id + ':' + document.cookie);
211
211
212 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
212 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
213 for (var i=0; i < channels.length; i++) {
213 for (var i=0; i < channels.length; i++) {
214 // if any channel is not ready, don't trigger event.
214 // if any channel is not ready, don't trigger event.
215 if ( !channels[i].readyState ) return;
215 if ( !channels[i].readyState ) return;
216 }
216 }
217 // all events ready, trigger started event.
217 // all events ready, trigger started event.
218 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
218 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
219 };
219 };
220
220
221 /**
221 /**
222 * Stop the websocket channels.
222 * Stop the websocket channels.
223 * @method stop_channels
223 * @method stop_channels
224 */
224 */
225 Kernel.prototype.stop_channels = function () {
225 Kernel.prototype.stop_channels = function () {
226 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
226 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
227 for (var i=0; i < channels.length; i++) {
227 for (var i=0; i < channels.length; i++) {
228 if ( channels[i] !== null ) {
228 if ( channels[i] !== null ) {
229 channels[i].onclose = null;
229 channels[i].onclose = null;
230 channels[i].close();
230 channels[i].close();
231 }
231 }
232 }
232 }
233 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
233 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
234 };
234 };
235
235
236 // Main public methods.
236 // Main public methods.
237
237
238 // send a message on the Kernel's shell channel
238 // send a message on the Kernel's shell channel
239 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
239 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
240 var msg = this._get_msg(msg_type, content, metadata);
240 var msg = this._get_msg(msg_type, content, metadata);
241 this.shell_channel.send(JSON.stringify(msg));
241 this.shell_channel.send(JSON.stringify(msg));
242 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
242 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
243 return msg.header.msg_id;
243 return msg.header.msg_id;
244 };
244 };
245
245
246 /**
246 /**
247 * Get kernel info
247 * Get kernel info
248 *
248 *
249 * @param callback {function}
249 * @param callback {function}
250 * @method object_info
250 * @method object_info
251 *
251 *
252 * When calling this method, pass a callback function that expects one argument.
252 * When calling this method, pass a callback function that expects one argument.
253 * The callback will be passed the complete `kernel_info_reply` message documented
253 * The callback will be passed the complete `kernel_info_reply` message documented
254 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
254 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
255 */
255 */
256 Kernel.prototype.kernel_info = function (callback) {
256 Kernel.prototype.kernel_info = function (callback) {
257 var callbacks;
257 var callbacks;
258 if (callback) {
258 if (callback) {
259 callbacks = { shell : { reply : callback } };
259 callbacks = { shell : { reply : callback } };
260 }
260 }
261 return this.send_shell_message("kernel_info_request", {}, callbacks);
261 return this.send_shell_message("kernel_info_request", {}, callbacks);
262 };
262 };
263
263
264 /**
264 /**
265 * Get info on an object
265 * Get info on an object
266 *
266 *
267 * @param objname {string}
267 * @param objname {string}
268 * @param callback {function}
268 * @param callback {function}
269 * @method object_info
269 * @method object_info
270 *
270 *
271 * When calling this method, pass a callback function that expects one argument.
271 * When calling this method, pass a callback function that expects one argument.
272 * The callback will be passed the complete `object_info_reply` message documented
272 * The callback will be passed the complete `object_info_reply` message documented
273 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
273 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
274 */
274 */
275 Kernel.prototype.object_info = function (objname, callback) {
275 Kernel.prototype.object_info = function (objname, callback) {
276 var callbacks;
276 var callbacks;
277 if (callback) {
277 if (callback) {
278 callbacks = { shell : { reply : callback } };
278 callbacks = { shell : { reply : callback } };
279 }
279 }
280
280
281 if (typeof(objname) !== null && objname !== null) {
281 if (typeof(objname) !== null && objname !== null) {
282 var content = {
282 var content = {
283 oname : objname.toString(),
283 oname : objname.toString(),
284 detail_level : 0,
284 detail_level : 0,
285 };
285 };
286 return this.send_shell_message("object_info_request", content, callbacks);
286 return this.send_shell_message("object_info_request", content, callbacks);
287 }
287 }
288 return;
288 return;
289 };
289 };
290
290
291 /**
291 /**
292 * Execute given code into kernel, and pass result to callback.
292 * Execute given code into kernel, and pass result to callback.
293 *
293 *
294 * @async
294 * @async
295 * @method execute
295 * @method execute
296 * @param {string} code
296 * @param {string} code
297 * @param [callbacks] {Object} With the following keys (all optional)
297 * @param [callbacks] {Object} With the following keys (all optional)
298 * @param callbacks.shell.reply {function}
298 * @param callbacks.shell.reply {function}
299 * @param callbacks.shell.payload.[payload_name] {function}
299 * @param callbacks.shell.payload.[payload_name] {function}
300 * @param callbacks.iopub.output {function}
300 * @param callbacks.iopub.output {function}
301 * @param callbacks.iopub.clear_output {function}
301 * @param callbacks.iopub.clear_output {function}
302 * @param callbacks.input {function}
302 * @param callbacks.input {function}
303 * @param {object} [options]
303 * @param {object} [options]
304 * @param [options.silent=false] {Boolean}
304 * @param [options.silent=false] {Boolean}
305 * @param [options.user_expressions=empty_dict] {Dict}
305 * @param [options.user_expressions=empty_dict] {Dict}
306 * @param [options.user_variables=empty_list] {List od Strings}
306 * @param [options.user_variables=empty_list] {List od Strings}
307 * @param [options.allow_stdin=false] {Boolean} true|false
307 * @param [options.allow_stdin=false] {Boolean} true|false
308 *
308 *
309 * @example
309 * @example
310 *
310 *
311 * The options object should contain the options for the execute call. Its default
311 * The options object should contain the options for the execute call. Its default
312 * values are:
312 * values are:
313 *
313 *
314 * options = {
314 * options = {
315 * silent : true,
315 * silent : true,
316 * user_variables : [],
316 * user_variables : [],
317 * user_expressions : {},
317 * user_expressions : {},
318 * allow_stdin : false
318 * allow_stdin : false
319 * }
319 * }
320 *
320 *
321 * When calling this method pass a callbacks structure of the form:
321 * When calling this method pass a callbacks structure of the form:
322 *
322 *
323 * callbacks = {
323 * callbacks = {
324 * shell : {
324 * shell : {
325 * reply : execute_reply_callback,
325 * reply : execute_reply_callback,
326 * payload : {
326 * payload : {
327 * set_next_input : set_next_input_callback,
327 * set_next_input : set_next_input_callback,
328 * }
328 * }
329 * },
329 * },
330 * iopub : {
330 * iopub : {
331 * output : output_callback,
331 * output : output_callback,
332 * clear_output : clear_output_callback,
332 * clear_output : clear_output_callback,
333 * },
333 * },
334 * input : raw_input_callback
334 * input : raw_input_callback
335 * }
335 * }
336 *
336 *
337 * Each callback will be passed the entire message as a single arugment.
337 * Each callback will be passed the entire message as a single arugment.
338 * Payload handlers will be passed the corresponding payload and the execute_reply message.
338 * Payload handlers will be passed the corresponding payload and the execute_reply message.
339 */
339 */
340 Kernel.prototype.execute = function (code, callbacks, options) {
340 Kernel.prototype.execute = function (code, callbacks, options) {
341
341
342 var content = {
342 var content = {
343 code : code,
343 code : code,
344 silent : true,
344 silent : true,
345 store_history : false,
345 store_history : false,
346 user_variables : [],
346 user_variables : [],
347 user_expressions : {},
347 user_expressions : {},
348 allow_stdin : false
348 allow_stdin : false
349 };
349 };
350 callbacks = callbacks || {};
350 callbacks = callbacks || {};
351 if (callbacks.input !== undefined) {
351 if (callbacks.input !== undefined) {
352 content.allow_stdin = true;
352 content.allow_stdin = true;
353 }
353 }
354 $.extend(true, content, options);
354 $.extend(true, content, options);
355 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
355 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
356 return this.send_shell_message("execute_request", content, callbacks);
356 return this.send_shell_message("execute_request", content, callbacks);
357 };
357 };
358
358
359 /**
359 /**
360 * When calling this method, pass a function to be called with the `complete_reply` message
360 * When calling this method, pass a function to be called with the `complete_reply` message
361 * as its only argument when it arrives.
361 * as its only argument when it arrives.
362 *
362 *
363 * `complete_reply` is documented
363 * `complete_reply` is documented
364 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
364 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
365 *
365 *
366 * @method complete
366 * @method complete
367 * @param line {integer}
367 * @param line {integer}
368 * @param cursor_pos {integer}
368 * @param cursor_pos {integer}
369 * @param callback {function}
369 * @param callback {function}
370 *
370 *
371 */
371 */
372 Kernel.prototype.complete = function (line, cursor_pos, callback) {
372 Kernel.prototype.complete = function (line, cursor_pos, callback) {
373 var callbacks;
373 var callbacks;
374 if (callback) {
374 if (callback) {
375 callbacks = { shell : { reply : callback } };
375 callbacks = { shell : { reply : callback } };
376 }
376 }
377 var content = {
377 var content = {
378 text : '',
378 text : '',
379 line : line,
379 line : line,
380 block : null,
380 block : null,
381 cursor_pos : cursor_pos
381 cursor_pos : cursor_pos
382 };
382 };
383 return this.send_shell_message("complete_request", content, callbacks);
383 return this.send_shell_message("complete_request", content, callbacks);
384 };
384 };
385
385
386
386
387 Kernel.prototype.interrupt = function () {
387 Kernel.prototype.interrupt = function () {
388 if (this.running) {
388 if (this.running) {
389 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
389 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
390 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
390 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
391 }
391 }
392 };
392 };
393
393
394
394
395 Kernel.prototype.kill = function () {
395 Kernel.prototype.kill = function () {
396 if (this.running) {
396 if (this.running) {
397 this.running = false;
397 this.running = false;
398 var settings = {
398 var settings = {
399 cache : false,
399 cache : false,
400 type : "DELETE"
400 type : "DELETE",
401 error : utils.log_ajax_error,
401 };
402 };
402 $.ajax(utils.url_join_encode(this.kernel_url), settings);
403 $.ajax(utils.url_join_encode(this.kernel_url), settings);
403 }
404 }
404 };
405 };
405
406
406 Kernel.prototype.send_input_reply = function (input) {
407 Kernel.prototype.send_input_reply = function (input) {
407 var content = {
408 var content = {
408 value : input,
409 value : input,
409 };
410 };
410 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
411 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
411 var msg = this._get_msg("input_reply", content);
412 var msg = this._get_msg("input_reply", content);
412 this.stdin_channel.send(JSON.stringify(msg));
413 this.stdin_channel.send(JSON.stringify(msg));
413 return msg.header.msg_id;
414 return msg.header.msg_id;
414 };
415 };
415
416
416
417
417 // Reply handlers
418 // Reply handlers
418
419
419 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
420 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
420 this._iopub_handlers[msg_type] = callback;
421 this._iopub_handlers[msg_type] = callback;
421 };
422 };
422
423
423 Kernel.prototype.get_iopub_handler = function (msg_type) {
424 Kernel.prototype.get_iopub_handler = function (msg_type) {
424 // get iopub handler for a specific message type
425 // get iopub handler for a specific message type
425 return this._iopub_handlers[msg_type];
426 return this._iopub_handlers[msg_type];
426 };
427 };
427
428
428
429
429 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
430 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
430 // get callbacks for a specific message
431 // get callbacks for a specific message
431 return this._msg_callbacks[msg_id];
432 return this._msg_callbacks[msg_id];
432 };
433 };
433
434
434
435
435 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
436 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
436 if (this._msg_callbacks[msg_id] !== undefined ) {
437 if (this._msg_callbacks[msg_id] !== undefined ) {
437 delete this._msg_callbacks[msg_id];
438 delete this._msg_callbacks[msg_id];
438 }
439 }
439 };
440 };
440
441
441 /* Set callbacks for a particular message.
442 /* Set callbacks for a particular message.
442 * Callbacks should be a struct of the following form:
443 * Callbacks should be a struct of the following form:
443 * shell : {
444 * shell : {
444 *
445 *
445 * }
446 * }
446
447
447 */
448 */
448 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
449 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
449 if (callbacks) {
450 if (callbacks) {
450 // shallow-copy mapping, because we will modify it at the top level
451 // shallow-copy mapping, because we will modify it at the top level
451 var cbcopy = this._msg_callbacks[msg_id] = {};
452 var cbcopy = this._msg_callbacks[msg_id] = {};
452 cbcopy.shell = callbacks.shell;
453 cbcopy.shell = callbacks.shell;
453 cbcopy.iopub = callbacks.iopub;
454 cbcopy.iopub = callbacks.iopub;
454 cbcopy.input = callbacks.input;
455 cbcopy.input = callbacks.input;
455 this._msg_callbacks[msg_id] = cbcopy;
456 this._msg_callbacks[msg_id] = cbcopy;
456 }
457 }
457 };
458 };
458
459
459
460
460 Kernel.prototype._handle_shell_reply = function (e) {
461 Kernel.prototype._handle_shell_reply = function (e) {
461 var reply = $.parseJSON(e.data);
462 var reply = $.parseJSON(e.data);
462 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
463 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
463 var content = reply.content;
464 var content = reply.content;
464 var metadata = reply.metadata;
465 var metadata = reply.metadata;
465 var parent_id = reply.parent_header.msg_id;
466 var parent_id = reply.parent_header.msg_id;
466 var callbacks = this.get_callbacks_for_msg(parent_id);
467 var callbacks = this.get_callbacks_for_msg(parent_id);
467 if (!callbacks || !callbacks.shell) {
468 if (!callbacks || !callbacks.shell) {
468 return;
469 return;
469 }
470 }
470 var shell_callbacks = callbacks.shell;
471 var shell_callbacks = callbacks.shell;
471
472
472 // clear callbacks on shell
473 // clear callbacks on shell
473 delete callbacks.shell;
474 delete callbacks.shell;
474 delete callbacks.input;
475 delete callbacks.input;
475 if (!callbacks.iopub) {
476 if (!callbacks.iopub) {
476 this.clear_callbacks_for_msg(parent_id);
477 this.clear_callbacks_for_msg(parent_id);
477 }
478 }
478
479
479 if (shell_callbacks.reply !== undefined) {
480 if (shell_callbacks.reply !== undefined) {
480 shell_callbacks.reply(reply);
481 shell_callbacks.reply(reply);
481 }
482 }
482 if (content.payload && shell_callbacks.payload) {
483 if (content.payload && shell_callbacks.payload) {
483 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
484 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
484 }
485 }
485 };
486 };
486
487
487
488
488 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
489 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
489 var l = payloads.length;
490 var l = payloads.length;
490 // Payloads are handled by triggering events because we don't want the Kernel
491 // Payloads are handled by triggering events because we don't want the Kernel
491 // to depend on the Notebook or Pager classes.
492 // to depend on the Notebook or Pager classes.
492 for (var i=0; i<l; i++) {
493 for (var i=0; i<l; i++) {
493 var payload = payloads[i];
494 var payload = payloads[i];
494 var callback = payload_callbacks[payload.source];
495 var callback = payload_callbacks[payload.source];
495 if (callback) {
496 if (callback) {
496 callback(payload, msg);
497 callback(payload, msg);
497 }
498 }
498 }
499 }
499 };
500 };
500
501
501 Kernel.prototype._handle_status_message = function (msg) {
502 Kernel.prototype._handle_status_message = function (msg) {
502 var execution_state = msg.content.execution_state;
503 var execution_state = msg.content.execution_state;
503 var parent_id = msg.parent_header.msg_id;
504 var parent_id = msg.parent_header.msg_id;
504
505
505 // dispatch status msg callbacks, if any
506 // dispatch status msg callbacks, if any
506 var callbacks = this.get_callbacks_for_msg(parent_id);
507 var callbacks = this.get_callbacks_for_msg(parent_id);
507 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
508 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
508 try {
509 try {
509 callbacks.iopub.status(msg);
510 callbacks.iopub.status(msg);
510 } catch (e) {
511 } catch (e) {
511 console.log("Exception in status msg handler", e, e.stack);
512 console.log("Exception in status msg handler", e, e.stack);
512 }
513 }
513 }
514 }
514
515
515 if (execution_state === 'busy') {
516 if (execution_state === 'busy') {
516 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
517 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
517 } else if (execution_state === 'idle') {
518 } else if (execution_state === 'idle') {
518 // clear callbacks on idle, there can be no more
519 // clear callbacks on idle, there can be no more
519 if (callbacks !== undefined) {
520 if (callbacks !== undefined) {
520 delete callbacks.iopub;
521 delete callbacks.iopub;
521 delete callbacks.input;
522 delete callbacks.input;
522 if (!callbacks.shell) {
523 if (!callbacks.shell) {
523 this.clear_callbacks_for_msg(parent_id);
524 this.clear_callbacks_for_msg(parent_id);
524 }
525 }
525 }
526 }
526 // trigger status_idle event
527 // trigger status_idle event
527 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
528 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
528 } else if (execution_state === 'restarting') {
529 } else if (execution_state === 'restarting') {
529 // autorestarting is distinct from restarting,
530 // autorestarting is distinct from restarting,
530 // in that it means the kernel died and the server is restarting it.
531 // in that it means the kernel died and the server is restarting it.
531 // status_restarting sets the notification widget,
532 // status_restarting sets the notification widget,
532 // autorestart shows the more prominent dialog.
533 // autorestart shows the more prominent dialog.
533 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
534 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
534 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
535 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
535 } else if (execution_state === 'dead') {
536 } else if (execution_state === 'dead') {
536 this.stop_channels();
537 this.stop_channels();
537 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
538 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
538 }
539 }
539 };
540 };
540
541
541
542
542 // handle clear_output message
543 // handle clear_output message
543 Kernel.prototype._handle_clear_output = function (msg) {
544 Kernel.prototype._handle_clear_output = function (msg) {
544 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
545 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
545 if (!callbacks || !callbacks.iopub) {
546 if (!callbacks || !callbacks.iopub) {
546 return;
547 return;
547 }
548 }
548 var callback = callbacks.iopub.clear_output;
549 var callback = callbacks.iopub.clear_output;
549 if (callback) {
550 if (callback) {
550 callback(msg);
551 callback(msg);
551 }
552 }
552 };
553 };
553
554
554
555
555 // handle an output message (pyout, display_data, etc.)
556 // handle an output message (pyout, display_data, etc.)
556 Kernel.prototype._handle_output_message = function (msg) {
557 Kernel.prototype._handle_output_message = function (msg) {
557 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
558 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
558 if (!callbacks || !callbacks.iopub) {
559 if (!callbacks || !callbacks.iopub) {
559 return;
560 return;
560 }
561 }
561 var callback = callbacks.iopub.output;
562 var callback = callbacks.iopub.output;
562 if (callback) {
563 if (callback) {
563 callback(msg);
564 callback(msg);
564 }
565 }
565 };
566 };
566
567
567 // dispatch IOPub messages to respective handlers.
568 // dispatch IOPub messages to respective handlers.
568 // each message type should have a handler.
569 // each message type should have a handler.
569 Kernel.prototype._handle_iopub_message = function (e) {
570 Kernel.prototype._handle_iopub_message = function (e) {
570 var msg = $.parseJSON(e.data);
571 var msg = $.parseJSON(e.data);
571
572
572 var handler = this.get_iopub_handler(msg.header.msg_type);
573 var handler = this.get_iopub_handler(msg.header.msg_type);
573 if (handler !== undefined) {
574 if (handler !== undefined) {
574 handler(msg);
575 handler(msg);
575 }
576 }
576 };
577 };
577
578
578
579
579 Kernel.prototype._handle_input_request = function (e) {
580 Kernel.prototype._handle_input_request = function (e) {
580 var request = $.parseJSON(e.data);
581 var request = $.parseJSON(e.data);
581 var header = request.header;
582 var header = request.header;
582 var content = request.content;
583 var content = request.content;
583 var metadata = request.metadata;
584 var metadata = request.metadata;
584 var msg_type = header.msg_type;
585 var msg_type = header.msg_type;
585 if (msg_type !== 'input_request') {
586 if (msg_type !== 'input_request') {
586 console.log("Invalid input request!", request);
587 console.log("Invalid input request!", request);
587 return;
588 return;
588 }
589 }
589 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
590 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
590 if (callbacks) {
591 if (callbacks) {
591 if (callbacks.input) {
592 if (callbacks.input) {
592 callbacks.input(request);
593 callbacks.input(request);
593 }
594 }
594 }
595 }
595 };
596 };
596
597
597
598
598 IPython.Kernel = Kernel;
599 IPython.Kernel = Kernel;
599
600
600 return IPython;
601 return IPython;
601
602
602 }(IPython));
603 }(IPython));
603
604
@@ -1,118 +1,121 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 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
16
17 var Session = function(notebook, options){
17 var Session = function(notebook, options){
18 this.kernel = null;
18 this.kernel = null;
19 this.id = null;
19 this.id = null;
20 this.notebook = notebook;
20 this.notebook = notebook;
21 this.name = notebook.notebook_name;
21 this.name = notebook.notebook_name;
22 this.path = notebook.notebook_path;
22 this.path = notebook.notebook_path;
23 this.base_url = notebook.base_url;
23 this.base_url = notebook.base_url;
24 };
24 };
25
25
26 Session.prototype.start = function(callback) {
26 Session.prototype.start = function(callback) {
27 var that = this;
27 var that = this;
28 var model = {
28 var model = {
29 notebook : {
29 notebook : {
30 name : this.name,
30 name : this.name,
31 path : this.path
31 path : this.path
32 }
32 }
33 };
33 };
34 var settings = {
34 var settings = {
35 processData : false,
35 processData : false,
36 cache : false,
36 cache : false,
37 type : "POST",
37 type : "POST",
38 data: JSON.stringify(model),
38 data: JSON.stringify(model),
39 dataType : "json",
39 dataType : "json",
40 success : function (data, status, xhr) {
40 success : function (data, status, xhr) {
41 that._handle_start_success(data);
41 that._handle_start_success(data);
42 if (callback) {
42 if (callback) {
43 callback(data, status, xhr);
43 callback(data, status, xhr);
44 }
44 }
45 },
45 },
46 error : utils.log_ajax_error,
46 };
47 };
47 var url = utils.url_join_encode(this.base_url, 'api/sessions');
48 var url = utils.url_join_encode(this.base_url, 'api/sessions');
48 $.ajax(url, settings);
49 $.ajax(url, settings);
49 };
50 };
50
51
51 Session.prototype.rename_notebook = function (name, path) {
52 Session.prototype.rename_notebook = function (name, path) {
52 this.name = name;
53 this.name = name;
53 this.path = path;
54 this.path = path;
54 var model = {
55 var model = {
55 notebook : {
56 notebook : {
56 name : this.name,
57 name : this.name,
57 path : this.path
58 path : this.path
58 }
59 }
59 };
60 };
60 var settings = {
61 var settings = {
61 processData : false,
62 processData : false,
62 cache : false,
63 cache : false,
63 type : "PATCH",
64 type : "PATCH",
64 data: JSON.stringify(model),
65 data: JSON.stringify(model),
65 dataType : "json",
66 dataType : "json",
67 error : utils.log_ajax_error,
66 };
68 };
67 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
69 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
68 $.ajax(url, settings);
70 $.ajax(url, settings);
69 };
71 };
70
72
71 Session.prototype.delete = function() {
73 Session.prototype.delete = function() {
72 var settings = {
74 var settings = {
73 processData : false,
75 processData : false,
74 cache : false,
76 cache : false,
75 type : "DELETE",
77 type : "DELETE",
76 dataType : "json",
78 dataType : "json",
79 error : utils.log_ajax_error,
77 };
80 };
78 this.kernel.running = false;
81 this.kernel.running = false;
79 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
82 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
80 $.ajax(url, settings);
83 $.ajax(url, settings);
81 };
84 };
82
85
83 // Kernel related things
86 // Kernel related things
84 /**
87 /**
85 * Create the Kernel object associated with this Session.
88 * Create the Kernel object associated with this Session.
86 *
89 *
87 * @method _handle_start_success
90 * @method _handle_start_success
88 */
91 */
89 Session.prototype._handle_start_success = function (data, status, xhr) {
92 Session.prototype._handle_start_success = function (data, status, xhr) {
90 this.id = data.id;
93 this.id = data.id;
91 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
94 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
92 this.kernel = new IPython.Kernel(kernel_service_url);
95 this.kernel = new IPython.Kernel(kernel_service_url);
93 this.kernel._kernel_started(data.kernel);
96 this.kernel._kernel_started(data.kernel);
94 };
97 };
95
98
96 /**
99 /**
97 * Prompt the user to restart the IPython kernel.
100 * Prompt the user to restart the IPython kernel.
98 *
101 *
99 * @method restart_kernel
102 * @method restart_kernel
100 */
103 */
101 Session.prototype.restart_kernel = function () {
104 Session.prototype.restart_kernel = function () {
102 this.kernel.restart();
105 this.kernel.restart();
103 };
106 };
104
107
105 Session.prototype.interrupt_kernel = function() {
108 Session.prototype.interrupt_kernel = function() {
106 this.kernel.interrupt();
109 this.kernel.interrupt();
107 };
110 };
108
111
109
112
110 Session.prototype.kill_kernel = function() {
113 Session.prototype.kill_kernel = function() {
111 this.kernel.kill();
114 this.kernel.kill();
112 };
115 };
113
116
114 IPython.Session = Session;
117 IPython.Session = Session;
115
118
116 return IPython;
119 return IPython;
117
120
118 }(IPython));
121 }(IPython));
@@ -1,196 +1,198 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 // NotebookList
9 // NotebookList
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
16
17 var ClusterList = function (selector, options) {
17 var ClusterList = function (selector, options) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 options = options || {};
24 options = options || {};
25 this.options = options;
25 this.options = options;
26 this.base_url = options.base_url || utils.get_body_data("baseUrl");
26 this.base_url = options.base_url || utils.get_body_data("baseUrl");
27 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
27 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
28 };
28 };
29
29
30 ClusterList.prototype.style = function () {
30 ClusterList.prototype.style = function () {
31 $('#cluster_list').addClass('list_container');
31 $('#cluster_list').addClass('list_container');
32 $('#cluster_toolbar').addClass('list_toolbar');
32 $('#cluster_toolbar').addClass('list_toolbar');
33 $('#cluster_list_info').addClass('toolbar_info');
33 $('#cluster_list_info').addClass('toolbar_info');
34 $('#cluster_buttons').addClass('toolbar_buttons');
34 $('#cluster_buttons').addClass('toolbar_buttons');
35 };
35 };
36
36
37
37
38 ClusterList.prototype.bind_events = function () {
38 ClusterList.prototype.bind_events = function () {
39 var that = this;
39 var that = this;
40 $('#refresh_cluster_list').click(function () {
40 $('#refresh_cluster_list').click(function () {
41 that.load_list();
41 that.load_list();
42 });
42 });
43 };
43 };
44
44
45
45
46 ClusterList.prototype.load_list = function () {
46 ClusterList.prototype.load_list = function () {
47 var settings = {
47 var settings = {
48 processData : false,
48 processData : false,
49 cache : false,
49 cache : false,
50 type : "GET",
50 type : "GET",
51 dataType : "json",
51 dataType : "json",
52 success : $.proxy(this.load_list_success, this)
52 success : $.proxy(this.load_list_success, this),
53 error : utils.log_ajax_error,
53 };
54 };
54 var url = utils.url_join_encode(this.base_url, 'clusters');
55 var url = utils.url_join_encode(this.base_url, 'clusters');
55 $.ajax(url, settings);
56 $.ajax(url, settings);
56 };
57 };
57
58
58
59
59 ClusterList.prototype.clear_list = function () {
60 ClusterList.prototype.clear_list = function () {
60 this.element.children('.list_item').remove();
61 this.element.children('.list_item').remove();
61 };
62 };
62
63
63 ClusterList.prototype.load_list_success = function (data, status, xhr) {
64 ClusterList.prototype.load_list_success = function (data, status, xhr) {
64 this.clear_list();
65 this.clear_list();
65 var len = data.length;
66 var len = data.length;
66 for (var i=0; i<len; i++) {
67 for (var i=0; i<len; i++) {
67 var element = $('<div/>');
68 var element = $('<div/>');
68 var item = new ClusterItem(element, this.options);
69 var item = new ClusterItem(element, this.options);
69 item.update_state(data[i]);
70 item.update_state(data[i]);
70 element.data('item', item);
71 element.data('item', item);
71 this.element.append(element);
72 this.element.append(element);
72 }
73 }
73 };
74 };
74
75
75
76
76 var ClusterItem = function (element, options) {
77 var ClusterItem = function (element, options) {
77 this.element = $(element);
78 this.element = $(element);
78 this.base_url = options.base_url || utils.get_body_data("baseUrl");
79 this.base_url = options.base_url || utils.get_body_data("baseUrl");
79 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
80 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
80 this.data = null;
81 this.data = null;
81 this.style();
82 this.style();
82 };
83 };
83
84
84 ClusterItem.prototype.style = function () {
85 ClusterItem.prototype.style = function () {
85 this.element.addClass('list_item').addClass("row-fluid");
86 this.element.addClass('list_item').addClass("row-fluid");
86 };
87 };
87
88
88 ClusterItem.prototype.update_state = function (data) {
89 ClusterItem.prototype.update_state = function (data) {
89 this.data = data;
90 this.data = data;
90 if (data.status === 'running') {
91 if (data.status === 'running') {
91 this.state_running();
92 this.state_running();
92 } else if (data.status === 'stopped') {
93 } else if (data.status === 'stopped') {
93 this.state_stopped();
94 this.state_stopped();
94 }
95 }
95 };
96 };
96
97
97
98
98 ClusterItem.prototype.state_stopped = function () {
99 ClusterItem.prototype.state_stopped = function () {
99 var that = this;
100 var that = this;
100 var profile_col = $('<div/>').addClass('profile_col span4').text(this.data.profile);
101 var profile_col = $('<div/>').addClass('profile_col span4').text(this.data.profile);
101 var status_col = $('<div/>').addClass('status_col span3').text('stopped');
102 var status_col = $('<div/>').addClass('status_col span3').text('stopped');
102 var engines_col = $('<div/>').addClass('engine_col span3');
103 var engines_col = $('<div/>').addClass('engine_col span3');
103 var input = $('<input/>').attr('type','number')
104 var input = $('<input/>').attr('type','number')
104 .attr('min',1)
105 .attr('min',1)
105 .attr('size',3)
106 .attr('size',3)
106 .addClass('engine_num_input');
107 .addClass('engine_num_input');
107 engines_col.append(input);
108 engines_col.append(input);
108 var start_button = $('<button/>').addClass("btn btn-mini").text("Start");
109 var start_button = $('<button/>').addClass("btn btn-mini").text("Start");
109 var action_col = $('<div/>').addClass('action_col span2').append(
110 var action_col = $('<div/>').addClass('action_col span2').append(
110 $("<span/>").addClass("item_buttons btn-group").append(
111 $("<span/>").addClass("item_buttons btn-group").append(
111 start_button
112 start_button
112 )
113 )
113 );
114 );
114 this.element.empty()
115 this.element.empty()
115 .append(profile_col)
116 .append(profile_col)
116 .append(status_col)
117 .append(status_col)
117 .append(engines_col)
118 .append(engines_col)
118 .append(action_col);
119 .append(action_col);
119 start_button.click(function (e) {
120 start_button.click(function (e) {
120 var n = that.element.find('.engine_num_input').val();
121 var n = that.element.find('.engine_num_input').val();
121 if (!/^\d+$/.test(n) && n.length>0) {
122 if (!/^\d+$/.test(n) && n.length>0) {
122 status_col.text('invalid engine #');
123 status_col.text('invalid engine #');
123 } else {
124 } else {
124 var settings = {
125 var settings = {
125 cache : false,
126 cache : false,
126 data : {n:n},
127 data : {n:n},
127 type : "POST",
128 type : "POST",
128 dataType : "json",
129 dataType : "json",
129 success : function (data, status, xhr) {
130 success : function (data, status, xhr) {
130 that.update_state(data);
131 that.update_state(data);
131 },
132 },
132 error : function (data, status, xhr) {
133 error : function (xhr, status, error) {
133 status_col.text("error starting cluster");
134 status_col.text("error starting cluster");
135 utils.log_ajax_error(xhr, status, error);
134 }
136 }
135 };
137 };
136 status_col.text('starting');
138 status_col.text('starting');
137 var url = utils.url_join_encode(
139 var url = utils.url_join_encode(
138 that.base_url,
140 that.base_url,
139 'clusters',
141 'clusters',
140 that.data.profile,
142 that.data.profile,
141 'start'
143 'start'
142 );
144 );
143 $.ajax(url, settings);
145 $.ajax(url, settings);
144 }
146 }
145 });
147 });
146 };
148 };
147
149
148
150
149 ClusterItem.prototype.state_running = function () {
151 ClusterItem.prototype.state_running = function () {
150 var that = this;
152 var that = this;
151 var profile_col = $('<div/>').addClass('profile_col span4').text(this.data.profile);
153 var profile_col = $('<div/>').addClass('profile_col span4').text(this.data.profile);
152 var status_col = $('<div/>').addClass('status_col span3').text('running');
154 var status_col = $('<div/>').addClass('status_col span3').text('running');
153 var engines_col = $('<div/>').addClass('engines_col span3').text(this.data.n);
155 var engines_col = $('<div/>').addClass('engines_col span3').text(this.data.n);
154 var stop_button = $('<button/>').addClass("btn btn-mini").text("Stop");
156 var stop_button = $('<button/>').addClass("btn btn-mini").text("Stop");
155 var action_col = $('<div/>').addClass('action_col span2').append(
157 var action_col = $('<div/>').addClass('action_col span2').append(
156 $("<span/>").addClass("item_buttons btn-group").append(
158 $("<span/>").addClass("item_buttons btn-group").append(
157 stop_button
159 stop_button
158 )
160 )
159 );
161 );
160 this.element.empty()
162 this.element.empty()
161 .append(profile_col)
163 .append(profile_col)
162 .append(status_col)
164 .append(status_col)
163 .append(engines_col)
165 .append(engines_col)
164 .append(action_col);
166 .append(action_col);
165 stop_button.click(function (e) {
167 stop_button.click(function (e) {
166 var settings = {
168 var settings = {
167 cache : false,
169 cache : false,
168 type : "POST",
170 type : "POST",
169 dataType : "json",
171 dataType : "json",
170 success : function (data, status, xhr) {
172 success : function (data, status, xhr) {
171 that.update_state(data);
173 that.update_state(data);
172 },
174 },
173 error : function (data, status, xhr) {
175 error : function (xhr, status, error) {
174 console.log('error',data);
176 utils.log_ajax_error(xhr, status, error),
175 status_col.text("error stopping cluster");
177 status_col.text("error stopping cluster");
176 }
178 }
177 };
179 };
178 status_col.text('stopping');
180 status_col.text('stopping');
179 var url = utils.url_join_encode(
181 var url = utils.url_join_encode(
180 that.base_url,
182 that.base_url,
181 'clusters',
183 'clusters',
182 that.data.profile,
184 that.data.profile,
183 'stop'
185 'stop'
184 );
186 );
185 $.ajax(url, settings);
187 $.ajax(url, settings);
186 });
188 });
187 };
189 };
188
190
189
191
190 IPython.ClusterList = ClusterList;
192 IPython.ClusterList = ClusterList;
191 IPython.ClusterItem = ClusterItem;
193 IPython.ClusterItem = ClusterItem;
192
194
193 return IPython;
195 return IPython;
194
196
195 }(IPython));
197 }(IPython));
196
198
@@ -1,422 +1,441 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 // NotebookList
9 // NotebookList
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
16
17 var NotebookList = function (selector, options, element_name) {
17 var NotebookList = function (selector, options, element_name) {
18 var that = this
18 var that = this
19 // allow code re-use by just changing element_name in kernellist.js
19 // allow code re-use by just changing element_name in kernellist.js
20 this.element_name = element_name || 'notebook';
20 this.element_name = element_name || 'notebook';
21 this.selector = selector;
21 this.selector = selector;
22 if (this.selector !== undefined) {
22 if (this.selector !== undefined) {
23 this.element = $(selector);
23 this.element = $(selector);
24 this.style();
24 this.style();
25 this.bind_events();
25 this.bind_events();
26 }
26 }
27 this.notebooks_list = [];
27 this.notebooks_list = [];
28 this.sessions = {};
28 this.sessions = {};
29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
30 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
30 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
31 $([IPython.events]).on('sessions_loaded.Dashboard',
31 $([IPython.events]).on('sessions_loaded.Dashboard',
32 function(e, d) { that.sessions_loaded(d); });
32 function(e, d) { that.sessions_loaded(d); });
33 };
33 };
34
34
35 NotebookList.prototype.style = function () {
35 NotebookList.prototype.style = function () {
36 var prefix = '#' + this.element_name
36 var prefix = '#' + this.element_name
37 $(prefix + '_toolbar').addClass('list_toolbar');
37 $(prefix + '_toolbar').addClass('list_toolbar');
38 $(prefix + '_list_info').addClass('toolbar_info');
38 $(prefix + '_list_info').addClass('toolbar_info');
39 $(prefix + '_buttons').addClass('toolbar_buttons');
39 $(prefix + '_buttons').addClass('toolbar_buttons');
40 $(prefix + '_list_header').addClass('list_header');
40 $(prefix + '_list_header').addClass('list_header');
41 this.element.addClass("list_container");
41 this.element.addClass("list_container");
42 };
42 };
43
43
44
44
45 NotebookList.prototype.bind_events = function () {
45 NotebookList.prototype.bind_events = function () {
46 var that = this;
46 var that = this;
47 $('#refresh_' + this.element_name + '_list').click(function () {
47 $('#refresh_' + this.element_name + '_list').click(function () {
48 that.load_sessions();
48 that.load_sessions();
49 });
49 });
50 this.element.bind('dragover', function () {
50 this.element.bind('dragover', function () {
51 return false;
51 return false;
52 });
52 });
53 this.element.bind('drop', function(event){
53 this.element.bind('drop', function(event){
54 that.handleFilesUpload(event,'drop');
54 that.handleFilesUpload(event,'drop');
55 return false;
55 return false;
56 });
56 });
57 };
57 };
58
58
59 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
59 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
60 var that = this;
60 var that = this;
61 var files;
61 var files;
62 if(dropOrForm =='drop'){
62 if(dropOrForm =='drop'){
63 files = event.originalEvent.dataTransfer.files;
63 files = event.originalEvent.dataTransfer.files;
64 } else
64 } else
65 {
65 {
66 files = event.originalEvent.target.files;
66 files = event.originalEvent.target.files;
67 }
67 }
68 for (var i = 0; i < files.length; i++) {
68 for (var i = 0; i < files.length; i++) {
69 var f = files[i];
69 var f = files[i];
70 var reader = new FileReader();
70 var reader = new FileReader();
71 reader.readAsText(f);
71 reader.readAsText(f);
72 var name_and_ext = utils.splitext(f.name);
72 var name_and_ext = utils.splitext(f.name);
73 var file_ext = name_and_ext[1];
73 var file_ext = name_and_ext[1];
74 if (file_ext === '.ipynb') {
74 if (file_ext === '.ipynb') {
75 var item = that.new_notebook_item(0);
75 var item = that.new_notebook_item(0);
76 item.addClass('new-file');
76 item.addClass('new-file');
77 that.add_name_input(f.name, item);
77 that.add_name_input(f.name, item);
78 // Store the notebook item in the reader so we can use it later
78 // Store the notebook item in the reader so we can use it later
79 // to know which item it belongs to.
79 // to know which item it belongs to.
80 $(reader).data('item', item);
80 $(reader).data('item', item);
81 reader.onload = function (event) {
81 reader.onload = function (event) {
82 var nbitem = $(event.target).data('item');
82 var nbitem = $(event.target).data('item');
83 that.add_notebook_data(event.target.result, nbitem);
83 that.add_notebook_data(event.target.result, nbitem);
84 that.add_upload_button(nbitem);
84 that.add_upload_button(nbitem);
85 };
85 };
86 } else {
86 } else {
87 var dialog = 'Uploaded notebooks must be .ipynb files';
87 var dialog = 'Uploaded notebooks must be .ipynb files';
88 IPython.dialog.modal({
88 IPython.dialog.modal({
89 title : 'Invalid file type',
89 title : 'Invalid file type',
90 body : dialog,
90 body : dialog,
91 buttons : {'OK' : {'class' : 'btn-primary'}}
91 buttons : {'OK' : {'class' : 'btn-primary'}}
92 });
92 });
93 }
93 }
94 }
94 }
95 // Replace the file input form wth a clone of itself. This is required to
95 // Replace the file input form wth a clone of itself. This is required to
96 // reset the form. Otherwise, if you upload a file, delete it and try to
96 // reset the form. Otherwise, if you upload a file, delete it and try to
97 // upload it again, the changed event won't fire.
97 // upload it again, the changed event won't fire.
98 var form = $('input.fileinput');
98 var form = $('input.fileinput');
99 form.replaceWith(form.clone(true));
99 form.replaceWith(form.clone(true));
100 return false;
100 return false;
101 };
101 };
102
102
103 NotebookList.prototype.clear_list = function (remove_uploads) {
103 NotebookList.prototype.clear_list = function (remove_uploads) {
104 // Clears the navigation tree.
104 // Clears the navigation tree.
105 //
105 //
106 // Parameters
106 // Parameters
107 // remove_uploads: bool=False
107 // remove_uploads: bool=False
108 // Should upload prompts also be removed from the tree.
108 // Should upload prompts also be removed from the tree.
109 if (remove_uploads) {
109 if (remove_uploads) {
110 this.element.children('.list_item').remove();
110 this.element.children('.list_item').remove();
111 } else {
111 } else {
112 this.element.children('.list_item:not(.new-file)').remove();
112 this.element.children('.list_item:not(.new-file)').remove();
113 }
113 }
114 };
114 };
115
115
116 NotebookList.prototype.load_sessions = function(){
116 NotebookList.prototype.load_sessions = function(){
117 IPython.session_list.load_sessions();
117 IPython.session_list.load_sessions();
118 };
118 };
119
119
120
120
121 NotebookList.prototype.sessions_loaded = function(data){
121 NotebookList.prototype.sessions_loaded = function(data){
122 this.sessions = data;
122 this.sessions = data;
123 this.load_list();
123 this.load_list();
124 };
124 };
125
125
126 NotebookList.prototype.load_list = function () {
126 NotebookList.prototype.load_list = function () {
127 var that = this;
127 var that = this;
128 var settings = {
128 var settings = {
129 processData : false,
129 processData : false,
130 cache : false,
130 cache : false,
131 type : "GET",
131 type : "GET",
132 dataType : "json",
132 dataType : "json",
133 success : $.proxy(this.list_loaded, this),
133 success : $.proxy(this.list_loaded, this),
134 error : $.proxy( function(){
134 error : $.proxy( function(xhr, status, error){
135 utils.log_ajax_error(xhr, status, error);
135 that.list_loaded([], null, null, {msg:"Error connecting to server."});
136 that.list_loaded([], null, null, {msg:"Error connecting to server."});
136 },this)
137 },this)
137 };
138 };
138
139
139 var url = utils.url_join_encode(
140 var url = utils.url_join_encode(
140 this.base_url,
141 this.base_url,
141 'api',
142 'api',
142 'notebooks',
143 'notebooks',
143 this.notebook_path
144 this.notebook_path
144 );
145 );
145 $.ajax(url, settings);
146 $.ajax(url, settings);
146 };
147 };
147
148
148
149
149 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
150 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
150 var message = 'Notebook list empty.';
151 var message = 'Notebook list empty.';
151 if (param !== undefined && param.msg) {
152 if (param !== undefined && param.msg) {
152 message = param.msg;
153 message = param.msg;
153 }
154 }
154 var item = null;
155 var item = null;
155 var len = data.length;
156 var len = data.length;
156 this.clear_list();
157 this.clear_list();
157 if (len === 0) {
158 if (len === 0) {
158 item = this.new_notebook_item(0);
159 item = this.new_notebook_item(0);
159 var span12 = item.children().first();
160 var span12 = item.children().first();
160 span12.empty();
161 span12.empty();
161 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
162 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
162 }
163 }
163 var path = this.notebook_path;
164 var path = this.notebook_path;
164 var offset = 0;
165 var offset = 0;
165 if (path !== '') {
166 if (path !== '') {
166 item = this.new_notebook_item(0);
167 item = this.new_notebook_item(0);
167 this.add_dir(path, '..', item);
168 this.add_dir(path, '..', item);
168 offset = 1;
169 offset = 1;
169 }
170 }
170 for (var i=0; i<len; i++) {
171 for (var i=0; i<len; i++) {
171 if (data[i].type === 'directory') {
172 if (data[i].type === 'directory') {
172 var name = data[i].name;
173 var name = data[i].name;
173 item = this.new_notebook_item(i+offset);
174 item = this.new_notebook_item(i+offset);
174 this.add_dir(path, name, item);
175 this.add_dir(path, name, item);
175 } else {
176 } else {
176 var name = data[i].name;
177 var name = data[i].name;
177 item = this.new_notebook_item(i+offset);
178 item = this.new_notebook_item(i+offset);
178 this.add_link(path, name, item);
179 this.add_link(path, name, item);
179 name = utils.url_path_join(path, name);
180 name = utils.url_path_join(path, name);
180 if(this.sessions[name] === undefined){
181 if(this.sessions[name] === undefined){
181 this.add_delete_button(item);
182 this.add_delete_button(item);
182 } else {
183 } else {
183 this.add_shutdown_button(item,this.sessions[name]);
184 this.add_shutdown_button(item,this.sessions[name]);
184 }
185 }
185 }
186 }
186 }
187 }
187 };
188 };
188
189
189
190
190 NotebookList.prototype.new_notebook_item = function (index) {
191 NotebookList.prototype.new_notebook_item = function (index) {
191 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
192 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
192 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
193 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
193 // item.css('border-top-style','none');
194 // item.css('border-top-style','none');
194 item.append($("<div/>").addClass("span12").append(
195 item.append($("<div/>").addClass("span12").append(
195 $('<i/>').addClass('item_icon')
196 $('<i/>').addClass('item_icon')
196 ).append(
197 ).append(
197 $("<a/>").addClass("item_link").append(
198 $("<a/>").addClass("item_link").append(
198 $("<span/>").addClass("item_name")
199 $("<span/>").addClass("item_name")
199 )
200 )
200 ).append(
201 ).append(
201 $('<div/>').addClass("item_buttons btn-group pull-right")
202 $('<div/>').addClass("item_buttons btn-group pull-right")
202 ));
203 ));
203
204
204 if (index === -1) {
205 if (index === -1) {
205 this.element.append(item);
206 this.element.append(item);
206 } else {
207 } else {
207 this.element.children().eq(index).after(item);
208 this.element.children().eq(index).after(item);
208 }
209 }
209 return item;
210 return item;
210 };
211 };
211
212
212
213
213 NotebookList.prototype.add_dir = function (path, name, item) {
214 NotebookList.prototype.add_dir = function (path, name, item) {
214 item.data('name', name);
215 item.data('name', name);
215 item.data('path', path);
216 item.data('path', path);
216 item.find(".item_name").text(name);
217 item.find(".item_name").text(name);
217 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
218 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
218 item.find("a.item_link")
219 item.find("a.item_link")
219 .attr('href',
220 .attr('href',
220 utils.url_join_encode(
221 utils.url_join_encode(
221 this.base_url,
222 this.base_url,
222 "tree",
223 "tree",
223 path,
224 path,
224 name
225 name
225 )
226 )
226 );
227 );
227 };
228 };
228
229
229
230
230 NotebookList.prototype.add_link = function (path, nbname, item) {
231 NotebookList.prototype.add_link = function (path, nbname, item) {
231 item.data('nbname', nbname);
232 item.data('nbname', nbname);
232 item.data('path', path);
233 item.data('path', path);
233 item.find(".item_name").text(nbname);
234 item.find(".item_name").text(nbname);
234 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
235 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
235 item.find("a.item_link")
236 item.find("a.item_link")
236 .attr('href',
237 .attr('href',
237 utils.url_join_encode(
238 utils.url_join_encode(
238 this.base_url,
239 this.base_url,
239 "notebooks",
240 "notebooks",
240 path,
241 path,
241 nbname
242 nbname
242 )
243 )
243 ).attr('target','_blank');
244 ).attr('target','_blank');
244 };
245 };
245
246
246
247
247 NotebookList.prototype.add_name_input = function (nbname, item) {
248 NotebookList.prototype.add_name_input = function (nbname, item) {
248 item.data('nbname', nbname);
249 item.data('nbname', nbname);
249 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
250 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
250 item.find(".item_name").empty().append(
251 item.find(".item_name").empty().append(
251 $('<input/>')
252 $('<input/>')
252 .addClass("nbname_input")
253 .addClass("nbname_input")
253 .attr('value', utils.splitext(nbname)[0])
254 .attr('value', utils.splitext(nbname)[0])
254 .attr('size', '30')
255 .attr('size', '30')
255 .attr('type', 'text')
256 .attr('type', 'text')
256 );
257 );
257 };
258 };
258
259
259
260
260 NotebookList.prototype.add_notebook_data = function (data, item) {
261 NotebookList.prototype.add_notebook_data = function (data, item) {
261 item.data('nbdata', data);
262 item.data('nbdata', data);
262 };
263 };
263
264
264
265
265 NotebookList.prototype.add_shutdown_button = function (item, session) {
266 NotebookList.prototype.add_shutdown_button = function (item, session) {
266 var that = this;
267 var that = this;
267 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini btn-danger").
268 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini btn-danger").
268 click(function (e) {
269 click(function (e) {
269 var settings = {
270 var settings = {
270 processData : false,
271 processData : false,
271 cache : false,
272 cache : false,
272 type : "DELETE",
273 type : "DELETE",
273 dataType : "json",
274 dataType : "json",
274 success : function () {
275 success : function () {
275 that.load_sessions();
276 that.load_sessions();
276 }
277 },
278 error : utils.log_ajax_error,
277 };
279 };
278 var url = utils.url_join_encode(
280 var url = utils.url_join_encode(
279 that.base_url,
281 that.base_url,
280 'api/sessions',
282 'api/sessions',
281 session
283 session
282 );
284 );
283 $.ajax(url, settings);
285 $.ajax(url, settings);
284 return false;
286 return false;
285 });
287 });
286 // var new_buttons = item.find('a'); // shutdown_button;
288 // var new_buttons = item.find('a'); // shutdown_button;
287 item.find(".item_buttons").text("").append(shutdown_button);
289 item.find(".item_buttons").text("").append(shutdown_button);
288 };
290 };
289
291
290 NotebookList.prototype.add_delete_button = function (item) {
292 NotebookList.prototype.add_delete_button = function (item) {
291 var new_buttons = $('<span/>').addClass("btn-group pull-right");
293 var new_buttons = $('<span/>').addClass("btn-group pull-right");
292 var notebooklist = this;
294 var notebooklist = this;
293 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
295 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
294 click(function (e) {
296 click(function (e) {
295 // $(this) is the button that was clicked.
297 // $(this) is the button that was clicked.
296 var that = $(this);
298 var that = $(this);
297 // We use the nbname and notebook_id from the parent notebook_item element's
299 // We use the nbname and notebook_id from the parent notebook_item element's
298 // data because the outer scopes values change as we iterate through the loop.
300 // data because the outer scopes values change as we iterate through the loop.
299 var parent_item = that.parents('div.list_item');
301 var parent_item = that.parents('div.list_item');
300 var nbname = parent_item.data('nbname');
302 var nbname = parent_item.data('nbname');
301 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
303 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
302 IPython.dialog.modal({
304 IPython.dialog.modal({
303 title : "Delete notebook",
305 title : "Delete notebook",
304 body : message,
306 body : message,
305 buttons : {
307 buttons : {
306 Delete : {
308 Delete : {
307 class: "btn-danger",
309 class: "btn-danger",
308 click: function() {
310 click: function() {
309 var settings = {
311 var settings = {
310 processData : false,
312 processData : false,
311 cache : false,
313 cache : false,
312 type : "DELETE",
314 type : "DELETE",
313 dataType : "json",
315 dataType : "json",
314 success : function (data, status, xhr) {
316 success : function (data, status, xhr) {
315 parent_item.remove();
317 parent_item.remove();
316 }
318 },
319 error : utils.log_ajax_error,
317 };
320 };
318 var url = utils.url_join_encode(
321 var url = utils.url_join_encode(
319 notebooklist.base_url,
322 notebooklist.base_url,
320 'api/notebooks',
323 'api/notebooks',
321 notebooklist.notebook_path,
324 notebooklist.notebook_path,
322 nbname
325 nbname
323 );
326 );
324 $.ajax(url, settings);
327 $.ajax(url, settings);
325 }
328 }
326 },
329 },
327 Cancel : {}
330 Cancel : {}
328 }
331 }
329 });
332 });
330 return false;
333 return false;
331 });
334 });
332 item.find(".item_buttons").text("").append(delete_button);
335 item.find(".item_buttons").text("").append(delete_button);
333 };
336 };
334
337
335
338
336 NotebookList.prototype.add_upload_button = function (item) {
339 NotebookList.prototype.add_upload_button = function (item) {
337 var that = this;
340 var that = this;
338 var upload_button = $('<button/>').text("Upload")
341 var upload_button = $('<button/>').text("Upload")
339 .addClass('btn btn-primary btn-mini upload_button')
342 .addClass('btn btn-primary btn-mini upload_button')
340 .click(function (e) {
343 .click(function (e) {
341 var nbname = item.find('.item_name > input').val();
344 var nbname = item.find('.item_name > input').val();
342 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
345 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
343 nbname = nbname + ".ipynb";
346 nbname = nbname + ".ipynb";
344 }
347 }
345 var path = that.notebook_path;
348 var path = that.notebook_path;
346 var nbdata = item.data('nbdata');
349 var nbdata = item.data('nbdata');
347 var content_type = 'application/json';
350 var content_type = 'application/json';
348 var model = {
351 var model = {
349 content : JSON.parse(nbdata),
352 content : JSON.parse(nbdata),
350 };
353 };
351 var settings = {
354 var settings = {
352 processData : false,
355 processData : false,
353 cache : false,
356 cache : false,
354 type : 'PUT',
357 type : 'PUT',
355 dataType : 'json',
358 dataType : 'json',
356 data : JSON.stringify(model),
359 data : JSON.stringify(model),
357 headers : {'Content-Type': content_type},
360 headers : {'Content-Type': content_type},
358 success : function (data, status, xhr) {
361 success : function (data, status, xhr) {
359 that.add_link(path, nbname, item);
362 that.add_link(path, nbname, item);
360 that.add_delete_button(item);
363 that.add_delete_button(item);
361 },
364 },
362 error : function (data, status, xhr) {
365 error : utils.log_ajax_error,
363 console.log(data, status);
364 }
365 };
366 };
366
367
367 var url = utils.url_join_encode(
368 var url = utils.url_join_encode(
368 that.base_url,
369 that.base_url,
369 'api/notebooks',
370 'api/notebooks',
370 that.notebook_path,
371 that.notebook_path,
371 nbname
372 nbname
372 );
373 );
373 $.ajax(url, settings);
374 $.ajax(url, settings);
374 return false;
375 return false;
375 });
376 });
376 var cancel_button = $('<button/>').text("Cancel")
377 var cancel_button = $('<button/>').text("Cancel")
377 .addClass("btn btn-mini")
378 .addClass("btn btn-mini")
378 .click(function (e) {
379 .click(function (e) {
379 console.log('cancel click');
380 console.log('cancel click');
380 item.remove();
381 item.remove();
381 return false;
382 return false;
382 });
383 });
383 item.find(".item_buttons").empty()
384 item.find(".item_buttons").empty()
384 .append(upload_button)
385 .append(upload_button)
385 .append(cancel_button);
386 .append(cancel_button);
386 };
387 };
387
388
388
389
389 NotebookList.prototype.new_notebook = function(){
390 NotebookList.prototype.new_notebook = function(){
390 var path = this.notebook_path;
391 var path = this.notebook_path;
391 var base_url = this.base_url;
392 var base_url = this.base_url;
392 var settings = {
393 var settings = {
393 processData : false,
394 processData : false,
394 cache : false,
395 cache : false,
395 type : "POST",
396 type : "POST",
396 dataType : "json",
397 dataType : "json",
397 async : false,
398 async : false,
398 success : function (data, status, xhr) {
399 success : function (data, status, xhr) {
399 var notebook_name = data.name;
400 var notebook_name = data.name;
400 window.open(
401 window.open(
401 utils.url_join_encode(
402 utils.url_join_encode(
402 base_url,
403 base_url,
403 'notebooks',
404 'notebooks',
404 path,
405 path,
405 notebook_name),
406 notebook_name),
406 '_blank'
407 '_blank'
407 );
408 );
408 }
409 },
410 error : $.proxy(this.new_notebook_failed, this),
409 };
411 };
410 var url = utils.url_join_encode(
412 var url = utils.url_join_encode(
411 base_url,
413 base_url,
412 'api/notebooks',
414 'api/notebooks',
413 path
415 path
414 );
416 );
415 $.ajax(url, settings);
417 $.ajax(url, settings);
416 };
418 };
417
419
420
421 NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
422 utils.log_ajax_error(xhr, status, error);
423 var msg;
424 if (xhr.responseJSON && xhr.responseJSON.message) {
425 msg = xhr.responseJSON.message;
426 } else {
427 msg = xhr.statusText;
428 }
429 IPython.dialog.modal({
430 title : 'Creating Notebook Failed',
431 body : "The error was: " + msg,
432 buttons : {'OK' : {'class' : 'btn-primary'}}
433 });
434 }
435
436
418 IPython.NotebookList = NotebookList;
437 IPython.NotebookList = NotebookList;
419
438
420 return IPython;
439 return IPython;
421
440
422 }(IPython));
441 }(IPython));
@@ -1,52 +1,53 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2014 The IPython Development Team
2 // Copyright (C) 2014 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 // Running Kernels List
9 // Running Kernels List
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
16
17 var SesssionList = function (options) {
17 var SesssionList = function (options) {
18 this.sessions = {};
18 this.sessions = {};
19 this.base_url = options.base_url || utils.get_body_data("baseUrl");
19 this.base_url = options.base_url || utils.get_body_data("baseUrl");
20 };
20 };
21
21
22 SesssionList.prototype.load_sessions = function(){
22 SesssionList.prototype.load_sessions = function(){
23 var that = this;
23 var that = this;
24 var settings = {
24 var settings = {
25 processData : false,
25 processData : false,
26 cache : false,
26 cache : false,
27 type : "GET",
27 type : "GET",
28 dataType : "json",
28 dataType : "json",
29 success : $.proxy(that.sessions_loaded, this)
29 success : $.proxy(that.sessions_loaded, this),
30 error : utils.log_ajax_error,
30 };
31 };
31 var url = utils.url_join_encode(this.base_url, 'api/sessions');
32 var url = utils.url_join_encode(this.base_url, 'api/sessions');
32 $.ajax(url, settings);
33 $.ajax(url, settings);
33 };
34 };
34
35
35 SesssionList.prototype.sessions_loaded = function(data){
36 SesssionList.prototype.sessions_loaded = function(data){
36 this.sessions = {};
37 this.sessions = {};
37 var len = data.length;
38 var len = data.length;
38 var nb_path;
39 var nb_path;
39 for (var i=0; i<len; i++) {
40 for (var i=0; i<len; i++) {
40 nb_path = utils.url_path_join(
41 nb_path = utils.url_path_join(
41 data[i].notebook.path,
42 data[i].notebook.path,
42 data[i].notebook.name
43 data[i].notebook.name
43 );
44 );
44 this.sessions[nb_path] = data[i].id;
45 this.sessions[nb_path] = data[i].id;
45 }
46 }
46 $([IPython.events]).trigger('sessions_loaded.Dashboard', this.sessions);
47 $([IPython.events]).trigger('sessions_loaded.Dashboard', this.sessions);
47 };
48 };
48 IPython.SesssionList = SesssionList;
49 IPython.SesssionList = SesssionList;
49
50
50 return IPython;
51 return IPython;
51
52
52 }(IPython));
53 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now