##// END OF EJS Templates
Added platform dep. logic.
Brian E. Granger -
Show More
@@ -1,508 +1,519
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 //============================================================================
16 //============================================================================
17 // Cross-browser RegEx Split
17 // Cross-browser RegEx Split
18 //============================================================================
18 //============================================================================
19
19
20 // This code has been MODIFIED from the code licensed below to not replace the
20 // This code has been MODIFIED from the code licensed below to not replace the
21 // default browser split. The license is reproduced here.
21 // default browser split. The license is reproduced here.
22
22
23 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
23 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
24 /*!
24 /*!
25 * Cross-Browser Split 1.1.1
25 * Cross-Browser Split 1.1.1
26 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
26 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
27 * Available under the MIT License
27 * Available under the MIT License
28 * ECMAScript compliant, uniform cross-browser split method
28 * ECMAScript compliant, uniform cross-browser split method
29 */
29 */
30
30
31 /**
31 /**
32 * Splits a string into an array of strings using a regex or string
32 * Splits a string into an array of strings using a regex or string
33 * separator. Matches of the separator are not included in the result array.
33 * separator. Matches of the separator are not included in the result array.
34 * However, if `separator` is a regex that contains capturing groups,
34 * However, if `separator` is a regex that contains capturing groups,
35 * backreferences are spliced into the result each time `separator` is
35 * backreferences are spliced into the result each time `separator` is
36 * matched. Fixes browser bugs compared to the native
36 * matched. Fixes browser bugs compared to the native
37 * `String.prototype.split` and can be used reliably cross-browser.
37 * `String.prototype.split` and can be used reliably cross-browser.
38 * @param {String} str String to split.
38 * @param {String} str String to split.
39 * @param {RegExp|String} separator Regex or string to use for separating
39 * @param {RegExp|String} separator Regex or string to use for separating
40 * the string.
40 * the string.
41 * @param {Number} [limit] Maximum number of items to include in the result
41 * @param {Number} [limit] Maximum number of items to include in the result
42 * array.
42 * array.
43 * @returns {Array} Array of substrings.
43 * @returns {Array} Array of substrings.
44 * @example
44 * @example
45 *
45 *
46 * // Basic use
46 * // Basic use
47 * regex_split('a b c d', ' ');
47 * regex_split('a b c d', ' ');
48 * // -> ['a', 'b', 'c', 'd']
48 * // -> ['a', 'b', 'c', 'd']
49 *
49 *
50 * // With limit
50 * // With limit
51 * regex_split('a b c d', ' ', 2);
51 * regex_split('a b c d', ' ', 2);
52 * // -> ['a', 'b']
52 * // -> ['a', 'b']
53 *
53 *
54 * // Backreferences in result array
54 * // Backreferences in result array
55 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
55 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
56 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
56 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
57 */
57 */
58 var regex_split = function (str, separator, limit) {
58 var regex_split = function (str, separator, limit) {
59 // If `separator` is not a regex, use `split`
59 // If `separator` is not a regex, use `split`
60 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
60 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
61 return split.call(str, separator, limit);
61 return split.call(str, separator, limit);
62 }
62 }
63 var output = [],
63 var output = [],
64 flags = (separator.ignoreCase ? "i" : "") +
64 flags = (separator.ignoreCase ? "i" : "") +
65 (separator.multiline ? "m" : "") +
65 (separator.multiline ? "m" : "") +
66 (separator.extended ? "x" : "") + // Proposed for ES6
66 (separator.extended ? "x" : "") + // Proposed for ES6
67 (separator.sticky ? "y" : ""), // Firefox 3+
67 (separator.sticky ? "y" : ""), // Firefox 3+
68 lastLastIndex = 0,
68 lastLastIndex = 0,
69 // Make `global` and avoid `lastIndex` issues by working with a copy
69 // Make `global` and avoid `lastIndex` issues by working with a copy
70 separator = new RegExp(separator.source, flags + "g"),
70 separator = new RegExp(separator.source, flags + "g"),
71 separator2, match, lastIndex, lastLength;
71 separator2, match, lastIndex, lastLength;
72 str += ""; // Type-convert
72 str += ""; // Type-convert
73
73
74 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
74 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
75 if (!compliantExecNpcg) {
75 if (!compliantExecNpcg) {
76 // Doesn't need flags gy, but they don't hurt
76 // Doesn't need flags gy, but they don't hurt
77 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
77 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
78 }
78 }
79 /* Values for `limit`, per the spec:
79 /* Values for `limit`, per the spec:
80 * If undefined: 4294967295 // Math.pow(2, 32) - 1
80 * If undefined: 4294967295 // Math.pow(2, 32) - 1
81 * If 0, Infinity, or NaN: 0
81 * If 0, Infinity, or NaN: 0
82 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
82 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
83 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
83 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
84 * If other: Type-convert, then use the above rules
84 * If other: Type-convert, then use the above rules
85 */
85 */
86 limit = typeof(limit) === "undefined" ?
86 limit = typeof(limit) === "undefined" ?
87 -1 >>> 0 : // Math.pow(2, 32) - 1
87 -1 >>> 0 : // Math.pow(2, 32) - 1
88 limit >>> 0; // ToUint32(limit)
88 limit >>> 0; // ToUint32(limit)
89 while (match = separator.exec(str)) {
89 while (match = separator.exec(str)) {
90 // `separator.lastIndex` is not reliable cross-browser
90 // `separator.lastIndex` is not reliable cross-browser
91 lastIndex = match.index + match[0].length;
91 lastIndex = match.index + match[0].length;
92 if (lastIndex > lastLastIndex) {
92 if (lastIndex > lastLastIndex) {
93 output.push(str.slice(lastLastIndex, match.index));
93 output.push(str.slice(lastLastIndex, match.index));
94 // Fix browsers whose `exec` methods don't consistently return `undefined` for
94 // Fix browsers whose `exec` methods don't consistently return `undefined` for
95 // nonparticipating capturing groups
95 // nonparticipating capturing groups
96 if (!compliantExecNpcg && match.length > 1) {
96 if (!compliantExecNpcg && match.length > 1) {
97 match[0].replace(separator2, function () {
97 match[0].replace(separator2, function () {
98 for (var i = 1; i < arguments.length - 2; i++) {
98 for (var i = 1; i < arguments.length - 2; i++) {
99 if (typeof(arguments[i]) === "undefined") {
99 if (typeof(arguments[i]) === "undefined") {
100 match[i] = undefined;
100 match[i] = undefined;
101 }
101 }
102 }
102 }
103 });
103 });
104 }
104 }
105 if (match.length > 1 && match.index < str.length) {
105 if (match.length > 1 && match.index < str.length) {
106 Array.prototype.push.apply(output, match.slice(1));
106 Array.prototype.push.apply(output, match.slice(1));
107 }
107 }
108 lastLength = match[0].length;
108 lastLength = match[0].length;
109 lastLastIndex = lastIndex;
109 lastLastIndex = lastIndex;
110 if (output.length >= limit) {
110 if (output.length >= limit) {
111 break;
111 break;
112 }
112 }
113 }
113 }
114 if (separator.lastIndex === match.index) {
114 if (separator.lastIndex === match.index) {
115 separator.lastIndex++; // Avoid an infinite loop
115 separator.lastIndex++; // Avoid an infinite loop
116 }
116 }
117 }
117 }
118 if (lastLastIndex === str.length) {
118 if (lastLastIndex === str.length) {
119 if (lastLength || !separator.test("")) {
119 if (lastLength || !separator.test("")) {
120 output.push("");
120 output.push("");
121 }
121 }
122 } else {
122 } else {
123 output.push(str.slice(lastLastIndex));
123 output.push(str.slice(lastLastIndex));
124 }
124 }
125 return output.length > limit ? output.slice(0, limit) : output;
125 return output.length > limit ? output.slice(0, limit) : output;
126 };
126 };
127
127
128 //============================================================================
128 //============================================================================
129 // End contributed Cross-browser RegEx Split
129 // End contributed Cross-browser RegEx Split
130 //============================================================================
130 //============================================================================
131
131
132
132
133 var uuid = function () {
133 var uuid = function () {
134 // http://www.ietf.org/rfc/rfc4122.txt
134 // http://www.ietf.org/rfc/rfc4122.txt
135 var s = [];
135 var s = [];
136 var hexDigits = "0123456789ABCDEF";
136 var hexDigits = "0123456789ABCDEF";
137 for (var i = 0; i < 32; i++) {
137 for (var i = 0; i < 32; i++) {
138 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
138 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
139 }
139 }
140 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
140 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
141 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
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 var uuid = s.join("");
143 var uuid = s.join("");
144 return uuid;
144 return uuid;
145 };
145 };
146
146
147
147
148 //Fix raw text to parse correctly in crazy XML
148 //Fix raw text to parse correctly in crazy XML
149 function xmlencode(string) {
149 function xmlencode(string) {
150 return string.replace(/\&/g,'&'+'amp;')
150 return string.replace(/\&/g,'&'+'amp;')
151 .replace(/</g,'&'+'lt;')
151 .replace(/</g,'&'+'lt;')
152 .replace(/>/g,'&'+'gt;')
152 .replace(/>/g,'&'+'gt;')
153 .replace(/\'/g,'&'+'apos;')
153 .replace(/\'/g,'&'+'apos;')
154 .replace(/\"/g,'&'+'quot;')
154 .replace(/\"/g,'&'+'quot;')
155 .replace(/`/g,'&'+'#96;');
155 .replace(/`/g,'&'+'#96;');
156 }
156 }
157
157
158
158
159 //Map from terminal commands to CSS classes
159 //Map from terminal commands to CSS classes
160 var ansi_colormap = {
160 var ansi_colormap = {
161 "01":"ansibold",
161 "01":"ansibold",
162
162
163 "30":"ansiblack",
163 "30":"ansiblack",
164 "31":"ansired",
164 "31":"ansired",
165 "32":"ansigreen",
165 "32":"ansigreen",
166 "33":"ansiyellow",
166 "33":"ansiyellow",
167 "34":"ansiblue",
167 "34":"ansiblue",
168 "35":"ansipurple",
168 "35":"ansipurple",
169 "36":"ansicyan",
169 "36":"ansicyan",
170 "37":"ansigray",
170 "37":"ansigray",
171
171
172 "40":"ansibgblack",
172 "40":"ansibgblack",
173 "41":"ansibgred",
173 "41":"ansibgred",
174 "42":"ansibggreen",
174 "42":"ansibggreen",
175 "43":"ansibgyellow",
175 "43":"ansibgyellow",
176 "44":"ansibgblue",
176 "44":"ansibgblue",
177 "45":"ansibgpurple",
177 "45":"ansibgpurple",
178 "46":"ansibgcyan",
178 "46":"ansibgcyan",
179 "47":"ansibggray"
179 "47":"ansibggray"
180 };
180 };
181
181
182 function _process_numbers(attrs, numbers) {
182 function _process_numbers(attrs, numbers) {
183 // process ansi escapes
183 // process ansi escapes
184 var n = numbers.shift();
184 var n = numbers.shift();
185 if (ansi_colormap[n]) {
185 if (ansi_colormap[n]) {
186 if ( ! attrs["class"] ) {
186 if ( ! attrs["class"] ) {
187 attrs["class"] = ansi_colormap[n];
187 attrs["class"] = ansi_colormap[n];
188 } else {
188 } else {
189 attrs["class"] += " " + ansi_colormap[n];
189 attrs["class"] += " " + ansi_colormap[n];
190 }
190 }
191 } else if (n == "38" || n == "48") {
191 } else if (n == "38" || n == "48") {
192 // VT100 256 color or 24 bit RGB
192 // VT100 256 color or 24 bit RGB
193 if (numbers.length < 2) {
193 if (numbers.length < 2) {
194 console.log("Not enough fields for VT100 color", numbers);
194 console.log("Not enough fields for VT100 color", numbers);
195 return;
195 return;
196 }
196 }
197
197
198 var index_or_rgb = numbers.shift();
198 var index_or_rgb = numbers.shift();
199 var r,g,b;
199 var r,g,b;
200 if (index_or_rgb == "5") {
200 if (index_or_rgb == "5") {
201 // 256 color
201 // 256 color
202 var idx = parseInt(numbers.shift());
202 var idx = parseInt(numbers.shift());
203 if (idx < 16) {
203 if (idx < 16) {
204 // indexed ANSI
204 // indexed ANSI
205 // ignore bright / non-bright distinction
205 // ignore bright / non-bright distinction
206 idx = idx % 8;
206 idx = idx % 8;
207 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
207 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
208 if ( ! attrs["class"] ) {
208 if ( ! attrs["class"] ) {
209 attrs["class"] = ansiclass;
209 attrs["class"] = ansiclass;
210 } else {
210 } else {
211 attrs["class"] += " " + ansiclass;
211 attrs["class"] += " " + ansiclass;
212 }
212 }
213 return;
213 return;
214 } else if (idx < 232) {
214 } else if (idx < 232) {
215 // 216 color 6x6x6 RGB
215 // 216 color 6x6x6 RGB
216 idx = idx - 16;
216 idx = idx - 16;
217 b = idx % 6;
217 b = idx % 6;
218 g = Math.floor(idx / 6) % 6;
218 g = Math.floor(idx / 6) % 6;
219 r = Math.floor(idx / 36) % 6;
219 r = Math.floor(idx / 36) % 6;
220 // convert to rgb
220 // convert to rgb
221 r = (r * 51);
221 r = (r * 51);
222 g = (g * 51);
222 g = (g * 51);
223 b = (b * 51);
223 b = (b * 51);
224 } else {
224 } else {
225 // grayscale
225 // grayscale
226 idx = idx - 231;
226 idx = idx - 231;
227 // it's 1-24 and should *not* include black or white,
227 // it's 1-24 and should *not* include black or white,
228 // so a 26 point scale
228 // so a 26 point scale
229 r = g = b = Math.floor(idx * 256 / 26);
229 r = g = b = Math.floor(idx * 256 / 26);
230 }
230 }
231 } else if (index_or_rgb == "2") {
231 } else if (index_or_rgb == "2") {
232 // Simple 24 bit RGB
232 // Simple 24 bit RGB
233 if (numbers.length > 3) {
233 if (numbers.length > 3) {
234 console.log("Not enough fields for RGB", numbers);
234 console.log("Not enough fields for RGB", numbers);
235 return;
235 return;
236 }
236 }
237 r = numbers.shift();
237 r = numbers.shift();
238 g = numbers.shift();
238 g = numbers.shift();
239 b = numbers.shift();
239 b = numbers.shift();
240 } else {
240 } else {
241 console.log("unrecognized control", numbers);
241 console.log("unrecognized control", numbers);
242 return;
242 return;
243 }
243 }
244 if (r !== undefined) {
244 if (r !== undefined) {
245 // apply the rgb color
245 // apply the rgb color
246 var line;
246 var line;
247 if (n == "38") {
247 if (n == "38") {
248 line = "color: ";
248 line = "color: ";
249 } else {
249 } else {
250 line = "background-color: ";
250 line = "background-color: ";
251 }
251 }
252 line = line + "rgb(" + r + "," + g + "," + b + ");"
252 line = line + "rgb(" + r + "," + g + "," + b + ");"
253 if ( !attrs["style"] ) {
253 if ( !attrs["style"] ) {
254 attrs["style"] = line;
254 attrs["style"] = line;
255 } else {
255 } else {
256 attrs["style"] += " " + line;
256 attrs["style"] += " " + line;
257 }
257 }
258 }
258 }
259 }
259 }
260 }
260 }
261
261
262 function ansispan(str) {
262 function ansispan(str) {
263 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
263 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
264 // regular ansi escapes (using the table above)
264 // regular ansi escapes (using the table above)
265 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
265 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
266 if (!pattern) {
266 if (!pattern) {
267 // [(01|22|39|)m close spans
267 // [(01|22|39|)m close spans
268 return "</span>";
268 return "</span>";
269 }
269 }
270 // consume sequence of color escapes
270 // consume sequence of color escapes
271 var numbers = pattern.match(/\d+/g);
271 var numbers = pattern.match(/\d+/g);
272 var attrs = {};
272 var attrs = {};
273 while (numbers.length > 0) {
273 while (numbers.length > 0) {
274 _process_numbers(attrs, numbers);
274 _process_numbers(attrs, numbers);
275 }
275 }
276
276
277 var span = "<span ";
277 var span = "<span ";
278 for (var attr in attrs) {
278 for (var attr in attrs) {
279 var value = attrs[attr];
279 var value = attrs[attr];
280 span = span + " " + attr + '="' + attrs[attr] + '"';
280 span = span + " " + attr + '="' + attrs[attr] + '"';
281 }
281 }
282 return span + ">";
282 return span + ">";
283 });
283 });
284 };
284 };
285
285
286 // Transform ANSI color escape codes into HTML <span> tags with css
286 // Transform ANSI color escape codes into HTML <span> tags with css
287 // classes listed in the above ansi_colormap object. The actual color used
287 // classes listed in the above ansi_colormap object. The actual color used
288 // are set in the css file.
288 // are set in the css file.
289 function fixConsole(txt) {
289 function fixConsole(txt) {
290 txt = xmlencode(txt);
290 txt = xmlencode(txt);
291 var re = /\033\[([\dA-Fa-f;]*?)m/;
291 var re = /\033\[([\dA-Fa-f;]*?)m/;
292 var opened = false;
292 var opened = false;
293 var cmds = [];
293 var cmds = [];
294 var opener = "";
294 var opener = "";
295 var closer = "";
295 var closer = "";
296
296
297 // Strip all ANSI codes that are not color related. Matches
297 // Strip all ANSI codes that are not color related. Matches
298 // all ANSI codes that do not end with "m".
298 // all ANSI codes that do not end with "m".
299 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
299 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
300 txt = txt.replace(ignored_re, "");
300 txt = txt.replace(ignored_re, "");
301
301
302 // color ansi codes
302 // color ansi codes
303 txt = ansispan(txt);
303 txt = ansispan(txt);
304 return txt;
304 return txt;
305 }
305 }
306
306
307 // Remove chunks that should be overridden by the effect of
307 // Remove chunks that should be overridden by the effect of
308 // carriage return characters
308 // carriage return characters
309 function fixCarriageReturn(txt) {
309 function fixCarriageReturn(txt) {
310 var tmp = txt;
310 var tmp = txt;
311 do {
311 do {
312 txt = tmp;
312 txt = tmp;
313 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
313 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
314 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
314 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
315 } while (tmp.length < txt.length);
315 } while (tmp.length < txt.length);
316 return txt;
316 return txt;
317 }
317 }
318
318
319 // Locate any URLs and convert them to a anchor tag
319 // Locate any URLs and convert them to a anchor tag
320 function autoLinkUrls(txt) {
320 function autoLinkUrls(txt) {
321 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
321 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
322 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
322 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
323 }
323 }
324
324
325 // some keycodes that seem to be platform/browser independent
325 // some keycodes that seem to be platform/browser independent
326 var keycodes = {
326 var keycodes = {
327 BACKSPACE: 8,
327 BACKSPACE: 8,
328 TAB : 9,
328 TAB : 9,
329 ENTER : 13,
329 ENTER : 13,
330 SHIFT : 16,
330 SHIFT : 16,
331 CTRL : 17,
331 CTRL : 17,
332 CONTROL : 17,
332 CONTROL : 17,
333 ALT : 18,
333 ALT : 18,
334 CAPS_LOCK: 20,
334 CAPS_LOCK: 20,
335 ESC : 27,
335 ESC : 27,
336 SPACE : 32,
336 SPACE : 32,
337 PGUP : 33,
337 PGUP : 33,
338 PGDOWN : 34,
338 PGDOWN : 34,
339 END : 35,
339 END : 35,
340 HOME : 36,
340 HOME : 36,
341 LEFT_ARROW: 37,
341 LEFT_ARROW: 37,
342 LEFTARROW: 37,
342 LEFTARROW: 37,
343 LEFT : 37,
343 LEFT : 37,
344 UP_ARROW : 38,
344 UP_ARROW : 38,
345 UPARROW : 38,
345 UPARROW : 38,
346 UP : 38,
346 UP : 38,
347 RIGHT_ARROW:39,
347 RIGHT_ARROW:39,
348 RIGHTARROW:39,
348 RIGHTARROW:39,
349 RIGHT : 39,
349 RIGHT : 39,
350 DOWN_ARROW: 40,
350 DOWN_ARROW: 40,
351 DOWNARROW: 40,
351 DOWNARROW: 40,
352 DOWN : 40,
352 DOWN : 40,
353 I : 73,
353 I : 73,
354 M : 77,
354 M : 77,
355 // all three of these keys may be COMMAND on OS X:
355 // all three of these keys may be COMMAND on OS X:
356 LEFT_SUPER : 91,
356 LEFT_SUPER : 91,
357 RIGHT_SUPER : 92,
357 RIGHT_SUPER : 92,
358 COMMAND : 93,
358 COMMAND : 93,
359 };
359 };
360
360
361 // trigger a key press event
361 // trigger a key press event
362 var press = function (key) {
362 var press = function (key) {
363 var key_press = $.Event('keydown', {which: key});
363 var key_press = $.Event('keydown', {which: key});
364 $(document).trigger(key_press);
364 $(document).trigger(key_press);
365 }
365 }
366
366
367 var press_up = function() { press(keycodes.UP); };
367 var press_up = function() { press(keycodes.UP); };
368 var press_down = function() { press(keycodes.DOWN); };
368 var press_down = function() { press(keycodes.DOWN); };
369
369
370 var press_ctrl_enter = function() {
370 var press_ctrl_enter = function() {
371 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, ctrlKey: true}));
371 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, ctrlKey: true}));
372 };
372 };
373
373
374 var press_shift_enter = function() {
374 var press_shift_enter = function() {
375 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, shiftKey: true}));
375 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, shiftKey: true}));
376 };
376 };
377
377
378 // trigger the ctrl-m shortcut followed by one of our keys
378 // trigger the ctrl-m shortcut followed by one of our keys
379 var press_ghetto = function(key) {
379 var press_ghetto = function(key) {
380 $(document).trigger($.Event('keydown', {which: keycodes.M, ctrlKey: true}));
380 $(document).trigger($.Event('keydown', {which: keycodes.M, ctrlKey: true}));
381 press(key);
381 press(key);
382 };
382 };
383
383
384
384
385 var points_to_pixels = function (points) {
385 var points_to_pixels = function (points) {
386 // A reasonably good way of converting between points and pixels.
386 // A reasonably good way of converting between points and pixels.
387 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
387 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
388 $(body).append(test);
388 $(body).append(test);
389 var pixel_per_point = test.width()/10000;
389 var pixel_per_point = test.width()/10000;
390 test.remove();
390 test.remove();
391 return Math.floor(points*pixel_per_point);
391 return Math.floor(points*pixel_per_point);
392 };
392 };
393
393
394 var always_new = function (constructor) {
394 var always_new = function (constructor) {
395 // wrapper around contructor to avoid requiring `var a = new constructor()`
395 // wrapper around contructor to avoid requiring `var a = new constructor()`
396 // useful for passing constructors as callbacks,
396 // useful for passing constructors as callbacks,
397 // not for programmer laziness.
397 // not for programmer laziness.
398 // from http://programmers.stackexchange.com/questions/118798
398 // from http://programmers.stackexchange.com/questions/118798
399 return function () {
399 return function () {
400 var obj = Object.create(constructor.prototype);
400 var obj = Object.create(constructor.prototype);
401 constructor.apply(obj, arguments);
401 constructor.apply(obj, arguments);
402 return obj;
402 return obj;
403 };
403 };
404 };
404 };
405
405
406
406
407 var url_path_join = function () {
407 var url_path_join = function () {
408 // join a sequence of url components with '/'
408 // join a sequence of url components with '/'
409 var url = '';
409 var url = '';
410 for (var i = 0; i < arguments.length; i++) {
410 for (var i = 0; i < arguments.length; i++) {
411 if (arguments[i] === '') {
411 if (arguments[i] === '') {
412 continue;
412 continue;
413 }
413 }
414 if (url.length > 0 && url[url.length-1] != '/') {
414 if (url.length > 0 && url[url.length-1] != '/') {
415 url = url + '/' + arguments[i];
415 url = url + '/' + arguments[i];
416 } else {
416 } else {
417 url = url + arguments[i];
417 url = url + arguments[i];
418 }
418 }
419 }
419 }
420 return url;
420 return url;
421 };
421 };
422
422
423
423
424 var encode_uri_components = function (uri) {
424 var encode_uri_components = function (uri) {
425 // encode just the components of a multi-segment uri,
425 // encode just the components of a multi-segment uri,
426 // leaving '/' separators
426 // leaving '/' separators
427 return uri.split('/').map(encodeURIComponent).join('/');
427 return uri.split('/').map(encodeURIComponent).join('/');
428 }
428 }
429
429
430 var url_join_encode = function () {
430 var url_join_encode = function () {
431 // join a sequence of url components with '/',
431 // join a sequence of url components with '/',
432 // encoding each component with encodeURIComponent
432 // encoding each component with encodeURIComponent
433 return encode_uri_components(url_path_join.apply(null, arguments));
433 return encode_uri_components(url_path_join.apply(null, arguments));
434 };
434 };
435
435
436
436
437 var splitext = function (filename) {
437 var splitext = function (filename) {
438 // mimic Python os.path.splitext
438 // mimic Python os.path.splitext
439 // Returns ['base', '.ext']
439 // Returns ['base', '.ext']
440 var idx = filename.lastIndexOf('.');
440 var idx = filename.lastIndexOf('.');
441 if (idx > 0) {
441 if (idx > 0) {
442 return [filename.slice(0, idx), filename.slice(idx)];
442 return [filename.slice(0, idx), filename.slice(idx)];
443 } else {
443 } else {
444 return [filename, ''];
444 return [filename, ''];
445 }
445 }
446 }
446 }
447
447
448
448
449 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
449 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
450 var browser = (function() {
450 var browser = (function() {
451 if (typeof navigator === 'undefined') {
451 if (typeof navigator === 'undefined') {
452 // navigator undefined in node
452 // navigator undefined in node
453 return 'None';
453 return 'None';
454 }
454 }
455 var N= navigator.appName, ua= navigator.userAgent, tem;
455 var N= navigator.appName, ua= navigator.userAgent, tem;
456 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
456 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
457 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
457 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
458 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
458 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
459 return M;
459 return M;
460 })();
460 })();
461
461
462 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
463 var platform = (function () {
464 var OSName="Unknown OS";
465 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
466 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
467 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
468 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
469 return OSName
470 })();
471
462 var is_or_has = function (a, b) {
472 var is_or_has = function (a, b) {
463 // Is b a child of a or a itself?
473 // Is b a child of a or a itself?
464 return a.has(b).length !==0 || a.is(b);
474 return a.has(b).length !==0 || a.is(b);
465 }
475 }
466
476
467 var is_focused = function (e) {
477 var is_focused = function (e) {
468 // Is element e, or one of its children focused?
478 // Is element e, or one of its children focused?
469 e = $(e);
479 e = $(e);
470 var target = $(document.activeElement);
480 var target = $(document.activeElement);
471 if (target.length > 0) {
481 if (target.length > 0) {
472 if (is_or_has(e, target)) {
482 if (is_or_has(e, target)) {
473 return true;
483 return true;
474 } else {
484 } else {
475 return false;
485 return false;
476 }
486 }
477 } else {
487 } else {
478 return false;
488 return false;
479 }
489 }
480 }
490 }
481
491
482
492
483 return {
493 return {
484 regex_split : regex_split,
494 regex_split : regex_split,
485 uuid : uuid,
495 uuid : uuid,
486 fixConsole : fixConsole,
496 fixConsole : fixConsole,
487 keycodes : keycodes,
497 keycodes : keycodes,
488 press : press,
498 press : press,
489 press_up : press_up,
499 press_up : press_up,
490 press_down : press_down,
500 press_down : press_down,
491 press_ctrl_enter : press_ctrl_enter,
501 press_ctrl_enter : press_ctrl_enter,
492 press_shift_enter : press_shift_enter,
502 press_shift_enter : press_shift_enter,
493 press_ghetto : press_ghetto,
503 press_ghetto : press_ghetto,
494 fixCarriageReturn : fixCarriageReturn,
504 fixCarriageReturn : fixCarriageReturn,
495 autoLinkUrls : autoLinkUrls,
505 autoLinkUrls : autoLinkUrls,
496 points_to_pixels : points_to_pixels,
506 points_to_pixels : points_to_pixels,
497 url_path_join : url_path_join,
507 url_path_join : url_path_join,
498 url_join_encode : url_join_encode,
508 url_join_encode : url_join_encode,
499 encode_uri_components : encode_uri_components,
509 encode_uri_components : encode_uri_components,
500 splitext : splitext,
510 splitext : splitext,
501 always_new : always_new,
511 always_new : always_new,
502 browser : browser,
512 browser : browser,
513 platform: platform,
503 is_or_has : is_or_has,
514 is_or_has : is_or_has,
504 is_focused : is_focused
515 is_focused : is_focused
505 };
516 };
506
517
507 }(IPython));
518 }(IPython));
508
519
@@ -1,709 +1,770
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 // Keyboard management
9 // Keyboard management
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 // Setup global keycodes and inverse keycodes.
15 // Setup global keycodes and inverse keycodes.
16
16
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
20 // but have minor differences.
20 // but have minor differences.
21
21
22 // These apply to Firefox, (Webkit and IE)
22 // These apply to Firefox, (Webkit and IE)
23 var _keycodes = {
23 var _keycodes = {
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
30 '\\ |': 220, '\' "': 222,
30 '\\ |': 220, '\' "': 222,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
39 'insert': 45, 'delete': 46, 'numlock': 144,
39 'insert': 45, 'delete': 46, 'numlock': 144,
40 };
40 };
41
41
42 // These apply to Firefox and Opera
42 // These apply to Firefox and Opera
43 var _mozilla_keycodes = {
43 var _mozilla_keycodes = {
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
45 }
45 }
46
46
47 // This apply to Webkit and IE
47 // This apply to Webkit and IE
48 var _ie_keycodes = {
48 var _ie_keycodes = {
49 '; :': 186, '= +': 187, '- _': 189,
49 '; :': 186, '= +': 187, '- _': 189,
50 }
50 }
51
51
52 var browser = IPython.utils.browser[0];
52 var browser = IPython.utils.browser[0];
53 var platform = IPython.utils.platform;
53
54
54 if (browser === 'Firefox' || browser === 'Opera') {
55 if (browser === 'Firefox' || browser === 'Opera') {
55 $.extend(_keycodes, _mozilla_keycodes);
56 $.extend(_keycodes, _mozilla_keycodes);
56 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
57 $.extend(_keycodes, _ie_keycodes);
58 $.extend(_keycodes, _ie_keycodes);
58 }
59 }
59
60
60 var keycodes = {};
61 var keycodes = {};
61 var inv_keycodes = {};
62 var inv_keycodes = {};
62 for (var name in _keycodes) {
63 for (var name in _keycodes) {
63 var names = name.split(' ');
64 var names = name.split(' ');
64 if (names.length === 1) {
65 if (names.length === 1) {
65 var n = names[0]
66 var n = names[0]
66 keycodes[n] = _keycodes[n]
67 keycodes[n] = _keycodes[n]
67 inv_keycodes[_keycodes[n]] = n
68 inv_keycodes[_keycodes[n]] = n
68 } else {
69 } else {
69 var primary = names[0];
70 var primary = names[0];
70 var secondary = names[1];
71 var secondary = names[1];
71 keycodes[primary] = _keycodes[name]
72 keycodes[primary] = _keycodes[name]
72 keycodes[secondary] = _keycodes[name]
73 keycodes[secondary] = _keycodes[name]
73 inv_keycodes[_keycodes[name]] = primary
74 inv_keycodes[_keycodes[name]] = primary
74 }
75 }
75 }
76 }
76
77
77
78
78 // Default keyboard shortcuts
79 // Default keyboard shortcuts
79
80
80 var default_common_shortcuts = {
81 var default_common_shortcuts = {
81 'meta+s' : {
82 help : 'save notebook',
83 help_index : 'fb',
84 handler : function (event) {
85 IPython.notebook.save_checkpoint();
86 event.preventDefault();
87 return false;
88 }
89 },
90 'ctrl+s' : {
91 help : 'save notebook',
92 help_index : 'fc',
93 handler : function (event) {
94 IPython.notebook.save_checkpoint();
95 event.preventDefault();
96 return false;
97 }
98 },
99 'shift' : {
82 'shift' : {
100 help : '',
83 help : '',
101 help_index : '',
84 help_index : '',
102 handler : function (event) {
85 handler : function (event) {
103 // ignore shift keydown
86 // ignore shift keydown
104 return true;
87 return true;
105 }
88 }
106 },
89 },
107 'shift+enter' : {
90 'shift+enter' : {
108 help : 'run cell',
91 help : 'run cell',
109 help_index : 'ba',
92 help_index : 'ba',
110 handler : function (event) {
93 handler : function (event) {
111 IPython.notebook.execute_cell();
94 IPython.notebook.execute_cell();
112 return false;
95 return false;
113 }
96 }
114 },
97 },
115 'ctrl+enter' : {
98 'ctrl+enter' : {
116 help : 'run cell, select below',
99 help : 'run cell, select below',
117 help_index : 'bb',
100 help_index : 'bb',
118 handler : function (event) {
101 handler : function (event) {
119 IPython.notebook.execute_cell_and_select_below();
102 IPython.notebook.execute_cell_and_select_below();
120 return false;
103 return false;
121 }
104 }
122 },
105 },
123 'alt+enter' : {
106 'alt+enter' : {
124 help : 'run cell, insert below',
107 help : 'run cell, insert below',
125 help_index : 'bc',
108 help_index : 'bc',
126 handler : function (event) {
109 handler : function (event) {
127 IPython.notebook.execute_cell_and_insert_below();
110 IPython.notebook.execute_cell_and_insert_below();
128 return false;
111 return false;
129 }
112 }
130 }
113 }
131 }
114 }
132
115
116 if (platform === 'MacOS') {
117 default_common_shortcuts['cmd+s'] =
118 {
119 help : 'save notebook',
120 help_index : 'fb',
121 handler : function (event) {
122 IPython.notebook.save_checkpoint();
123 event.preventDefault();
124 return false;
125 }
126 };
127 } else {
128 default_common_shortcuts['ctrl+s'] =
129 {
130 help : 'save notebook',
131 help_index : 'fb',
132 handler : function (event) {
133 IPython.notebook.save_checkpoint();
134 event.preventDefault();
135 return false;
136 }
137 };
138 }
139
133 // Edit mode defaults
140 // Edit mode defaults
134
141
135 var default_edit_shortcuts = {
142 var default_edit_shortcuts = {
136 'esc' : {
143 'esc' : {
137 help : 'command mode',
144 help : 'command mode',
138 help_index : 'aa',
145 help_index : 'aa',
139 handler : function (event) {
146 handler : function (event) {
140 IPython.notebook.command_mode();
147 IPython.notebook.command_mode();
141 IPython.notebook.focus_cell();
148 IPython.notebook.focus_cell();
142 return false;
149 return false;
143 }
150 }
144 },
151 },
145 'ctrl+m' : {
152 'ctrl+m' : {
146 help : 'command mode',
153 help : 'command mode',
147 help_index : 'ab',
154 help_index : 'ab',
148 handler : function (event) {
155 handler : function (event) {
149 IPython.notebook.command_mode();
156 IPython.notebook.command_mode();
150 IPython.notebook.focus_cell();
157 IPython.notebook.focus_cell();
151 return false;
158 return false;
152 }
159 }
153 },
160 },
154 'up' : {
161 'up' : {
155 help : '',
162 help : '',
156 help_index : '',
163 help_index : '',
157 handler : function (event) {
164 handler : function (event) {
158 var cell = IPython.notebook.get_selected_cell();
165 var cell = IPython.notebook.get_selected_cell();
159 if (cell && cell.at_top()) {
166 if (cell && cell.at_top()) {
160 event.preventDefault();
167 event.preventDefault();
161 IPython.notebook.command_mode()
168 IPython.notebook.command_mode()
162 IPython.notebook.select_prev();
169 IPython.notebook.select_prev();
163 IPython.notebook.edit_mode();
170 IPython.notebook.edit_mode();
164 return false;
171 return false;
165 };
172 };
166 }
173 }
167 },
174 },
168 'down' : {
175 'down' : {
169 help : '',
176 help : '',
170 help_index : '',
177 help_index : '',
171 handler : function (event) {
178 handler : function (event) {
172 var cell = IPython.notebook.get_selected_cell();
179 var cell = IPython.notebook.get_selected_cell();
173 if (cell && cell.at_bottom()) {
180 if (cell && cell.at_bottom()) {
174 event.preventDefault();
181 event.preventDefault();
175 IPython.notebook.command_mode()
182 IPython.notebook.command_mode()
176 IPython.notebook.select_next();
183 IPython.notebook.select_next();
177 IPython.notebook.edit_mode();
184 IPython.notebook.edit_mode();
178 return false;
185 return false;
179 };
186 };
180 }
187 }
181 },
188 },
182 'alt+-' : {
189 'alt+-' : {
183 help : 'split cell',
190 help : 'split cell',
184 help_index : 'ea',
191 help_index : 'ea',
185 handler : function (event) {
192 handler : function (event) {
186 IPython.notebook.split_cell();
193 IPython.notebook.split_cell();
187 return false;
194 return false;
188 }
195 }
189 },
196 },
190 'alt+subtract' : {
197 'alt+subtract' : {
191 help : '',
198 help : '',
192 help_index : 'eb',
199 help_index : 'eb',
193 handler : function (event) {
200 handler : function (event) {
194 IPython.notebook.split_cell();
201 IPython.notebook.split_cell();
195 return false;
202 return false;
196 }
203 }
197 },
204 },
205 'tab' : {
206 help : 'indent or complete',
207 help_index : 'ec',
208 },
209 'shift+tab' : {
210 help : 'tooltip',
211 help_index : 'ed',
212 },
213 }
214
215 if (platform === 'MacOS') {
216 default_edit_shortcuts['cmd+/'] =
217 {
218 help : 'toggle comment',
219 help_index : 'ee'
220 };
221 default_edit_shortcuts['cmd+]'] =
222 {
223 help : 'indent',
224 help_index : 'ef'
225 };
226 default_edit_shortcuts['cmd+['] =
227 {
228 help : 'dedent',
229 help_index : 'eg'
230 };
231 } else {
232 default_edit_shortcuts['ctrl+/'] =
233 {
234 help : 'toggle comment',
235 help_index : 'ee'
236 };
237 default_edit_shortcuts['ctrl+]'] =
238 {
239 help : 'indent',
240 help_index : 'ef'
241 };
242 default_edit_shortcuts['ctrl+['] =
243 {
244 help : 'dedent',
245 help_index : 'eg'
246 };
198 }
247 }
199
248
200 // Command mode defaults
249 // Command mode defaults
201
250
202 var default_command_shortcuts = {
251 var default_command_shortcuts = {
203 'enter' : {
252 'enter' : {
204 help : 'edit mode',
253 help : 'edit mode',
205 help_index : 'aa',
254 help_index : 'aa',
206 handler : function (event) {
255 handler : function (event) {
207 IPython.notebook.edit_mode();
256 IPython.notebook.edit_mode();
208 return false;
257 return false;
209 }
258 }
210 },
259 },
211 'up' : {
260 'up' : {
212 help : 'select previous cell',
261 help : 'select previous cell',
213 help_index : 'da',
262 help_index : 'da',
214 handler : function (event) {
263 handler : function (event) {
215 var index = IPython.notebook.get_selected_index();
264 var index = IPython.notebook.get_selected_index();
216 if (index !== 0 && index !== null) {
265 if (index !== 0 && index !== null) {
217 IPython.notebook.select_prev();
266 IPython.notebook.select_prev();
218 var cell = IPython.notebook.get_selected_cell();
267 var cell = IPython.notebook.get_selected_cell();
219 cell.focus_cell();
268 cell.focus_cell();
220 };
269 };
221 return false;
270 return false;
222 }
271 }
223 },
272 },
224 'down' : {
273 'down' : {
225 help : 'select next cell',
274 help : 'select next cell',
226 help_index : 'db',
275 help_index : 'db',
227 handler : function (event) {
276 handler : function (event) {
228 var index = IPython.notebook.get_selected_index();
277 var index = IPython.notebook.get_selected_index();
229 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
278 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
230 IPython.notebook.select_next();
279 IPython.notebook.select_next();
231 var cell = IPython.notebook.get_selected_cell();
280 var cell = IPython.notebook.get_selected_cell();
232 cell.focus_cell();
281 cell.focus_cell();
233 };
282 };
234 return false;
283 return false;
235 }
284 }
236 },
285 },
237 'k' : {
286 'k' : {
238 help : 'select previous cell',
287 help : 'select previous cell',
239 help_index : 'dc',
288 help_index : 'dc',
240 handler : function (event) {
289 handler : function (event) {
241 var index = IPython.notebook.get_selected_index();
290 var index = IPython.notebook.get_selected_index();
242 if (index !== 0 && index !== null) {
291 if (index !== 0 && index !== null) {
243 IPython.notebook.select_prev();
292 IPython.notebook.select_prev();
244 var cell = IPython.notebook.get_selected_cell();
293 var cell = IPython.notebook.get_selected_cell();
245 cell.focus_cell();
294 cell.focus_cell();
246 };
295 };
247 return false;
296 return false;
248 }
297 }
249 },
298 },
250 'j' : {
299 'j' : {
251 help : 'select next cell',
300 help : 'select next cell',
252 help_index : 'dd',
301 help_index : 'dd',
253 handler : function (event) {
302 handler : function (event) {
254 var index = IPython.notebook.get_selected_index();
303 var index = IPython.notebook.get_selected_index();
255 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
304 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
256 IPython.notebook.select_next();
305 IPython.notebook.select_next();
257 var cell = IPython.notebook.get_selected_cell();
306 var cell = IPython.notebook.get_selected_cell();
258 cell.focus_cell();
307 cell.focus_cell();
259 };
308 };
260 return false;
309 return false;
261 }
310 }
262 },
311 },
263 'x' : {
312 'x' : {
264 help : 'cut cell',
313 help : 'cut cell',
265 help_index : 'ee',
314 help_index : 'ee',
266 handler : function (event) {
315 handler : function (event) {
267 IPython.notebook.cut_cell();
316 IPython.notebook.cut_cell();
268 return false;
317 return false;
269 }
318 }
270 },
319 },
271 'c' : {
320 'c' : {
272 help : 'copy cell',
321 help : 'copy cell',
273 help_index : 'ef',
322 help_index : 'ef',
274 handler : function (event) {
323 handler : function (event) {
275 IPython.notebook.copy_cell();
324 IPython.notebook.copy_cell();
276 return false;
325 return false;
277 }
326 }
278 },
327 },
279 'shift+v' : {
328 'shift+v' : {
280 help : 'paste cell above',
329 help : 'paste cell above',
281 help_index : 'eg',
330 help_index : 'eg',
282 handler : function (event) {
331 handler : function (event) {
283 IPython.notebook.paste_cell_above();
332 IPython.notebook.paste_cell_above();
284 return false;
333 return false;
285 }
334 }
286 },
335 },
287 'v' : {
336 'v' : {
288 help : 'paste cell below',
337 help : 'paste cell below',
289 help_index : 'eh',
338 help_index : 'eh',
290 handler : function (event) {
339 handler : function (event) {
291 IPython.notebook.paste_cell_below();
340 IPython.notebook.paste_cell_below();
292 return false;
341 return false;
293 }
342 }
294 },
343 },
295 'd' : {
344 'd' : {
296 help : 'delete cell (press twice)',
345 help : 'delete cell (press twice)',
297 help_index : 'ej',
346 help_index : 'ej',
298 count: 2,
347 count: 2,
299 handler : function (event) {
348 handler : function (event) {
300 IPython.notebook.delete_cell();
349 IPython.notebook.delete_cell();
301 return false;
350 return false;
302 }
351 }
303 },
352 },
304 'a' : {
353 'a' : {
305 help : 'insert cell above',
354 help : 'insert cell above',
306 help_index : 'ec',
355 help_index : 'ec',
307 handler : function (event) {
356 handler : function (event) {
308 IPython.notebook.insert_cell_above('code');
357 IPython.notebook.insert_cell_above('code');
309 IPython.notebook.select_prev();
358 IPython.notebook.select_prev();
310 IPython.notebook.focus_cell();
359 IPython.notebook.focus_cell();
311 return false;
360 return false;
312 }
361 }
313 },
362 },
314 'b' : {
363 'b' : {
315 help : 'insert cell below',
364 help : 'insert cell below',
316 help_index : 'ed',
365 help_index : 'ed',
317 handler : function (event) {
366 handler : function (event) {
318 IPython.notebook.insert_cell_below('code');
367 IPython.notebook.insert_cell_below('code');
319 IPython.notebook.select_next();
368 IPython.notebook.select_next();
320 IPython.notebook.focus_cell();
369 IPython.notebook.focus_cell();
321 return false;
370 return false;
322 }
371 }
323 },
372 },
324 'y' : {
373 'y' : {
325 help : 'to code',
374 help : 'to code',
326 help_index : 'ca',
375 help_index : 'ca',
327 handler : function (event) {
376 handler : function (event) {
328 IPython.notebook.to_code();
377 IPython.notebook.to_code();
329 return false;
378 return false;
330 }
379 }
331 },
380 },
332 'm' : {
381 'm' : {
333 help : 'to markdown',
382 help : 'to markdown',
334 help_index : 'cb',
383 help_index : 'cb',
335 handler : function (event) {
384 handler : function (event) {
336 IPython.notebook.to_markdown();
385 IPython.notebook.to_markdown();
337 return false;
386 return false;
338 }
387 }
339 },
388 },
340 'r' : {
389 'r' : {
341 help : 'to raw',
390 help : 'to raw',
342 help_index : 'cc',
391 help_index : 'cc',
343 handler : function (event) {
392 handler : function (event) {
344 IPython.notebook.to_raw();
393 IPython.notebook.to_raw();
345 return false;
394 return false;
346 }
395 }
347 },
396 },
348 '1' : {
397 '1' : {
349 help : 'to heading 1',
398 help : 'to heading 1',
350 help_index : 'cd',
399 help_index : 'cd',
351 handler : function (event) {
400 handler : function (event) {
352 IPython.notebook.to_heading(undefined, 1);
401 IPython.notebook.to_heading(undefined, 1);
353 return false;
402 return false;
354 }
403 }
355 },
404 },
356 '2' : {
405 '2' : {
357 help : 'to heading 2',
406 help : 'to heading 2',
358 help_index : 'ce',
407 help_index : 'ce',
359 handler : function (event) {
408 handler : function (event) {
360 IPython.notebook.to_heading(undefined, 2);
409 IPython.notebook.to_heading(undefined, 2);
361 return false;
410 return false;
362 }
411 }
363 },
412 },
364 '3' : {
413 '3' : {
365 help : 'to heading 3',
414 help : 'to heading 3',
366 help_index : 'cf',
415 help_index : 'cf',
367 handler : function (event) {
416 handler : function (event) {
368 IPython.notebook.to_heading(undefined, 3);
417 IPython.notebook.to_heading(undefined, 3);
369 return false;
418 return false;
370 }
419 }
371 },
420 },
372 '4' : {
421 '4' : {
373 help : 'to heading 4',
422 help : 'to heading 4',
374 help_index : 'cg',
423 help_index : 'cg',
375 handler : function (event) {
424 handler : function (event) {
376 IPython.notebook.to_heading(undefined, 4);
425 IPython.notebook.to_heading(undefined, 4);
377 return false;
426 return false;
378 }
427 }
379 },
428 },
380 '5' : {
429 '5' : {
381 help : 'to heading 5',
430 help : 'to heading 5',
382 help_index : 'ch',
431 help_index : 'ch',
383 handler : function (event) {
432 handler : function (event) {
384 IPython.notebook.to_heading(undefined, 5);
433 IPython.notebook.to_heading(undefined, 5);
385 return false;
434 return false;
386 }
435 }
387 },
436 },
388 '6' : {
437 '6' : {
389 help : 'to heading 6',
438 help : 'to heading 6',
390 help_index : 'ci',
439 help_index : 'ci',
391 handler : function (event) {
440 handler : function (event) {
392 IPython.notebook.to_heading(undefined, 6);
441 IPython.notebook.to_heading(undefined, 6);
393 return false;
442 return false;
394 }
443 }
395 },
444 },
396 'o' : {
445 'o' : {
397 help : 'toggle output',
446 help : 'toggle output',
398 help_index : 'gb',
447 help_index : 'gb',
399 handler : function (event) {
448 handler : function (event) {
400 IPython.notebook.toggle_output();
449 IPython.notebook.toggle_output();
401 return false;
450 return false;
402 }
451 }
403 },
452 },
404 'shift+o' : {
453 'shift+o' : {
405 help : 'toggle output',
454 help : 'toggle output scroll',
406 help_index : 'gc',
455 help_index : 'gc',
407 handler : function (event) {
456 handler : function (event) {
408 IPython.notebook.toggle_output_scroll();
457 IPython.notebook.toggle_output_scroll();
409 return false;
458 return false;
410 }
459 }
411 },
460 },
412 's' : {
461 's' : {
413 help : 'save notebook',
462 help : 'save notebook',
414 help_index : 'fa',
463 help_index : 'fa',
415 handler : function (event) {
464 handler : function (event) {
416 IPython.notebook.save_checkpoint();
465 IPython.notebook.save_checkpoint();
417 return false;
466 return false;
418 }
467 }
419 },
468 },
420 'ctrl+j' : {
469 'ctrl+j' : {
421 help : 'move cell down',
470 help : 'move cell down',
422 help_index : 'eb',
471 help_index : 'eb',
423 handler : function (event) {
472 handler : function (event) {
424 IPython.notebook.move_cell_down();
473 IPython.notebook.move_cell_down();
425 return false;
474 return false;
426 }
475 }
427 },
476 },
428 'ctrl+k' : {
477 'ctrl+k' : {
429 help : 'move cell up',
478 help : 'move cell up',
430 help_index : 'ea',
479 help_index : 'ea',
431 handler : function (event) {
480 handler : function (event) {
432 IPython.notebook.move_cell_up();
481 IPython.notebook.move_cell_up();
433 return false;
482 return false;
434 }
483 }
435 },
484 },
436 'l' : {
485 'l' : {
437 help : 'toggle line numbers',
486 help : 'toggle line numbers',
438 help_index : 'ga',
487 help_index : 'ga',
439 handler : function (event) {
488 handler : function (event) {
440 IPython.notebook.cell_toggle_line_numbers();
489 IPython.notebook.cell_toggle_line_numbers();
441 return false;
490 return false;
442 }
491 }
443 },
492 },
444 'i' : {
493 'i' : {
445 help : 'interrupt kernel (press twice)',
494 help : 'interrupt kernel (press twice)',
446 help_index : 'ha',
495 help_index : 'ha',
447 count: 2,
496 count: 2,
448 handler : function (event) {
497 handler : function (event) {
449 IPython.notebook.kernel.interrupt();
498 IPython.notebook.kernel.interrupt();
450 return false;
499 return false;
451 }
500 }
452 },
501 },
453 '0' : {
502 '0' : {
454 help : 'restart kernel (press twice)',
503 help : 'restart kernel (press twice)',
455 help_index : 'hb',
504 help_index : 'hb',
456 count: 2,
505 count: 2,
457 handler : function (event) {
506 handler : function (event) {
458 IPython.notebook.restart_kernel();
507 IPython.notebook.restart_kernel();
459 return false;
508 return false;
460 }
509 }
461 },
510 },
462 'h' : {
511 'h' : {
463 help : 'keyboard shortcuts',
512 help : 'keyboard shortcuts',
464 help_index : 'gd',
513 help_index : 'gd',
465 handler : function (event) {
514 handler : function (event) {
466 IPython.quick_help.show_keyboard_shortcuts();
515 IPython.quick_help.show_keyboard_shortcuts();
467 return false;
516 return false;
468 }
517 }
469 },
518 },
470 'z' : {
519 'z' : {
471 help : 'undo last delete',
520 help : 'undo last delete',
472 help_index : 'ei',
521 help_index : 'ei',
473 handler : function (event) {
522 handler : function (event) {
474 IPython.notebook.undelete_cell();
523 IPython.notebook.undelete_cell();
475 return false;
524 return false;
476 }
525 }
477 },
526 },
478 'shift+=' : {
527 'shift+=' : {
479 help : 'merge cell below',
528 help : 'merge cell below',
480 help_index : 'ek',
529 help_index : 'ek',
481 handler : function (event) {
530 handler : function (event) {
482 IPython.notebook.merge_cell_below();
531 IPython.notebook.merge_cell_below();
483 return false;
532 return false;
484 }
533 }
485 },
534 },
535 'shift+m' : {
536 help : 'merge cell below',
537 help_index : 'ek',
538 handler : function (event) {
539 IPython.notebook.merge_cell_below();
540 return false;
541 }
542 },
486 }
543 }
487
544
488
545
489 // Shortcut manager class
546 // Shortcut manager class
490
547
491 var ShortcutManager = function (delay) {
548 var ShortcutManager = function (delay) {
492 this._shortcuts = {}
549 this._shortcuts = {}
493 this._counts = {}
550 this._counts = {}
494 this.delay = delay || 800; // delay in milliseconds
551 this.delay = delay || 800; // delay in milliseconds
495 }
552 }
496
553
497 ShortcutManager.prototype.help = function () {
554 ShortcutManager.prototype.help = function () {
498 var help = [];
555 var help = [];
499 for (var shortcut in this._shortcuts) {
556 for (var shortcut in this._shortcuts) {
500 var help_string = this._shortcuts[shortcut]['help'];
557 var help_string = this._shortcuts[shortcut]['help'];
501 var help_index = this._shortcuts[shortcut]['help_index'];
558 var help_index = this._shortcuts[shortcut]['help_index'];
502 if (help_string) {
559 if (help_string) {
560 if (platform === 'MacOS') {
561 shortcut = shortcut.replace('meta', 'cmd');
562 }
503 help.push({
563 help.push({
504 shortcut: shortcut,
564 shortcut: shortcut,
505 help: help_string,
565 help: help_string,
506 help_index: help_index}
566 help_index: help_index}
507 );
567 );
508 }
568 }
509 }
569 }
510 help.sort(function (a, b) {
570 help.sort(function (a, b) {
511 if (a.help_index > b.help_index)
571 if (a.help_index > b.help_index)
512 return 1;
572 return 1;
513 if (a.help_index < b.help_index)
573 if (a.help_index < b.help_index)
514 return -1;
574 return -1;
515 return 0;
575 return 0;
516 });
576 });
517 return help;
577 return help;
518 }
578 }
519
579
520 ShortcutManager.prototype.normalize_key = function (key) {
580 ShortcutManager.prototype.normalize_key = function (key) {
521 return inv_keycodes[keycodes[key]];
581 return inv_keycodes[keycodes[key]];
522 }
582 }
523
583
524 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
584 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
525 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
585 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
586 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
526 var values = shortcut.split("+");
587 var values = shortcut.split("+");
527 if (values.length === 1) {
588 if (values.length === 1) {
528 return this.normalize_key(values[0])
589 return this.normalize_key(values[0])
529 } else {
590 } else {
530 var modifiers = values.slice(0,-1);
591 var modifiers = values.slice(0,-1);
531 var key = this.normalize_key(values[values.length-1]);
592 var key = this.normalize_key(values[values.length-1]);
532 modifiers.sort();
593 modifiers.sort();
533 return modifiers.join('+') + '+' + key;
594 return modifiers.join('+') + '+' + key;
534 }
595 }
535 }
596 }
536
597
537 ShortcutManager.prototype.event_to_shortcut = function (event) {
598 ShortcutManager.prototype.event_to_shortcut = function (event) {
538 // Convert a jQuery keyboard event to a strong based keyboard shortcut
599 // Convert a jQuery keyboard event to a strong based keyboard shortcut
539 var shortcut = '';
600 var shortcut = '';
540 var key = inv_keycodes[event.which]
601 var key = inv_keycodes[event.which]
541 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
602 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
542 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
603 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
543 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
604 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
544 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
605 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
545 shortcut += key;
606 shortcut += key;
546 return shortcut
607 return shortcut
547 }
608 }
548
609
549 ShortcutManager.prototype.clear_shortcuts = function () {
610 ShortcutManager.prototype.clear_shortcuts = function () {
550 this._shortcuts = {};
611 this._shortcuts = {};
551 }
612 }
552
613
553 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
614 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
554 if (typeof(data) === 'function') {
615 if (typeof(data) === 'function') {
555 data = {help: '', help_index: '', handler: data}
616 data = {help: '', help_index: '', handler: data}
556 }
617 }
557 data.help_index = data.help_index || '';
618 data.help_index = data.help_index || '';
558 data.help = data.help || '';
619 data.help = data.help || '';
559 data.count = data.count || 1;
620 data.count = data.count || 1;
560 if (data.help_index === '') {
621 if (data.help_index === '') {
561 data.help_index = 'zz';
622 data.help_index = 'zz';
562 }
623 }
563 shortcut = this.normalize_shortcut(shortcut);
624 shortcut = this.normalize_shortcut(shortcut);
564 this._counts[shortcut] = 0;
625 this._counts[shortcut] = 0;
565 this._shortcuts[shortcut] = data;
626 this._shortcuts[shortcut] = data;
566 }
627 }
567
628
568 ShortcutManager.prototype.add_shortcuts = function (data) {
629 ShortcutManager.prototype.add_shortcuts = function (data) {
569 for (var shortcut in data) {
630 for (var shortcut in data) {
570 this.add_shortcut(shortcut, data[shortcut]);
631 this.add_shortcut(shortcut, data[shortcut]);
571 }
632 }
572 }
633 }
573
634
574 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
635 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
575 shortcut = this.normalize_shortcut(shortcut);
636 shortcut = this.normalize_shortcut(shortcut);
576 delete this._counts[shortcut];
637 delete this._counts[shortcut];
577 delete this._shortcuts[shortcut];
638 delete this._shortcuts[shortcut];
578 }
639 }
579
640
580 ShortcutManager.prototype.count_handler = function (shortcut, event, handler) {
641 ShortcutManager.prototype.count_handler = function (shortcut, event, handler) {
581 var that = this;
642 var that = this;
582 var c = this._counts;
643 var c = this._counts;
583 if (c[shortcut] === 0) {
644 if (c[shortcut] === 0) {
584 c[shortcut] = 1;
645 c[shortcut] = 1;
585 setTimeout(function () {
646 setTimeout(function () {
586 c[shortcut] = 0;
647 c[shortcut] = 0;
587 }, that.delay);
648 }, that.delay);
588 } else if (c[shortcut] === 1) {
649 } else if (c[shortcut] === 1) {
589 c[shortcut] = 0;
650 c[shortcut] = 0;
590 return handler(event);
651 return handler(event);
591 }
652 }
592 return false;
653 return false;
593
654
594 }
655 }
595
656
596 ShortcutManager.prototype.call_handler = function (event) {
657 ShortcutManager.prototype.call_handler = function (event) {
597 var shortcut = this.event_to_shortcut(event);
658 var shortcut = this.event_to_shortcut(event);
598 var data = this._shortcuts[shortcut];
659 var data = this._shortcuts[shortcut];
599 if (data) {
660 if (data) {
600 var handler = data['handler'];
661 var handler = data['handler'];
601 if (handler) {
662 if (handler) {
602 if (data.count === 1) {
663 if (data.count === 1) {
603 return handler(event);
664 return handler(event);
604 } else if (data.count > 1) {
665 } else if (data.count > 1) {
605 return this.count_handler(shortcut, event, handler);
666 return this.count_handler(shortcut, event, handler);
606 }
667 }
607 }
668 }
608 }
669 }
609 return true;
670 return true;
610 }
671 }
611
672
612
673
613
674
614 // Main keyboard manager for the notebook
675 // Main keyboard manager for the notebook
615
676
616 var KeyboardManager = function () {
677 var KeyboardManager = function () {
617 this.mode = 'command';
678 this.mode = 'command';
618 this.enabled = true;
679 this.enabled = true;
619 this.bind_events();
680 this.bind_events();
620 this.command_shortcuts = new ShortcutManager();
681 this.command_shortcuts = new ShortcutManager();
621 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
682 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
622 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
683 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
623 this.edit_shortcuts = new ShortcutManager();
684 this.edit_shortcuts = new ShortcutManager();
624 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
685 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
625 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
686 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
626 };
687 };
627
688
628 KeyboardManager.prototype.bind_events = function () {
689 KeyboardManager.prototype.bind_events = function () {
629 var that = this;
690 var that = this;
630 $(document).keydown(function (event) {
691 $(document).keydown(function (event) {
631 return that.handle_keydown(event);
692 return that.handle_keydown(event);
632 });
693 });
633 };
694 };
634
695
635 KeyboardManager.prototype.handle_keydown = function (event) {
696 KeyboardManager.prototype.handle_keydown = function (event) {
636 var notebook = IPython.notebook;
697 var notebook = IPython.notebook;
637
698
638 if (event.which === keycodes['esc']) {
699 if (event.which === keycodes['esc']) {
639 // Intercept escape at highest level to avoid closing
700 // Intercept escape at highest level to avoid closing
640 // websocket connection with firefox
701 // websocket connection with firefox
641 event.preventDefault();
702 event.preventDefault();
642 }
703 }
643
704
644 if (!this.enabled) {
705 if (!this.enabled) {
645 if (event.which === keycodes['esc']) {
706 if (event.which === keycodes['esc']) {
646 // ESC
707 // ESC
647 notebook.command_mode();
708 notebook.command_mode();
648 return false;
709 return false;
649 }
710 }
650 return true;
711 return true;
651 }
712 }
652
713
653 if (this.mode === 'edit') {
714 if (this.mode === 'edit') {
654 return this.edit_shortcuts.call_handler(event);
715 return this.edit_shortcuts.call_handler(event);
655 } else if (this.mode === 'command') {
716 } else if (this.mode === 'command') {
656 return this.command_shortcuts.call_handler(event);
717 return this.command_shortcuts.call_handler(event);
657 }
718 }
658 return true;
719 return true;
659 }
720 }
660
721
661 KeyboardManager.prototype.edit_mode = function () {
722 KeyboardManager.prototype.edit_mode = function () {
662 this.last_mode = this.mode;
723 this.last_mode = this.mode;
663 this.mode = 'edit';
724 this.mode = 'edit';
664 }
725 }
665
726
666 KeyboardManager.prototype.command_mode = function () {
727 KeyboardManager.prototype.command_mode = function () {
667 this.last_mode = this.mode;
728 this.last_mode = this.mode;
668 this.mode = 'command';
729 this.mode = 'command';
669 }
730 }
670
731
671 KeyboardManager.prototype.enable = function () {
732 KeyboardManager.prototype.enable = function () {
672 this.enabled = true;
733 this.enabled = true;
673 }
734 }
674
735
675 KeyboardManager.prototype.disable = function () {
736 KeyboardManager.prototype.disable = function () {
676 this.enabled = false;
737 this.enabled = false;
677 }
738 }
678
739
679 KeyboardManager.prototype.register_events = function (e) {
740 KeyboardManager.prototype.register_events = function (e) {
680 var that = this;
741 var that = this;
681 e.on('focusin', function () {
742 e.on('focusin', function () {
682 that.command_mode();
743 that.command_mode();
683 that.disable();
744 that.disable();
684 });
745 });
685 e.on('focusout', function () {
746 e.on('focusout', function () {
686 that.command_mode();
747 that.command_mode();
687 that.enable();
748 that.enable();
688 });
749 });
689 // There are times (raw_input) where we remove the element from the DOM before
750 // There are times (raw_input) where we remove the element from the DOM before
690 // focusout is called. In this case we bind to the remove event of jQueryUI,
751 // focusout is called. In this case we bind to the remove event of jQueryUI,
691 // which gets triggered upon removal.
752 // which gets triggered upon removal.
692 e.on('remove', function () {
753 e.on('remove', function () {
693 that.command_mode();
754 that.command_mode();
694 that.enable();
755 that.enable();
695 });
756 });
696 }
757 }
697
758
698
759
699 IPython.keycodes = keycodes;
760 IPython.keycodes = keycodes;
700 IPython.inv_keycodes = inv_keycodes;
761 IPython.inv_keycodes = inv_keycodes;
701 IPython.default_common_shortcuts = default_common_shortcuts;
762 IPython.default_common_shortcuts = default_common_shortcuts;
702 IPython.default_edit_shortcuts = default_edit_shortcuts;
763 IPython.default_edit_shortcuts = default_edit_shortcuts;
703 IPython.default_command_shortcuts = default_command_shortcuts;
764 IPython.default_command_shortcuts = default_command_shortcuts;
704 IPython.ShortcutManager = ShortcutManager;
765 IPython.ShortcutManager = ShortcutManager;
705 IPython.KeyboardManager = KeyboardManager;
766 IPython.KeyboardManager = KeyboardManager;
706
767
707 return IPython;
768 return IPython;
708
769
709 }(IPython));
770 }(IPython));
@@ -1,210 +1,210
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
2 // Copyright (C) 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 // Notification widget
9 // Notification widget
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16
16
17 var NotificationArea = function (selector) {
17 var NotificationArea = function (selector) {
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 }
21 }
22 this.widget_dict = {};
22 this.widget_dict = {};
23 };
23 };
24
24
25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
26 var uuid = utils.uuid();
26 var uuid = utils.uuid();
27 if( css_class == 'danger') {css_class = 'ui-state-error';}
27 if( css_class == 'danger') {css_class = 'ui-state-error';}
28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
29 var tdiv = $('<div>')
29 var tdiv = $('<div>')
30 .attr('id',uuid)
30 .attr('id',uuid)
31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
32 .addClass('border-box-sizing')
32 .addClass('border-box-sizing')
33 .addClass(css_class)
33 .addClass(css_class)
34 .hide()
34 .hide()
35 .text(msg);
35 .text(msg);
36
36
37 $(this.selector).append(tdiv);
37 $(this.selector).append(tdiv);
38 var tmout = Math.max(1500,(timeout||1500));
38 var tmout = Math.max(1500,(timeout||1500));
39 tdiv.fadeIn(100);
39 tdiv.fadeIn(100);
40
40
41 setTimeout(function () {
41 setTimeout(function () {
42 tdiv.fadeOut(100, function () {tdiv.remove();});
42 tdiv.fadeOut(100, function () {tdiv.remove();});
43 }, tmout);
43 }, tmout);
44 };
44 };
45
45
46 NotificationArea.prototype.widget = function(name) {
46 NotificationArea.prototype.widget = function(name) {
47 if(this.widget_dict[name] == undefined) {
47 if(this.widget_dict[name] == undefined) {
48 return this.new_notification_widget(name);
48 return this.new_notification_widget(name);
49 }
49 }
50 return this.get_widget(name);
50 return this.get_widget(name);
51 };
51 };
52
52
53 NotificationArea.prototype.get_widget = function(name) {
53 NotificationArea.prototype.get_widget = function(name) {
54 if(this.widget_dict[name] == undefined) {
54 if(this.widget_dict[name] == undefined) {
55 throw('no widgets with this name');
55 throw('no widgets with this name');
56 }
56 }
57 return this.widget_dict[name];
57 return this.widget_dict[name];
58 };
58 };
59
59
60 NotificationArea.prototype.new_notification_widget = function(name) {
60 NotificationArea.prototype.new_notification_widget = function(name) {
61 if(this.widget_dict[name] != undefined) {
61 if(this.widget_dict[name] != undefined) {
62 throw('widget with that name already exists ! ');
62 throw('widget with that name already exists ! ');
63 }
63 }
64 var div = $('<div/>').attr('id','notification_'+name);
64 var div = $('<div/>').attr('id','notification_'+name);
65 $(this.selector).append(div);
65 $(this.selector).append(div);
66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
67 return this.widget_dict[name];
67 return this.widget_dict[name];
68 };
68 };
69
69
70 NotificationArea.prototype.init_notification_widgets = function() {
70 NotificationArea.prototype.init_notification_widgets = function() {
71 var knw = this.new_notification_widget('kernel');
71 var knw = this.new_notification_widget('kernel');
72
72
73 // Kernel events
73 // Kernel events
74 $([IPython.events]).on('status_idle.Kernel',function () {
74 $([IPython.events]).on('status_idle.Kernel',function () {
75 IPython.save_widget.update_document_title();
75 IPython.save_widget.update_document_title();
76 knw.set_message('Kernel Idle',200);
76 knw.set_message('Kernel Idle',200);
77 }
77 }
78 );
78 );
79
79
80 $([IPython.events]).on('status_busy.Kernel',function () {
80 $([IPython.events]).on('status_busy.Kernel',function () {
81 window.document.title='(Busy) '+window.document.title;
81 window.document.title='(Busy) '+window.document.title;
82 knw.set_message("Kernel busy");
82 knw.set_message("Kernel busy");
83 });
83 });
84
84
85 $([IPython.events]).on('status_restarting.Kernel',function () {
85 $([IPython.events]).on('status_restarting.Kernel',function () {
86 IPython.save_widget.update_document_title();
86 IPython.save_widget.update_document_title();
87 knw.set_message("Restarting kernel", 2000);
87 knw.set_message("Restarting kernel", 2000);
88 });
88 });
89
89
90 $([IPython.events]).on('status_interrupting.Kernel',function () {
90 $([IPython.events]).on('status_interrupting.Kernel',function () {
91 knw.set_message("Interrupting kernel");
91 knw.set_message("Interrupting kernel", 2000);
92 });
92 });
93
93
94 $([IPython.events]).on('status_dead.Kernel',function () {
94 $([IPython.events]).on('status_dead.Kernel',function () {
95 var msg = 'The kernel has died, and the automatic restart has failed.' +
95 var msg = 'The kernel has died, and the automatic restart has failed.' +
96 ' It is possible the kernel cannot be restarted.' +
96 ' It is possible the kernel cannot be restarted.' +
97 ' If you are not able to restart the kernel, you will still be able to save' +
97 ' If you are not able to restart the kernel, you will still be able to save' +
98 ' the notebook, but running code will no longer work until the notebook' +
98 ' the notebook, but running code will no longer work until the notebook' +
99 ' is reopened.';
99 ' is reopened.';
100
100
101 IPython.dialog.modal({
101 IPython.dialog.modal({
102 title: "Dead kernel",
102 title: "Dead kernel",
103 body : msg,
103 body : msg,
104 buttons : {
104 buttons : {
105 "Manual Restart": {
105 "Manual Restart": {
106 class: "btn-danger",
106 class: "btn-danger",
107 click: function () {
107 click: function () {
108 $([IPython.events]).trigger('status_restarting.Kernel');
108 $([IPython.events]).trigger('status_restarting.Kernel');
109 IPython.notebook.start_kernel();
109 IPython.notebook.start_kernel();
110 }
110 }
111 },
111 },
112 "Don't restart": {}
112 "Don't restart": {}
113 }
113 }
114 });
114 });
115 });
115 });
116
116
117 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
117 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
118 var kernel = data.kernel;
118 var kernel = data.kernel;
119 var ws_url = data.ws_url;
119 var ws_url = data.ws_url;
120 var early = data.early;
120 var early = data.early;
121 var msg;
121 var msg;
122 if (!early) {
122 if (!early) {
123 knw.set_message('Reconnecting WebSockets', 1000);
123 knw.set_message('Reconnecting WebSockets', 1000);
124 setTimeout(function () {
124 setTimeout(function () {
125 kernel.start_channels();
125 kernel.start_channels();
126 }, 5000);
126 }, 5000);
127 return;
127 return;
128 }
128 }
129 console.log('WebSocket connection failed: ', ws_url)
129 console.log('WebSocket connection failed: ', ws_url)
130 msg = "A WebSocket connection to could not be established." +
130 msg = "A WebSocket connection to could not be established." +
131 " You will NOT be able to run code. Check your" +
131 " You will NOT be able to run code. Check your" +
132 " network connection or notebook server configuration.";
132 " network connection or notebook server configuration.";
133 IPython.dialog.modal({
133 IPython.dialog.modal({
134 title: "WebSocket connection failed",
134 title: "WebSocket connection failed",
135 body: msg,
135 body: msg,
136 buttons : {
136 buttons : {
137 "OK": {},
137 "OK": {},
138 "Reconnect": {
138 "Reconnect": {
139 click: function () {
139 click: function () {
140 knw.set_message('Reconnecting WebSockets', 1000);
140 knw.set_message('Reconnecting WebSockets', 1000);
141 setTimeout(function () {
141 setTimeout(function () {
142 kernel.start_channels();
142 kernel.start_channels();
143 }, 5000);
143 }, 5000);
144 }
144 }
145 }
145 }
146 }
146 }
147 });
147 });
148 });
148 });
149
149
150
150
151 var nnw = this.new_notification_widget('notebook');
151 var nnw = this.new_notification_widget('notebook');
152
152
153 // Notebook events
153 // Notebook events
154 $([IPython.events]).on('notebook_loading.Notebook', function () {
154 $([IPython.events]).on('notebook_loading.Notebook', function () {
155 nnw.set_message("Loading notebook",500);
155 nnw.set_message("Loading notebook",500);
156 });
156 });
157 $([IPython.events]).on('notebook_loaded.Notebook', function () {
157 $([IPython.events]).on('notebook_loaded.Notebook', function () {
158 nnw.set_message("Notebook loaded",500);
158 nnw.set_message("Notebook loaded",500);
159 });
159 });
160 $([IPython.events]).on('notebook_saving.Notebook', function () {
160 $([IPython.events]).on('notebook_saving.Notebook', function () {
161 nnw.set_message("Saving notebook",500);
161 nnw.set_message("Saving notebook",500);
162 });
162 });
163 $([IPython.events]).on('notebook_saved.Notebook', function () {
163 $([IPython.events]).on('notebook_saved.Notebook', function () {
164 nnw.set_message("Notebook saved",2000);
164 nnw.set_message("Notebook saved",2000);
165 });
165 });
166 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
166 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
167 nnw.set_message("Notebook save failed");
167 nnw.set_message("Notebook save failed");
168 });
168 });
169
169
170 // Checkpoint events
170 // Checkpoint events
171 $([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
171 $([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
172 var msg = "Checkpoint created";
172 var msg = "Checkpoint created";
173 if (data.last_modified) {
173 if (data.last_modified) {
174 var d = new Date(data.last_modified);
174 var d = new Date(data.last_modified);
175 msg = msg + ": " + d.format("HH:MM:ss");
175 msg = msg + ": " + d.format("HH:MM:ss");
176 }
176 }
177 nnw.set_message(msg, 2000);
177 nnw.set_message(msg, 2000);
178 });
178 });
179 $([IPython.events]).on('checkpoint_failed.Notebook', function () {
179 $([IPython.events]).on('checkpoint_failed.Notebook', function () {
180 nnw.set_message("Checkpoint failed");
180 nnw.set_message("Checkpoint failed");
181 });
181 });
182 $([IPython.events]).on('checkpoint_deleted.Notebook', function () {
182 $([IPython.events]).on('checkpoint_deleted.Notebook', function () {
183 nnw.set_message("Checkpoint deleted", 500);
183 nnw.set_message("Checkpoint deleted", 500);
184 });
184 });
185 $([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
185 $([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
186 nnw.set_message("Checkpoint delete failed");
186 nnw.set_message("Checkpoint delete failed");
187 });
187 });
188 $([IPython.events]).on('checkpoint_restoring.Notebook', function () {
188 $([IPython.events]).on('checkpoint_restoring.Notebook', function () {
189 nnw.set_message("Restoring to checkpoint...", 500);
189 nnw.set_message("Restoring to checkpoint...", 500);
190 });
190 });
191 $([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
191 $([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
192 nnw.set_message("Checkpoint restore failed");
192 nnw.set_message("Checkpoint restore failed");
193 });
193 });
194
194
195 // Autosave events
195 // Autosave events
196 $([IPython.events]).on('autosave_disabled.Notebook', function () {
196 $([IPython.events]).on('autosave_disabled.Notebook', function () {
197 nnw.set_message("Autosave disabled", 2000);
197 nnw.set_message("Autosave disabled", 2000);
198 });
198 });
199 $([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) {
199 $([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) {
200 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
200 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
201 });
201 });
202
202
203 };
203 };
204
204
205 IPython.NotificationArea = NotificationArea;
205 IPython.NotificationArea = NotificationArea;
206
206
207 return IPython;
207 return IPython;
208
208
209 }(IPython));
209 }(IPython));
210
210
General Comments 0
You need to be logged in to leave comments. Login now