##// END OF EJS Templates
Semicolons
Thomas Kluyver -
Show More
@@ -1,645 +1,645 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'codemirror/lib/codemirror',
8 8 'es6promise',
9 9 ], function(IPython, $, CodeMirror){
10 10 "use strict";
11 11
12 12 IPython.load_extensions = function () {
13 13 // load one or more IPython notebook extensions with requirejs
14 14
15 15 var extensions = [];
16 16 var extension_names = arguments;
17 17 for (var i = 0; i < extension_names.length; i++) {
18 18 extensions.push("nbextensions/" + arguments[i]);
19 19 }
20 20
21 21 require(extensions,
22 22 function () {
23 23 for (var i = 0; i < arguments.length; i++) {
24 24 var ext = arguments[i];
25 25 var ext_name = extension_names[i];
26 26 // success callback
27 27 console.log("Loaded extension: " + ext_name);
28 28 if (ext && ext.load_ipython_extension !== undefined) {
29 29 ext.load_ipython_extension();
30 30 }
31 31 }
32 32 },
33 33 function (err) {
34 34 // failure callback
35 35 console.log("Failed to load extension(s):", err.requireModules, err);
36 36 }
37 37 );
38 38 };
39 39
40 40 //============================================================================
41 41 // Cross-browser RegEx Split
42 42 //============================================================================
43 43
44 44 // This code has been MODIFIED from the code licensed below to not replace the
45 45 // default browser split. The license is reproduced here.
46 46
47 47 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
48 48 /*!
49 49 * Cross-Browser Split 1.1.1
50 50 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
51 51 * Available under the MIT License
52 52 * ECMAScript compliant, uniform cross-browser split method
53 53 */
54 54
55 55 /**
56 56 * Splits a string into an array of strings using a regex or string
57 57 * separator. Matches of the separator are not included in the result array.
58 58 * However, if `separator` is a regex that contains capturing groups,
59 59 * backreferences are spliced into the result each time `separator` is
60 60 * matched. Fixes browser bugs compared to the native
61 61 * `String.prototype.split` and can be used reliably cross-browser.
62 62 * @param {String} str String to split.
63 63 * @param {RegExp|String} separator Regex or string to use for separating
64 64 * the string.
65 65 * @param {Number} [limit] Maximum number of items to include in the result
66 66 * array.
67 67 * @returns {Array} Array of substrings.
68 68 * @example
69 69 *
70 70 * // Basic use
71 71 * regex_split('a b c d', ' ');
72 72 * // -> ['a', 'b', 'c', 'd']
73 73 *
74 74 * // With limit
75 75 * regex_split('a b c d', ' ', 2);
76 76 * // -> ['a', 'b']
77 77 *
78 78 * // Backreferences in result array
79 79 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
80 80 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
81 81 */
82 82 var regex_split = function (str, separator, limit) {
83 83 // If `separator` is not a regex, use `split`
84 84 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
85 85 return split.call(str, separator, limit);
86 86 }
87 87 var output = [],
88 88 flags = (separator.ignoreCase ? "i" : "") +
89 89 (separator.multiline ? "m" : "") +
90 90 (separator.extended ? "x" : "") + // Proposed for ES6
91 91 (separator.sticky ? "y" : ""), // Firefox 3+
92 92 lastLastIndex = 0,
93 93 // Make `global` and avoid `lastIndex` issues by working with a copy
94 94 separator = new RegExp(separator.source, flags + "g"),
95 95 separator2, match, lastIndex, lastLength;
96 96 str += ""; // Type-convert
97 97
98 98 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
99 99 if (!compliantExecNpcg) {
100 100 // Doesn't need flags gy, but they don't hurt
101 101 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
102 102 }
103 103 /* Values for `limit`, per the spec:
104 104 * If undefined: 4294967295 // Math.pow(2, 32) - 1
105 105 * If 0, Infinity, or NaN: 0
106 106 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
107 107 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
108 108 * If other: Type-convert, then use the above rules
109 109 */
110 110 limit = typeof(limit) === "undefined" ?
111 111 -1 >>> 0 : // Math.pow(2, 32) - 1
112 112 limit >>> 0; // ToUint32(limit)
113 113 while (match = separator.exec(str)) {
114 114 // `separator.lastIndex` is not reliable cross-browser
115 115 lastIndex = match.index + match[0].length;
116 116 if (lastIndex > lastLastIndex) {
117 117 output.push(str.slice(lastLastIndex, match.index));
118 118 // Fix browsers whose `exec` methods don't consistently return `undefined` for
119 119 // nonparticipating capturing groups
120 120 if (!compliantExecNpcg && match.length > 1) {
121 121 match[0].replace(separator2, function () {
122 122 for (var i = 1; i < arguments.length - 2; i++) {
123 123 if (typeof(arguments[i]) === "undefined") {
124 124 match[i] = undefined;
125 125 }
126 126 }
127 127 });
128 128 }
129 129 if (match.length > 1 && match.index < str.length) {
130 130 Array.prototype.push.apply(output, match.slice(1));
131 131 }
132 132 lastLength = match[0].length;
133 133 lastLastIndex = lastIndex;
134 134 if (output.length >= limit) {
135 135 break;
136 136 }
137 137 }
138 138 if (separator.lastIndex === match.index) {
139 139 separator.lastIndex++; // Avoid an infinite loop
140 140 }
141 141 }
142 142 if (lastLastIndex === str.length) {
143 143 if (lastLength || !separator.test("")) {
144 144 output.push("");
145 145 }
146 146 } else {
147 147 output.push(str.slice(lastLastIndex));
148 148 }
149 149 return output.length > limit ? output.slice(0, limit) : output;
150 150 };
151 151
152 152 //============================================================================
153 153 // End contributed Cross-browser RegEx Split
154 154 //============================================================================
155 155
156 156
157 157 var uuid = function () {
158 158 // http://www.ietf.org/rfc/rfc4122.txt
159 159 var s = [];
160 160 var hexDigits = "0123456789ABCDEF";
161 161 for (var i = 0; i < 32; i++) {
162 162 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
163 163 }
164 164 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
165 165 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
166 166
167 167 var uuid = s.join("");
168 168 return uuid;
169 169 };
170 170
171 171
172 172 //Fix raw text to parse correctly in crazy XML
173 173 function xmlencode(string) {
174 174 return string.replace(/\&/g,'&'+'amp;')
175 175 .replace(/</g,'&'+'lt;')
176 176 .replace(/>/g,'&'+'gt;')
177 177 .replace(/\'/g,'&'+'apos;')
178 178 .replace(/\"/g,'&'+'quot;')
179 179 .replace(/`/g,'&'+'#96;');
180 180 }
181 181
182 182
183 183 //Map from terminal commands to CSS classes
184 184 var ansi_colormap = {
185 185 "01":"ansibold",
186 186
187 187 "30":"ansiblack",
188 188 "31":"ansired",
189 189 "32":"ansigreen",
190 190 "33":"ansiyellow",
191 191 "34":"ansiblue",
192 192 "35":"ansipurple",
193 193 "36":"ansicyan",
194 194 "37":"ansigray",
195 195
196 196 "40":"ansibgblack",
197 197 "41":"ansibgred",
198 198 "42":"ansibggreen",
199 199 "43":"ansibgyellow",
200 200 "44":"ansibgblue",
201 201 "45":"ansibgpurple",
202 202 "46":"ansibgcyan",
203 203 "47":"ansibggray"
204 204 };
205 205
206 206 function _process_numbers(attrs, numbers) {
207 207 // process ansi escapes
208 208 var n = numbers.shift();
209 209 if (ansi_colormap[n]) {
210 210 if ( ! attrs["class"] ) {
211 211 attrs["class"] = ansi_colormap[n];
212 212 } else {
213 213 attrs["class"] += " " + ansi_colormap[n];
214 214 }
215 215 } else if (n == "38" || n == "48") {
216 216 // VT100 256 color or 24 bit RGB
217 217 if (numbers.length < 2) {
218 218 console.log("Not enough fields for VT100 color", numbers);
219 219 return;
220 220 }
221 221
222 222 var index_or_rgb = numbers.shift();
223 223 var r,g,b;
224 224 if (index_or_rgb == "5") {
225 225 // 256 color
226 226 var idx = parseInt(numbers.shift());
227 227 if (idx < 16) {
228 228 // indexed ANSI
229 229 // ignore bright / non-bright distinction
230 230 idx = idx % 8;
231 231 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
232 232 if ( ! attrs["class"] ) {
233 233 attrs["class"] = ansiclass;
234 234 } else {
235 235 attrs["class"] += " " + ansiclass;
236 236 }
237 237 return;
238 238 } else if (idx < 232) {
239 239 // 216 color 6x6x6 RGB
240 240 idx = idx - 16;
241 241 b = idx % 6;
242 242 g = Math.floor(idx / 6) % 6;
243 243 r = Math.floor(idx / 36) % 6;
244 244 // convert to rgb
245 245 r = (r * 51);
246 246 g = (g * 51);
247 247 b = (b * 51);
248 248 } else {
249 249 // grayscale
250 250 idx = idx - 231;
251 251 // it's 1-24 and should *not* include black or white,
252 252 // so a 26 point scale
253 253 r = g = b = Math.floor(idx * 256 / 26);
254 254 }
255 255 } else if (index_or_rgb == "2") {
256 256 // Simple 24 bit RGB
257 257 if (numbers.length > 3) {
258 258 console.log("Not enough fields for RGB", numbers);
259 259 return;
260 260 }
261 261 r = numbers.shift();
262 262 g = numbers.shift();
263 263 b = numbers.shift();
264 264 } else {
265 265 console.log("unrecognized control", numbers);
266 266 return;
267 267 }
268 268 if (r !== undefined) {
269 269 // apply the rgb color
270 270 var line;
271 271 if (n == "38") {
272 272 line = "color: ";
273 273 } else {
274 274 line = "background-color: ";
275 275 }
276 276 line = line + "rgb(" + r + "," + g + "," + b + ");";
277 277 if ( !attrs.style ) {
278 278 attrs.style = line;
279 279 } else {
280 280 attrs.style += " " + line;
281 281 }
282 282 }
283 283 }
284 284 }
285 285
286 286 function ansispan(str) {
287 287 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
288 288 // regular ansi escapes (using the table above)
289 289 var is_open = false;
290 290 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
291 291 if (!pattern) {
292 292 // [(01|22|39|)m close spans
293 293 if (is_open) {
294 294 is_open = false;
295 295 return "</span>";
296 296 } else {
297 297 return "";
298 298 }
299 299 } else {
300 300 is_open = true;
301 301
302 302 // consume sequence of color escapes
303 303 var numbers = pattern.match(/\d+/g);
304 304 var attrs = {};
305 305 while (numbers.length > 0) {
306 306 _process_numbers(attrs, numbers);
307 307 }
308 308
309 309 var span = "<span ";
310 310 for (var attr in attrs) {
311 311 var value = attrs[attr];
312 312 span = span + " " + attr + '="' + attrs[attr] + '"';
313 313 }
314 314 return span + ">";
315 315 }
316 316 });
317 317 }
318 318
319 319 // Transform ANSI color escape codes into HTML <span> tags with css
320 320 // classes listed in the above ansi_colormap object. The actual color used
321 321 // are set in the css file.
322 322 function fixConsole(txt) {
323 323 txt = xmlencode(txt);
324 324 var re = /\033\[([\dA-Fa-f;]*?)m/;
325 325 var opened = false;
326 326 var cmds = [];
327 327 var opener = "";
328 328 var closer = "";
329 329
330 330 // Strip all ANSI codes that are not color related. Matches
331 331 // all ANSI codes that do not end with "m".
332 332 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
333 333 txt = txt.replace(ignored_re, "");
334 334
335 335 // color ansi codes
336 336 txt = ansispan(txt);
337 337 return txt;
338 338 }
339 339
340 340 // Remove chunks that should be overridden by the effect of
341 341 // carriage return characters
342 342 function fixCarriageReturn(txt) {
343 343 var tmp = txt;
344 344 do {
345 345 txt = tmp;
346 346 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
347 347 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
348 348 } while (tmp.length < txt.length);
349 349 return txt;
350 350 }
351 351
352 352 // Locate any URLs and convert them to a anchor tag
353 353 function autoLinkUrls(txt) {
354 354 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
355 355 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
356 356 }
357 357
358 358 var points_to_pixels = function (points) {
359 359 // A reasonably good way of converting between points and pixels.
360 360 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
361 361 $(body).append(test);
362 362 var pixel_per_point = test.width()/10000;
363 363 test.remove();
364 364 return Math.floor(points*pixel_per_point);
365 365 };
366 366
367 367 var always_new = function (constructor) {
368 368 // wrapper around contructor to avoid requiring `var a = new constructor()`
369 369 // useful for passing constructors as callbacks,
370 370 // not for programmer laziness.
371 371 // from http://programmers.stackexchange.com/questions/118798
372 372 return function () {
373 373 var obj = Object.create(constructor.prototype);
374 374 constructor.apply(obj, arguments);
375 375 return obj;
376 376 };
377 377 };
378 378
379 379 var url_path_join = function () {
380 380 // join a sequence of url components with '/'
381 381 var url = '';
382 382 for (var i = 0; i < arguments.length; i++) {
383 383 if (arguments[i] === '') {
384 384 continue;
385 385 }
386 386 if (url.length > 0 && url[url.length-1] != '/') {
387 387 url = url + '/' + arguments[i];
388 388 } else {
389 389 url = url + arguments[i];
390 390 }
391 391 }
392 392 url = url.replace(/\/\/+/, '/');
393 393 return url;
394 394 };
395 395
396 396 var url_path_split = function (path) {
397 397 // Like os.path.split for URLs.
398 398 // Always returns two strings, the directory path and the base filename
399 399
400 400 var idx = path.lastIndexOf('/');
401 401 if (idx === -1) {
402 402 return ['', path];
403 403 } else {
404 404 return [ path.slice(0, idx), path.slice(idx + 1) ];
405 405 }
406 406 };
407 407
408 408 var parse_url = function (url) {
409 409 // an `a` element with an href allows attr-access to the parsed segments of a URL
410 410 // a = parse_url("http://localhost:8888/path/name#hash")
411 411 // a.protocol = "http:"
412 412 // a.host = "localhost:8888"
413 413 // a.hostname = "localhost"
414 414 // a.port = 8888
415 415 // a.pathname = "/path/name"
416 416 // a.hash = "#hash"
417 417 var a = document.createElement("a");
418 418 a.href = url;
419 419 return a;
420 420 };
421 421
422 422 var encode_uri_components = function (uri) {
423 423 // encode just the components of a multi-segment uri,
424 424 // leaving '/' separators
425 425 return uri.split('/').map(encodeURIComponent).join('/');
426 426 };
427 427
428 428 var url_join_encode = function () {
429 429 // join a sequence of url components with '/',
430 430 // encoding each component with encodeURIComponent
431 431 return encode_uri_components(url_path_join.apply(null, arguments));
432 432 };
433 433
434 434
435 435 var splitext = function (filename) {
436 436 // mimic Python os.path.splitext
437 437 // Returns ['base', '.ext']
438 438 var idx = filename.lastIndexOf('.');
439 439 if (idx > 0) {
440 440 return [filename.slice(0, idx), filename.slice(idx)];
441 441 } else {
442 442 return [filename, ''];
443 443 }
444 444 };
445 445
446 446
447 447 var escape_html = function (text) {
448 448 // escape text to HTML
449 449 return $("<div/>").text(text).html();
450 450 };
451 451
452 452
453 453 var get_body_data = function(key) {
454 454 // get a url-encoded item from body.data and decode it
455 455 // we should never have any encoded URLs anywhere else in code
456 456 // until we are building an actual request
457 457 return decodeURIComponent($('body').data(key));
458 458 };
459 459
460 460 var to_absolute_cursor_pos = function (cm, cursor) {
461 461 // get the absolute cursor position from CodeMirror's col, ch
462 462 if (!cursor) {
463 463 cursor = cm.getCursor();
464 464 }
465 465 var cursor_pos = cursor.ch;
466 466 for (var i = 0; i < cursor.line; i++) {
467 467 cursor_pos += cm.getLine(i).length + 1;
468 468 }
469 469 return cursor_pos;
470 470 };
471 471
472 472 var from_absolute_cursor_pos = function (cm, cursor_pos) {
473 473 // turn absolute cursor postion into CodeMirror col, ch cursor
474 474 var i, line;
475 475 var offset = 0;
476 476 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
477 477 if (offset + line.length < cursor_pos) {
478 478 offset += line.length + 1;
479 479 } else {
480 480 return {
481 481 line : i,
482 482 ch : cursor_pos - offset,
483 483 };
484 484 }
485 485 }
486 486 // reached end, return endpoint
487 487 return {
488 488 ch : line.length - 1,
489 489 line : i - 1,
490 490 };
491 491 };
492 492
493 493 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
494 494 var browser = (function() {
495 495 if (typeof navigator === 'undefined') {
496 496 // navigator undefined in node
497 497 return 'None';
498 498 }
499 499 var N= navigator.appName, ua= navigator.userAgent, tem;
500 500 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
501 501 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
502 502 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
503 503 return M;
504 504 })();
505 505
506 506 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
507 507 var platform = (function () {
508 508 if (typeof navigator === 'undefined') {
509 509 // navigator undefined in node
510 510 return 'None';
511 511 }
512 512 var OSName="None";
513 513 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
514 514 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
515 515 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
516 516 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
517 517 return OSName;
518 518 })();
519 519
520 520 var is_or_has = function (a, b) {
521 521 // Is b a child of a or a itself?
522 522 return a.has(b).length !==0 || a.is(b);
523 523 };
524 524
525 525 var is_focused = function (e) {
526 526 // Is element e, or one of its children focused?
527 527 e = $(e);
528 528 var target = $(document.activeElement);
529 529 if (target.length > 0) {
530 530 if (is_or_has(e, target)) {
531 531 return true;
532 532 } else {
533 533 return false;
534 534 }
535 535 } else {
536 536 return false;
537 537 }
538 538 };
539 539
540 540 var mergeopt = function(_class, options, overwrite){
541 541 options = options || {};
542 542 overwrite = overwrite || {};
543 543 return $.extend(true, {}, _class.options_default, options, overwrite);
544 544 };
545 545
546 546 var ajax_error_msg = function (jqXHR) {
547 547 // Return a JSON error message if there is one,
548 548 // otherwise the basic HTTP status text.
549 549 if (jqXHR.responseJSON && jqXHR.responseJSON.traceback) {
550 550 return jqXHR.responseJSON.traceback;
551 551 } else if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
552 552 return jqXHR.responseJSON.message;
553 553 } else {
554 554 return jqXHR.statusText;
555 555 }
556 556 };
557 557 var log_ajax_error = function (jqXHR, status, error) {
558 558 // log ajax failures with informative messages
559 559 var msg = "API request failed (" + jqXHR.status + "): ";
560 560 console.log(jqXHR);
561 561 msg += ajax_error_msg(jqXHR);
562 562 console.log(msg);
563 563 };
564 564
565 565 var requireCodeMirrorMode = function (mode, callback, errback) {
566 566 // load a mode with requirejs
567 567 if (typeof mode != "string") mode = mode.name;
568 568 if (CodeMirror.modes.hasOwnProperty(mode)) {
569 569 callback(CodeMirror.modes.mode);
570 570 return;
571 571 }
572 572 require([
573 573 // might want to use CodeMirror.modeURL here
574 574 ['codemirror/mode', mode, mode].join('/'),
575 575 ], callback, errback
576 576 );
577 577 };
578 578
579 579 /** Error type for wrapped XHR errors. */
580 580 var XHR_ERROR = 'XhrError';
581 581
582 582 /**
583 583 * Wraps an AJAX error as an Error object.
584 584 */
585 585 var wrap_ajax_error = function (jqXHR, status, error) {
586 586 var wrapped_error = new Error(ajax_error_msg(jqXHR));
587 587 wrapped_error.name = XHR_ERROR;
588 588 // provide xhr response
589 589 wrapped_error.xhr = jqXHR;
590 590 wrapped_error.xhr_status = status;
591 591 wrapped_error.xhr_error = error;
592 592 return wrapped_error;
593 593 };
594 594
595 595 var promising_ajax = function(url, settings) {
596 596 // Like $.ajax, but returning an ES6 promise. success and error settings
597 597 // will be ignored.
598 598 return new Promise(function(resolve, reject) {
599 599 settings.success = function(data, status, jqXHR) {
600 600 resolve(data);
601 }
601 };
602 602 settings.error = function(jqXHR, status, error) {
603 603 log_ajax_error(jqXHR, status, error);
604 604 reject(wrap_ajax_error(jqXHR, status, error));
605 }
605 };
606 606 $.ajax(url, settings);
607 607 });
608 608 };
609 609
610 610 var utils = {
611 611 regex_split : regex_split,
612 612 uuid : uuid,
613 613 fixConsole : fixConsole,
614 614 fixCarriageReturn : fixCarriageReturn,
615 615 autoLinkUrls : autoLinkUrls,
616 616 points_to_pixels : points_to_pixels,
617 617 get_body_data : get_body_data,
618 618 parse_url : parse_url,
619 619 url_path_split : url_path_split,
620 620 url_path_join : url_path_join,
621 621 url_join_encode : url_join_encode,
622 622 encode_uri_components : encode_uri_components,
623 623 splitext : splitext,
624 624 escape_html : escape_html,
625 625 always_new : always_new,
626 626 to_absolute_cursor_pos : to_absolute_cursor_pos,
627 627 from_absolute_cursor_pos : from_absolute_cursor_pos,
628 628 browser : browser,
629 629 platform: platform,
630 630 is_or_has : is_or_has,
631 631 is_focused : is_focused,
632 632 mergeopt: mergeopt,
633 633 ajax_error_msg : ajax_error_msg,
634 634 log_ajax_error : log_ajax_error,
635 635 requireCodeMirrorMode : requireCodeMirrorMode,
636 636 XHR_ERROR : XHR_ERROR,
637 637 wrap_ajax_error : wrap_ajax_error,
638 638 promising_ajax : promising_ajax,
639 639 };
640 640
641 641 // Backwards compatability.
642 642 IPython.utils = utils;
643 643
644 644 return utils;
645 645 });
General Comments 0
You need to be logged in to leave comments. Login now