##// END OF EJS Templates
utility functions + tests for shift- & ctrl-enter
Paul Ivanov -
Show More
@@ -1,435 +1,447 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Utilities
10 10 //============================================================================
11 11 IPython.namespace('IPython.utils');
12 12
13 13 IPython.utils = (function (IPython) {
14 14 "use strict";
15 15
16 16 //============================================================================
17 17 // Cross-browser RegEx Split
18 18 //============================================================================
19 19
20 20 // This code has been MODIFIED from the code licensed below to not replace the
21 21 // default browser split. The license is reproduced here.
22 22
23 23 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
24 24 /*!
25 25 * Cross-Browser Split 1.1.1
26 26 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
27 27 * Available under the MIT License
28 28 * ECMAScript compliant, uniform cross-browser split method
29 29 */
30 30
31 31 /**
32 32 * Splits a string into an array of strings using a regex or string
33 33 * separator. Matches of the separator are not included in the result array.
34 34 * However, if `separator` is a regex that contains capturing groups,
35 35 * backreferences are spliced into the result each time `separator` is
36 36 * matched. Fixes browser bugs compared to the native
37 37 * `String.prototype.split` and can be used reliably cross-browser.
38 38 * @param {String} str String to split.
39 39 * @param {RegExp|String} separator Regex or string to use for separating
40 40 * the string.
41 41 * @param {Number} [limit] Maximum number of items to include in the result
42 42 * array.
43 43 * @returns {Array} Array of substrings.
44 44 * @example
45 45 *
46 46 * // Basic use
47 47 * regex_split('a b c d', ' ');
48 48 * // -> ['a', 'b', 'c', 'd']
49 49 *
50 50 * // With limit
51 51 * regex_split('a b c d', ' ', 2);
52 52 * // -> ['a', 'b']
53 53 *
54 54 * // Backreferences in result array
55 55 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
56 56 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
57 57 */
58 58 var regex_split = function (str, separator, limit) {
59 59 // If `separator` is not a regex, use `split`
60 60 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
61 61 return split.call(str, separator, limit);
62 62 }
63 63 var output = [],
64 64 flags = (separator.ignoreCase ? "i" : "") +
65 65 (separator.multiline ? "m" : "") +
66 66 (separator.extended ? "x" : "") + // Proposed for ES6
67 67 (separator.sticky ? "y" : ""), // Firefox 3+
68 68 lastLastIndex = 0,
69 69 // Make `global` and avoid `lastIndex` issues by working with a copy
70 70 separator = new RegExp(separator.source, flags + "g"),
71 71 separator2, match, lastIndex, lastLength;
72 72 str += ""; // Type-convert
73 73
74 74 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
75 75 if (!compliantExecNpcg) {
76 76 // Doesn't need flags gy, but they don't hurt
77 77 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
78 78 }
79 79 /* Values for `limit`, per the spec:
80 80 * If undefined: 4294967295 // Math.pow(2, 32) - 1
81 81 * If 0, Infinity, or NaN: 0
82 82 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
83 83 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
84 84 * If other: Type-convert, then use the above rules
85 85 */
86 86 limit = typeof(limit) === "undefined" ?
87 87 -1 >>> 0 : // Math.pow(2, 32) - 1
88 88 limit >>> 0; // ToUint32(limit)
89 89 while (match = separator.exec(str)) {
90 90 // `separator.lastIndex` is not reliable cross-browser
91 91 lastIndex = match.index + match[0].length;
92 92 if (lastIndex > lastLastIndex) {
93 93 output.push(str.slice(lastLastIndex, match.index));
94 94 // Fix browsers whose `exec` methods don't consistently return `undefined` for
95 95 // nonparticipating capturing groups
96 96 if (!compliantExecNpcg && match.length > 1) {
97 97 match[0].replace(separator2, function () {
98 98 for (var i = 1; i < arguments.length - 2; i++) {
99 99 if (typeof(arguments[i]) === "undefined") {
100 100 match[i] = undefined;
101 101 }
102 102 }
103 103 });
104 104 }
105 105 if (match.length > 1 && match.index < str.length) {
106 106 Array.prototype.push.apply(output, match.slice(1));
107 107 }
108 108 lastLength = match[0].length;
109 109 lastLastIndex = lastIndex;
110 110 if (output.length >= limit) {
111 111 break;
112 112 }
113 113 }
114 114 if (separator.lastIndex === match.index) {
115 115 separator.lastIndex++; // Avoid an infinite loop
116 116 }
117 117 }
118 118 if (lastLastIndex === str.length) {
119 119 if (lastLength || !separator.test("")) {
120 120 output.push("");
121 121 }
122 122 } else {
123 123 output.push(str.slice(lastLastIndex));
124 124 }
125 125 return output.length > limit ? output.slice(0, limit) : output;
126 126 };
127 127
128 128 //============================================================================
129 129 // End contributed Cross-browser RegEx Split
130 130 //============================================================================
131 131
132 132
133 133 var uuid = function () {
134 134 // http://www.ietf.org/rfc/rfc4122.txt
135 135 var s = [];
136 136 var hexDigits = "0123456789ABCDEF";
137 137 for (var i = 0; i < 32; i++) {
138 138 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
139 139 }
140 140 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
141 141 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
142 142
143 143 var uuid = s.join("");
144 144 return uuid;
145 145 };
146 146
147 147
148 148 //Fix raw text to parse correctly in crazy XML
149 149 function xmlencode(string) {
150 150 return string.replace(/\&/g,'&'+'amp;')
151 151 .replace(/</g,'&'+'lt;')
152 152 .replace(/>/g,'&'+'gt;')
153 153 .replace(/\'/g,'&'+'apos;')
154 154 .replace(/\"/g,'&'+'quot;')
155 155 .replace(/`/g,'&'+'#96;');
156 156 }
157 157
158 158
159 159 //Map from terminal commands to CSS classes
160 160 var ansi_colormap = {
161 161 "01":"ansibold",
162 162
163 163 "30":"ansiblack",
164 164 "31":"ansired",
165 165 "32":"ansigreen",
166 166 "33":"ansiyellow",
167 167 "34":"ansiblue",
168 168 "35":"ansipurple",
169 169 "36":"ansicyan",
170 170 "37":"ansigray",
171 171
172 172 "40":"ansibgblack",
173 173 "41":"ansibgred",
174 174 "42":"ansibggreen",
175 175 "43":"ansibgyellow",
176 176 "44":"ansibgblue",
177 177 "45":"ansibgpurple",
178 178 "46":"ansibgcyan",
179 179 "47":"ansibggray"
180 180 };
181 181
182 182 function _process_numbers(attrs, numbers) {
183 183 // process ansi escapes
184 184 var n = numbers.shift();
185 185 if (ansi_colormap[n]) {
186 186 if ( ! attrs["class"] ) {
187 187 attrs["class"] = ansi_colormap[n];
188 188 } else {
189 189 attrs["class"] += " " + ansi_colormap[n];
190 190 }
191 191 } else if (n == "38" || n == "48") {
192 192 // VT100 256 color or 24 bit RGB
193 193 if (numbers.length < 2) {
194 194 console.log("Not enough fields for VT100 color", numbers);
195 195 return;
196 196 }
197 197
198 198 var index_or_rgb = numbers.shift();
199 199 var r,g,b;
200 200 if (index_or_rgb == "5") {
201 201 // 256 color
202 202 var idx = parseInt(numbers.shift());
203 203 if (idx < 16) {
204 204 // indexed ANSI
205 205 // ignore bright / non-bright distinction
206 206 idx = idx % 8;
207 207 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
208 208 if ( ! attrs["class"] ) {
209 209 attrs["class"] = ansiclass;
210 210 } else {
211 211 attrs["class"] += " " + ansiclass;
212 212 }
213 213 return;
214 214 } else if (idx < 232) {
215 215 // 216 color 6x6x6 RGB
216 216 idx = idx - 16;
217 217 b = idx % 6;
218 218 g = Math.floor(idx / 6) % 6;
219 219 r = Math.floor(idx / 36) % 6;
220 220 // convert to rgb
221 221 r = (r * 51);
222 222 g = (g * 51);
223 223 b = (b * 51);
224 224 } else {
225 225 // grayscale
226 226 idx = idx - 231;
227 227 // it's 1-24 and should *not* include black or white,
228 228 // so a 26 point scale
229 229 r = g = b = Math.floor(idx * 256 / 26);
230 230 }
231 231 } else if (index_or_rgb == "2") {
232 232 // Simple 24 bit RGB
233 233 if (numbers.length > 3) {
234 234 console.log("Not enough fields for RGB", numbers);
235 235 return;
236 236 }
237 237 r = numbers.shift();
238 238 g = numbers.shift();
239 239 b = numbers.shift();
240 240 } else {
241 241 console.log("unrecognized control", numbers);
242 242 return;
243 243 }
244 244 if (r !== undefined) {
245 245 // apply the rgb color
246 246 var line;
247 247 if (n == "38") {
248 248 line = "color: ";
249 249 } else {
250 250 line = "background-color: ";
251 251 }
252 252 line = line + "rgb(" + r + "," + g + "," + b + ");"
253 253 if ( !attrs["style"] ) {
254 254 attrs["style"] = line;
255 255 } else {
256 256 attrs["style"] += " " + line;
257 257 }
258 258 }
259 259 }
260 260 }
261 261
262 262 function ansispan(str) {
263 263 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
264 264 // regular ansi escapes (using the table above)
265 265 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
266 266 if (!pattern) {
267 267 // [(01|22|39|)m close spans
268 268 return "</span>";
269 269 }
270 270 // consume sequence of color escapes
271 271 var numbers = pattern.match(/\d+/g);
272 272 var attrs = {};
273 273 while (numbers.length > 0) {
274 274 _process_numbers(attrs, numbers);
275 275 }
276 276
277 277 var span = "<span ";
278 278 for (var attr in attrs) {
279 279 var value = attrs[attr];
280 280 span = span + " " + attr + '="' + attrs[attr] + '"';
281 281 }
282 282 return span + ">";
283 283 });
284 284 };
285 285
286 286 // Transform ANSI color escape codes into HTML <span> tags with css
287 287 // classes listed in the above ansi_colormap object. The actual color used
288 288 // are set in the css file.
289 289 function fixConsole(txt) {
290 290 txt = xmlencode(txt);
291 291 var re = /\033\[([\dA-Fa-f;]*?)m/;
292 292 var opened = false;
293 293 var cmds = [];
294 294 var opener = "";
295 295 var closer = "";
296 296
297 297 // Strip all ANSI codes that are not color related. Matches
298 298 // all ANSI codes that do not end with "m".
299 299 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
300 300 txt = txt.replace(ignored_re, "");
301 301
302 302 // color ansi codes
303 303 txt = ansispan(txt);
304 304 return txt;
305 305 }
306 306
307 307 // Remove chunks that should be overridden by the effect of
308 308 // carriage return characters
309 309 function fixCarriageReturn(txt) {
310 310 var tmp = txt;
311 311 do {
312 312 txt = tmp;
313 313 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
314 314 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
315 315 } while (tmp.length < txt.length);
316 316 return txt;
317 317 }
318 318
319 319 // Locate any URLs and convert them to a anchor tag
320 320 function autoLinkUrls(txt) {
321 321 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
322 322 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
323 323 }
324 324
325 325 // some keycodes that seem to be platform/browser independent
326 326 var keycodes = {
327 327 BACKSPACE: 8,
328 328 TAB : 9,
329 329 ENTER : 13,
330 330 SHIFT : 16,
331 331 CTRL : 17,
332 332 CONTROL : 17,
333 333 ALT : 18,
334 334 CAPS_LOCK: 20,
335 335 ESC : 27,
336 336 SPACE : 32,
337 337 PGUP : 33,
338 338 PGDOWN : 34,
339 339 END : 35,
340 340 HOME : 36,
341 341 LEFT_ARROW: 37,
342 342 LEFTARROW: 37,
343 343 LEFT : 37,
344 344 UP_ARROW : 38,
345 345 UPARROW : 38,
346 346 UP : 38,
347 347 RIGHT_ARROW:39,
348 348 RIGHTARROW:39,
349 349 RIGHT : 39,
350 350 DOWN_ARROW: 40,
351 351 DOWNARROW: 40,
352 352 DOWN : 40,
353 353 // all three of these keys may be COMMAND on OS X:
354 354 LEFT_SUPER : 91,
355 355 RIGHT_SUPER : 92,
356 356 COMMAND : 93,
357 357 };
358 358
359 359 // trigger a key press event
360 360 var press = function (key) {
361 361 var key_press = $.Event('keydown', {which: key});
362 362 $(document).trigger(key_press);
363 363 }
364 364
365 365 var press_up = function() { press(keycodes.UP); };
366 366 var press_down = function() { press(keycodes.DOWN); };
367 367
368 var press_ctrl_enter = function() {
369 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, ctrlKey: true}));
370 };
371
372 var press_shift_enter = function() {
373 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, shiftKey: true}));
374 };
375 // trigger the ctrl-m shortcut followed by one of our keys
376 var press_ghetto = function(key) { presspress(key); };
377
368 378
369 379 var points_to_pixels = function (points) {
370 380 // A reasonably good way of converting between points and pixels.
371 381 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
372 382 $(body).append(test);
373 383 var pixel_per_point = test.width()/10000;
374 384 test.remove();
375 385 return Math.floor(points*pixel_per_point);
376 386 };
377 387
378 388
379 389 var url_path_join = function () {
380 390 // join a sequence of url components with '/'
381 391 var url = '';
382 392 for (var i = 0; i < arguments.length; i++) {
383 393 if (arguments[i] === '') {
384 394 continue;
385 395 }
386 396 if (url.length > 0 && url[url.length-1] != '/') {
387 397 url = url + '/' + arguments[i];
388 398 } else {
389 399 url = url + arguments[i];
390 400 }
391 401 }
392 402 return url;
393 403 };
394 404
395 405
396 406 var splitext = function (filename) {
397 407 // mimic Python os.path.splitext
398 408 // Returns ['base', '.ext']
399 409 var idx = filename.lastIndexOf('.');
400 410 if (idx > 0) {
401 411 return [filename.slice(0, idx), filename.slice(idx)];
402 412 } else {
403 413 return [filename, ''];
404 414 }
405 415 }
406 416
407 417
408 418 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
409 419 var browser = (function() {
410 420 var N= navigator.appName, ua= navigator.userAgent, tem;
411 421 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
412 422 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
413 423 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
414 424 return M;
415 425 })();
416 426
417 427
418 428 return {
419 429 regex_split : regex_split,
420 430 uuid : uuid,
421 431 fixConsole : fixConsole,
422 432 keycodes : keycodes,
423 433 press : press,
424 434 press_up : press_up,
425 435 press_down : press_down,
436 press_ctrl_enter : press_ctrl_enter,
437 press_shift_enter : press_shift_enter,
426 438 fixCarriageReturn : fixCarriageReturn,
427 439 autoLinkUrls : autoLinkUrls,
428 440 points_to_pixels : points_to_pixels,
429 441 url_path_join : url_path_join,
430 442 splitext : splitext,
431 443 browser : browser
432 444 };
433 445
434 446 }(IPython));
435 447
@@ -1,51 +1,75 b''
1 1 //
2 2 // Test code cell execution.
3 3 //
4 4 casper.notebook_test(function () {
5 5 this.evaluate(function () {
6 6 var cell = IPython.notebook.get_cell(0);
7 7 cell.set_text('a=10; print(a)');
8 8 cell.execute();
9 9 });
10 10
11 11 this.waitFor(function () {
12 12 return this.evaluate(function get_output() {
13 13 var cell = IPython.notebook.get_cell(0);
14 14 return cell.output_area.outputs.length != 0;
15 15 })
16 16 });
17 17
18 18 this.then(function () {
19 19 var result = this.evaluate(function () {
20 20 var cell = IPython.notebook.get_cell(0);
21 21 var output = cell.output_area.outputs[0].text;
22 22 return output;
23 23 })
24 24 this.test.assertEquals(result, '10\n', 'cell execute (using js)')
25 25 });
26 26
27 27
28 28 // do it again with the keyboard shortcut
29 29 this.thenEvaluate(function () {
30 30 var cell = IPython.notebook.get_cell(0);
31 31 cell.set_text('a=11; print(a)');
32 32 cell.clear_output()
33 $(document).trigger($.Event('keydown', {which: 13, ctrlKey: true}))
33 IPython.utils.press_ctrl_enter();
34 34 });
35 35
36 36 this.waitFor(function () {
37 37 return this.evaluate(function get_output() {
38 38 var cell = IPython.notebook.get_cell(0);
39 39 return cell.output_area.outputs.length != 0;
40 40 })
41 41 });
42 42
43 43 this.then(function () {
44 44 var result = this.evaluate(function () {
45 45 var cell = IPython.notebook.get_cell(0);
46 46 var output = cell.output_area.outputs[0].text;
47 47 return output;
48 48 })
49 49 this.test.assertEquals(result, '11\n', 'cell execute (using ctrl-enter)')
50 50 });
51
52 // do it again with the keyboard shortcut
53 this.thenEvaluate(function () {
54 var cell = IPython.notebook.get_cell(0);
55 cell.set_text('a=12; print(a)');
56 cell.clear_output()
57 IPython.utils.press_shift_enter();
58 });
59
60 this.waitFor(function () {
61 return this.evaluate(function get_output() {
62 var cell = IPython.notebook.get_cell(0);
63 return cell.output_area.outputs.length != 0;
64 })
65 });
66
67 this.then(function () {
68 var result = this.evaluate(function () {
69 var cell = IPython.notebook.get_cell(0);
70 var output = cell.output_area.outputs[0].text;
71 return output;
72 })
73 this.test.assertEquals(result, '12\n', 'cell execute (using shift-enter)')
74 });
51 75 });
General Comments 0
You need to be logged in to leave comments. Login now