##// END OF EJS Templates
move mergeopt to utils...
MinRK -
Show More
@@ -1,563 +1,570 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $){
7 ], function(IPython, $){
8 "use strict";
8 "use strict";
9
9
10 IPython.load_extensions = function () {
10 IPython.load_extensions = function () {
11 // load one or more IPython notebook extensions with requirejs
11 // load one or more IPython notebook extensions with requirejs
12
12
13 var extensions = [];
13 var extensions = [];
14 var extension_names = arguments;
14 var extension_names = arguments;
15 for (var i = 0; i < extension_names.length; i++) {
15 for (var i = 0; i < extension_names.length; i++) {
16 extensions.push("nbextensions/" + arguments[i]);
16 extensions.push("nbextensions/" + arguments[i]);
17 }
17 }
18
18
19 require(extensions,
19 require(extensions,
20 function () {
20 function () {
21 for (var i = 0; i < arguments.length; i++) {
21 for (var i = 0; i < arguments.length; i++) {
22 var ext = arguments[i];
22 var ext = arguments[i];
23 var ext_name = extension_names[i];
23 var ext_name = extension_names[i];
24 // success callback
24 // success callback
25 console.log("Loaded extension: " + ext_name);
25 console.log("Loaded extension: " + ext_name);
26 if (ext && ext.load_ipython_extension !== undefined) {
26 if (ext && ext.load_ipython_extension !== undefined) {
27 ext.load_ipython_extension();
27 ext.load_ipython_extension();
28 }
28 }
29 }
29 }
30 },
30 },
31 function (err) {
31 function (err) {
32 // failure callback
32 // failure callback
33 console.log("Failed to load extension(s):", err.requireModules, err);
33 console.log("Failed to load extension(s):", err.requireModules, err);
34 }
34 }
35 );
35 );
36 };
36 };
37
37
38 //============================================================================
38 //============================================================================
39 // Cross-browser RegEx Split
39 // Cross-browser RegEx Split
40 //============================================================================
40 //============================================================================
41
41
42 // This code has been MODIFIED from the code licensed below to not replace the
42 // This code has been MODIFIED from the code licensed below to not replace the
43 // default browser split. The license is reproduced here.
43 // default browser split. The license is reproduced here.
44
44
45 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
45 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
46 /*!
46 /*!
47 * Cross-Browser Split 1.1.1
47 * Cross-Browser Split 1.1.1
48 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
48 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
49 * Available under the MIT License
49 * Available under the MIT License
50 * ECMAScript compliant, uniform cross-browser split method
50 * ECMAScript compliant, uniform cross-browser split method
51 */
51 */
52
52
53 /**
53 /**
54 * Splits a string into an array of strings using a regex or string
54 * Splits a string into an array of strings using a regex or string
55 * separator. Matches of the separator are not included in the result array.
55 * separator. Matches of the separator are not included in the result array.
56 * However, if `separator` is a regex that contains capturing groups,
56 * However, if `separator` is a regex that contains capturing groups,
57 * backreferences are spliced into the result each time `separator` is
57 * backreferences are spliced into the result each time `separator` is
58 * matched. Fixes browser bugs compared to the native
58 * matched. Fixes browser bugs compared to the native
59 * `String.prototype.split` and can be used reliably cross-browser.
59 * `String.prototype.split` and can be used reliably cross-browser.
60 * @param {String} str String to split.
60 * @param {String} str String to split.
61 * @param {RegExp|String} separator Regex or string to use for separating
61 * @param {RegExp|String} separator Regex or string to use for separating
62 * the string.
62 * the string.
63 * @param {Number} [limit] Maximum number of items to include in the result
63 * @param {Number} [limit] Maximum number of items to include in the result
64 * array.
64 * array.
65 * @returns {Array} Array of substrings.
65 * @returns {Array} Array of substrings.
66 * @example
66 * @example
67 *
67 *
68 * // Basic use
68 * // Basic use
69 * regex_split('a b c d', ' ');
69 * regex_split('a b c d', ' ');
70 * // -> ['a', 'b', 'c', 'd']
70 * // -> ['a', 'b', 'c', 'd']
71 *
71 *
72 * // With limit
72 * // With limit
73 * regex_split('a b c d', ' ', 2);
73 * regex_split('a b c d', ' ', 2);
74 * // -> ['a', 'b']
74 * // -> ['a', 'b']
75 *
75 *
76 * // Backreferences in result array
76 * // Backreferences in result array
77 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
77 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
78 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
78 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
79 */
79 */
80 var regex_split = function (str, separator, limit) {
80 var regex_split = function (str, separator, limit) {
81 // If `separator` is not a regex, use `split`
81 // If `separator` is not a regex, use `split`
82 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
82 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
83 return split.call(str, separator, limit);
83 return split.call(str, separator, limit);
84 }
84 }
85 var output = [],
85 var output = [],
86 flags = (separator.ignoreCase ? "i" : "") +
86 flags = (separator.ignoreCase ? "i" : "") +
87 (separator.multiline ? "m" : "") +
87 (separator.multiline ? "m" : "") +
88 (separator.extended ? "x" : "") + // Proposed for ES6
88 (separator.extended ? "x" : "") + // Proposed for ES6
89 (separator.sticky ? "y" : ""), // Firefox 3+
89 (separator.sticky ? "y" : ""), // Firefox 3+
90 lastLastIndex = 0,
90 lastLastIndex = 0,
91 // Make `global` and avoid `lastIndex` issues by working with a copy
91 // Make `global` and avoid `lastIndex` issues by working with a copy
92 separator = new RegExp(separator.source, flags + "g"),
92 separator = new RegExp(separator.source, flags + "g"),
93 separator2, match, lastIndex, lastLength;
93 separator2, match, lastIndex, lastLength;
94 str += ""; // Type-convert
94 str += ""; // Type-convert
95
95
96 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
96 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
97 if (!compliantExecNpcg) {
97 if (!compliantExecNpcg) {
98 // Doesn't need flags gy, but they don't hurt
98 // Doesn't need flags gy, but they don't hurt
99 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
99 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
100 }
100 }
101 /* Values for `limit`, per the spec:
101 /* Values for `limit`, per the spec:
102 * If undefined: 4294967295 // Math.pow(2, 32) - 1
102 * If undefined: 4294967295 // Math.pow(2, 32) - 1
103 * If 0, Infinity, or NaN: 0
103 * If 0, Infinity, or NaN: 0
104 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
104 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
105 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
105 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
106 * If other: Type-convert, then use the above rules
106 * If other: Type-convert, then use the above rules
107 */
107 */
108 limit = typeof(limit) === "undefined" ?
108 limit = typeof(limit) === "undefined" ?
109 -1 >>> 0 : // Math.pow(2, 32) - 1
109 -1 >>> 0 : // Math.pow(2, 32) - 1
110 limit >>> 0; // ToUint32(limit)
110 limit >>> 0; // ToUint32(limit)
111 while (match = separator.exec(str)) {
111 while (match = separator.exec(str)) {
112 // `separator.lastIndex` is not reliable cross-browser
112 // `separator.lastIndex` is not reliable cross-browser
113 lastIndex = match.index + match[0].length;
113 lastIndex = match.index + match[0].length;
114 if (lastIndex > lastLastIndex) {
114 if (lastIndex > lastLastIndex) {
115 output.push(str.slice(lastLastIndex, match.index));
115 output.push(str.slice(lastLastIndex, match.index));
116 // Fix browsers whose `exec` methods don't consistently return `undefined` for
116 // Fix browsers whose `exec` methods don't consistently return `undefined` for
117 // nonparticipating capturing groups
117 // nonparticipating capturing groups
118 if (!compliantExecNpcg && match.length > 1) {
118 if (!compliantExecNpcg && match.length > 1) {
119 match[0].replace(separator2, function () {
119 match[0].replace(separator2, function () {
120 for (var i = 1; i < arguments.length - 2; i++) {
120 for (var i = 1; i < arguments.length - 2; i++) {
121 if (typeof(arguments[i]) === "undefined") {
121 if (typeof(arguments[i]) === "undefined") {
122 match[i] = undefined;
122 match[i] = undefined;
123 }
123 }
124 }
124 }
125 });
125 });
126 }
126 }
127 if (match.length > 1 && match.index < str.length) {
127 if (match.length > 1 && match.index < str.length) {
128 Array.prototype.push.apply(output, match.slice(1));
128 Array.prototype.push.apply(output, match.slice(1));
129 }
129 }
130 lastLength = match[0].length;
130 lastLength = match[0].length;
131 lastLastIndex = lastIndex;
131 lastLastIndex = lastIndex;
132 if (output.length >= limit) {
132 if (output.length >= limit) {
133 break;
133 break;
134 }
134 }
135 }
135 }
136 if (separator.lastIndex === match.index) {
136 if (separator.lastIndex === match.index) {
137 separator.lastIndex++; // Avoid an infinite loop
137 separator.lastIndex++; // Avoid an infinite loop
138 }
138 }
139 }
139 }
140 if (lastLastIndex === str.length) {
140 if (lastLastIndex === str.length) {
141 if (lastLength || !separator.test("")) {
141 if (lastLength || !separator.test("")) {
142 output.push("");
142 output.push("");
143 }
143 }
144 } else {
144 } else {
145 output.push(str.slice(lastLastIndex));
145 output.push(str.slice(lastLastIndex));
146 }
146 }
147 return output.length > limit ? output.slice(0, limit) : output;
147 return output.length > limit ? output.slice(0, limit) : output;
148 };
148 };
149
149
150 //============================================================================
150 //============================================================================
151 // End contributed Cross-browser RegEx Split
151 // End contributed Cross-browser RegEx Split
152 //============================================================================
152 //============================================================================
153
153
154
154
155 var uuid = function () {
155 var uuid = function () {
156 // http://www.ietf.org/rfc/rfc4122.txt
156 // http://www.ietf.org/rfc/rfc4122.txt
157 var s = [];
157 var s = [];
158 var hexDigits = "0123456789ABCDEF";
158 var hexDigits = "0123456789ABCDEF";
159 for (var i = 0; i < 32; i++) {
159 for (var i = 0; i < 32; i++) {
160 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
160 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
161 }
161 }
162 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
162 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
163 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
163 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
164
164
165 var uuid = s.join("");
165 var uuid = s.join("");
166 return uuid;
166 return uuid;
167 };
167 };
168
168
169
169
170 //Fix raw text to parse correctly in crazy XML
170 //Fix raw text to parse correctly in crazy XML
171 function xmlencode(string) {
171 function xmlencode(string) {
172 return string.replace(/\&/g,'&'+'amp;')
172 return string.replace(/\&/g,'&'+'amp;')
173 .replace(/</g,'&'+'lt;')
173 .replace(/</g,'&'+'lt;')
174 .replace(/>/g,'&'+'gt;')
174 .replace(/>/g,'&'+'gt;')
175 .replace(/\'/g,'&'+'apos;')
175 .replace(/\'/g,'&'+'apos;')
176 .replace(/\"/g,'&'+'quot;')
176 .replace(/\"/g,'&'+'quot;')
177 .replace(/`/g,'&'+'#96;');
177 .replace(/`/g,'&'+'#96;');
178 }
178 }
179
179
180
180
181 //Map from terminal commands to CSS classes
181 //Map from terminal commands to CSS classes
182 var ansi_colormap = {
182 var ansi_colormap = {
183 "01":"ansibold",
183 "01":"ansibold",
184
184
185 "30":"ansiblack",
185 "30":"ansiblack",
186 "31":"ansired",
186 "31":"ansired",
187 "32":"ansigreen",
187 "32":"ansigreen",
188 "33":"ansiyellow",
188 "33":"ansiyellow",
189 "34":"ansiblue",
189 "34":"ansiblue",
190 "35":"ansipurple",
190 "35":"ansipurple",
191 "36":"ansicyan",
191 "36":"ansicyan",
192 "37":"ansigray",
192 "37":"ansigray",
193
193
194 "40":"ansibgblack",
194 "40":"ansibgblack",
195 "41":"ansibgred",
195 "41":"ansibgred",
196 "42":"ansibggreen",
196 "42":"ansibggreen",
197 "43":"ansibgyellow",
197 "43":"ansibgyellow",
198 "44":"ansibgblue",
198 "44":"ansibgblue",
199 "45":"ansibgpurple",
199 "45":"ansibgpurple",
200 "46":"ansibgcyan",
200 "46":"ansibgcyan",
201 "47":"ansibggray"
201 "47":"ansibggray"
202 };
202 };
203
203
204 function _process_numbers(attrs, numbers) {
204 function _process_numbers(attrs, numbers) {
205 // process ansi escapes
205 // process ansi escapes
206 var n = numbers.shift();
206 var n = numbers.shift();
207 if (ansi_colormap[n]) {
207 if (ansi_colormap[n]) {
208 if ( ! attrs["class"] ) {
208 if ( ! attrs["class"] ) {
209 attrs["class"] = ansi_colormap[n];
209 attrs["class"] = ansi_colormap[n];
210 } else {
210 } else {
211 attrs["class"] += " " + ansi_colormap[n];
211 attrs["class"] += " " + ansi_colormap[n];
212 }
212 }
213 } else if (n == "38" || n == "48") {
213 } else if (n == "38" || n == "48") {
214 // VT100 256 color or 24 bit RGB
214 // VT100 256 color or 24 bit RGB
215 if (numbers.length < 2) {
215 if (numbers.length < 2) {
216 console.log("Not enough fields for VT100 color", numbers);
216 console.log("Not enough fields for VT100 color", numbers);
217 return;
217 return;
218 }
218 }
219
219
220 var index_or_rgb = numbers.shift();
220 var index_or_rgb = numbers.shift();
221 var r,g,b;
221 var r,g,b;
222 if (index_or_rgb == "5") {
222 if (index_or_rgb == "5") {
223 // 256 color
223 // 256 color
224 var idx = parseInt(numbers.shift());
224 var idx = parseInt(numbers.shift());
225 if (idx < 16) {
225 if (idx < 16) {
226 // indexed ANSI
226 // indexed ANSI
227 // ignore bright / non-bright distinction
227 // ignore bright / non-bright distinction
228 idx = idx % 8;
228 idx = idx % 8;
229 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
229 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
230 if ( ! attrs["class"] ) {
230 if ( ! attrs["class"] ) {
231 attrs["class"] = ansiclass;
231 attrs["class"] = ansiclass;
232 } else {
232 } else {
233 attrs["class"] += " " + ansiclass;
233 attrs["class"] += " " + ansiclass;
234 }
234 }
235 return;
235 return;
236 } else if (idx < 232) {
236 } else if (idx < 232) {
237 // 216 color 6x6x6 RGB
237 // 216 color 6x6x6 RGB
238 idx = idx - 16;
238 idx = idx - 16;
239 b = idx % 6;
239 b = idx % 6;
240 g = Math.floor(idx / 6) % 6;
240 g = Math.floor(idx / 6) % 6;
241 r = Math.floor(idx / 36) % 6;
241 r = Math.floor(idx / 36) % 6;
242 // convert to rgb
242 // convert to rgb
243 r = (r * 51);
243 r = (r * 51);
244 g = (g * 51);
244 g = (g * 51);
245 b = (b * 51);
245 b = (b * 51);
246 } else {
246 } else {
247 // grayscale
247 // grayscale
248 idx = idx - 231;
248 idx = idx - 231;
249 // it's 1-24 and should *not* include black or white,
249 // it's 1-24 and should *not* include black or white,
250 // so a 26 point scale
250 // so a 26 point scale
251 r = g = b = Math.floor(idx * 256 / 26);
251 r = g = b = Math.floor(idx * 256 / 26);
252 }
252 }
253 } else if (index_or_rgb == "2") {
253 } else if (index_or_rgb == "2") {
254 // Simple 24 bit RGB
254 // Simple 24 bit RGB
255 if (numbers.length > 3) {
255 if (numbers.length > 3) {
256 console.log("Not enough fields for RGB", numbers);
256 console.log("Not enough fields for RGB", numbers);
257 return;
257 return;
258 }
258 }
259 r = numbers.shift();
259 r = numbers.shift();
260 g = numbers.shift();
260 g = numbers.shift();
261 b = numbers.shift();
261 b = numbers.shift();
262 } else {
262 } else {
263 console.log("unrecognized control", numbers);
263 console.log("unrecognized control", numbers);
264 return;
264 return;
265 }
265 }
266 if (r !== undefined) {
266 if (r !== undefined) {
267 // apply the rgb color
267 // apply the rgb color
268 var line;
268 var line;
269 if (n == "38") {
269 if (n == "38") {
270 line = "color: ";
270 line = "color: ";
271 } else {
271 } else {
272 line = "background-color: ";
272 line = "background-color: ";
273 }
273 }
274 line = line + "rgb(" + r + "," + g + "," + b + ");"
274 line = line + "rgb(" + r + "," + g + "," + b + ");"
275 if ( !attrs["style"] ) {
275 if ( !attrs["style"] ) {
276 attrs["style"] = line;
276 attrs["style"] = line;
277 } else {
277 } else {
278 attrs["style"] += " " + line;
278 attrs["style"] += " " + line;
279 }
279 }
280 }
280 }
281 }
281 }
282 }
282 }
283
283
284 function ansispan(str) {
284 function ansispan(str) {
285 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
285 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
286 // regular ansi escapes (using the table above)
286 // regular ansi escapes (using the table above)
287 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
287 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
288 if (!pattern) {
288 if (!pattern) {
289 // [(01|22|39|)m close spans
289 // [(01|22|39|)m close spans
290 return "</span>";
290 return "</span>";
291 }
291 }
292 // consume sequence of color escapes
292 // consume sequence of color escapes
293 var numbers = pattern.match(/\d+/g);
293 var numbers = pattern.match(/\d+/g);
294 var attrs = {};
294 var attrs = {};
295 while (numbers.length > 0) {
295 while (numbers.length > 0) {
296 _process_numbers(attrs, numbers);
296 _process_numbers(attrs, numbers);
297 }
297 }
298
298
299 var span = "<span ";
299 var span = "<span ";
300 for (var attr in attrs) {
300 for (var attr in attrs) {
301 var value = attrs[attr];
301 var value = attrs[attr];
302 span = span + " " + attr + '="' + attrs[attr] + '"';
302 span = span + " " + attr + '="' + attrs[attr] + '"';
303 }
303 }
304 return span + ">";
304 return span + ">";
305 });
305 });
306 };
306 };
307
307
308 // Transform ANSI color escape codes into HTML <span> tags with css
308 // Transform ANSI color escape codes into HTML <span> tags with css
309 // classes listed in the above ansi_colormap object. The actual color used
309 // classes listed in the above ansi_colormap object. The actual color used
310 // are set in the css file.
310 // are set in the css file.
311 function fixConsole(txt) {
311 function fixConsole(txt) {
312 txt = xmlencode(txt);
312 txt = xmlencode(txt);
313 var re = /\033\[([\dA-Fa-f;]*?)m/;
313 var re = /\033\[([\dA-Fa-f;]*?)m/;
314 var opened = false;
314 var opened = false;
315 var cmds = [];
315 var cmds = [];
316 var opener = "";
316 var opener = "";
317 var closer = "";
317 var closer = "";
318
318
319 // Strip all ANSI codes that are not color related. Matches
319 // Strip all ANSI codes that are not color related. Matches
320 // all ANSI codes that do not end with "m".
320 // all ANSI codes that do not end with "m".
321 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
321 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
322 txt = txt.replace(ignored_re, "");
322 txt = txt.replace(ignored_re, "");
323
323
324 // color ansi codes
324 // color ansi codes
325 txt = ansispan(txt);
325 txt = ansispan(txt);
326 return txt;
326 return txt;
327 }
327 }
328
328
329 // Remove chunks that should be overridden by the effect of
329 // Remove chunks that should be overridden by the effect of
330 // carriage return characters
330 // carriage return characters
331 function fixCarriageReturn(txt) {
331 function fixCarriageReturn(txt) {
332 var tmp = txt;
332 var tmp = txt;
333 do {
333 do {
334 txt = tmp;
334 txt = tmp;
335 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
335 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
336 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
336 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
337 } while (tmp.length < txt.length);
337 } while (tmp.length < txt.length);
338 return txt;
338 return txt;
339 }
339 }
340
340
341 // Locate any URLs and convert them to a anchor tag
341 // Locate any URLs and convert them to a anchor tag
342 function autoLinkUrls(txt) {
342 function autoLinkUrls(txt) {
343 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
343 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
344 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
344 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
345 }
345 }
346
346
347 var points_to_pixels = function (points) {
347 var points_to_pixels = function (points) {
348 // A reasonably good way of converting between points and pixels.
348 // A reasonably good way of converting between points and pixels.
349 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
349 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
350 $(body).append(test);
350 $(body).append(test);
351 var pixel_per_point = test.width()/10000;
351 var pixel_per_point = test.width()/10000;
352 test.remove();
352 test.remove();
353 return Math.floor(points*pixel_per_point);
353 return Math.floor(points*pixel_per_point);
354 };
354 };
355
355
356 var always_new = function (constructor) {
356 var always_new = function (constructor) {
357 // wrapper around contructor to avoid requiring `var a = new constructor()`
357 // wrapper around contructor to avoid requiring `var a = new constructor()`
358 // useful for passing constructors as callbacks,
358 // useful for passing constructors as callbacks,
359 // not for programmer laziness.
359 // not for programmer laziness.
360 // from http://programmers.stackexchange.com/questions/118798
360 // from http://programmers.stackexchange.com/questions/118798
361 return function () {
361 return function () {
362 var obj = Object.create(constructor.prototype);
362 var obj = Object.create(constructor.prototype);
363 constructor.apply(obj, arguments);
363 constructor.apply(obj, arguments);
364 return obj;
364 return obj;
365 };
365 };
366 };
366 };
367
367
368 var url_path_join = function () {
368 var url_path_join = function () {
369 // join a sequence of url components with '/'
369 // join a sequence of url components with '/'
370 var url = '';
370 var url = '';
371 for (var i = 0; i < arguments.length; i++) {
371 for (var i = 0; i < arguments.length; i++) {
372 if (arguments[i] === '') {
372 if (arguments[i] === '') {
373 continue;
373 continue;
374 }
374 }
375 if (url.length > 0 && url[url.length-1] != '/') {
375 if (url.length > 0 && url[url.length-1] != '/') {
376 url = url + '/' + arguments[i];
376 url = url + '/' + arguments[i];
377 } else {
377 } else {
378 url = url + arguments[i];
378 url = url + arguments[i];
379 }
379 }
380 }
380 }
381 url = url.replace(/\/\/+/, '/');
381 url = url.replace(/\/\/+/, '/');
382 return url;
382 return url;
383 };
383 };
384
384
385 var parse_url = function (url) {
385 var parse_url = function (url) {
386 // an `a` element with an href allows attr-access to the parsed segments of a URL
386 // an `a` element with an href allows attr-access to the parsed segments of a URL
387 // a = parse_url("http://localhost:8888/path/name#hash")
387 // a = parse_url("http://localhost:8888/path/name#hash")
388 // a.protocol = "http:"
388 // a.protocol = "http:"
389 // a.host = "localhost:8888"
389 // a.host = "localhost:8888"
390 // a.hostname = "localhost"
390 // a.hostname = "localhost"
391 // a.port = 8888
391 // a.port = 8888
392 // a.pathname = "/path/name"
392 // a.pathname = "/path/name"
393 // a.hash = "#hash"
393 // a.hash = "#hash"
394 var a = document.createElement("a");
394 var a = document.createElement("a");
395 a.href = url;
395 a.href = url;
396 return a;
396 return a;
397 };
397 };
398
398
399 var encode_uri_components = function (uri) {
399 var encode_uri_components = function (uri) {
400 // encode just the components of a multi-segment uri,
400 // encode just the components of a multi-segment uri,
401 // leaving '/' separators
401 // leaving '/' separators
402 return uri.split('/').map(encodeURIComponent).join('/');
402 return uri.split('/').map(encodeURIComponent).join('/');
403 };
403 };
404
404
405 var url_join_encode = function () {
405 var url_join_encode = function () {
406 // join a sequence of url components with '/',
406 // join a sequence of url components with '/',
407 // encoding each component with encodeURIComponent
407 // encoding each component with encodeURIComponent
408 return encode_uri_components(url_path_join.apply(null, arguments));
408 return encode_uri_components(url_path_join.apply(null, arguments));
409 };
409 };
410
410
411
411
412 var splitext = function (filename) {
412 var splitext = function (filename) {
413 // mimic Python os.path.splitext
413 // mimic Python os.path.splitext
414 // Returns ['base', '.ext']
414 // Returns ['base', '.ext']
415 var idx = filename.lastIndexOf('.');
415 var idx = filename.lastIndexOf('.');
416 if (idx > 0) {
416 if (idx > 0) {
417 return [filename.slice(0, idx), filename.slice(idx)];
417 return [filename.slice(0, idx), filename.slice(idx)];
418 } else {
418 } else {
419 return [filename, ''];
419 return [filename, ''];
420 }
420 }
421 };
421 };
422
422
423
423
424 var escape_html = function (text) {
424 var escape_html = function (text) {
425 // escape text to HTML
425 // escape text to HTML
426 return $("<div/>").text(text).html();
426 return $("<div/>").text(text).html();
427 };
427 };
428
428
429
429
430 var get_body_data = function(key) {
430 var get_body_data = function(key) {
431 // get a url-encoded item from body.data and decode it
431 // get a url-encoded item from body.data and decode it
432 // we should never have any encoded URLs anywhere else in code
432 // we should never have any encoded URLs anywhere else in code
433 // until we are building an actual request
433 // until we are building an actual request
434 return decodeURIComponent($('body').data(key));
434 return decodeURIComponent($('body').data(key));
435 };
435 };
436
436
437 var to_absolute_cursor_pos = function (cm, cursor) {
437 var to_absolute_cursor_pos = function (cm, cursor) {
438 // get the absolute cursor position from CodeMirror's col, ch
438 // get the absolute cursor position from CodeMirror's col, ch
439 if (!cursor) {
439 if (!cursor) {
440 cursor = cm.getCursor();
440 cursor = cm.getCursor();
441 }
441 }
442 var cursor_pos = cursor.ch;
442 var cursor_pos = cursor.ch;
443 for (var i = 0; i < cursor.line; i++) {
443 for (var i = 0; i < cursor.line; i++) {
444 cursor_pos += cm.getLine(i).length + 1;
444 cursor_pos += cm.getLine(i).length + 1;
445 }
445 }
446 return cursor_pos;
446 return cursor_pos;
447 };
447 };
448
448
449 var from_absolute_cursor_pos = function (cm, cursor_pos) {
449 var from_absolute_cursor_pos = function (cm, cursor_pos) {
450 // turn absolute cursor postion into CodeMirror col, ch cursor
450 // turn absolute cursor postion into CodeMirror col, ch cursor
451 var i, line;
451 var i, line;
452 var offset = 0;
452 var offset = 0;
453 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
453 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
454 if (offset + line.length < cursor_pos) {
454 if (offset + line.length < cursor_pos) {
455 offset += line.length + 1;
455 offset += line.length + 1;
456 } else {
456 } else {
457 return {
457 return {
458 line : i,
458 line : i,
459 ch : cursor_pos - offset,
459 ch : cursor_pos - offset,
460 };
460 };
461 }
461 }
462 }
462 }
463 // reached end, return endpoint
463 // reached end, return endpoint
464 return {
464 return {
465 ch : line.length - 1,
465 ch : line.length - 1,
466 line : i - 1,
466 line : i - 1,
467 };
467 };
468 };
468 };
469
469
470 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
470 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
471 var browser = (function() {
471 var browser = (function() {
472 if (typeof navigator === 'undefined') {
472 if (typeof navigator === 'undefined') {
473 // navigator undefined in node
473 // navigator undefined in node
474 return 'None';
474 return 'None';
475 }
475 }
476 var N= navigator.appName, ua= navigator.userAgent, tem;
476 var N= navigator.appName, ua= navigator.userAgent, tem;
477 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
477 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
478 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
478 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
479 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
479 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
480 return M;
480 return M;
481 })();
481 })();
482
482
483 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
483 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
484 var platform = (function () {
484 var platform = (function () {
485 if (typeof navigator === 'undefined') {
485 if (typeof navigator === 'undefined') {
486 // navigator undefined in node
486 // navigator undefined in node
487 return 'None';
487 return 'None';
488 }
488 }
489 var OSName="None";
489 var OSName="None";
490 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
490 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
491 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
491 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
492 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
492 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
493 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
493 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
494 return OSName;
494 return OSName;
495 })();
495 })();
496
496
497 var is_or_has = function (a, b) {
497 var is_or_has = function (a, b) {
498 // Is b a child of a or a itself?
498 // Is b a child of a or a itself?
499 return a.has(b).length !==0 || a.is(b);
499 return a.has(b).length !==0 || a.is(b);
500 };
500 };
501
501
502 var is_focused = function (e) {
502 var is_focused = function (e) {
503 // Is element e, or one of its children focused?
503 // Is element e, or one of its children focused?
504 e = $(e);
504 e = $(e);
505 var target = $(document.activeElement);
505 var target = $(document.activeElement);
506 if (target.length > 0) {
506 if (target.length > 0) {
507 if (is_or_has(e, target)) {
507 if (is_or_has(e, target)) {
508 return true;
508 return true;
509 } else {
509 } else {
510 return false;
510 return false;
511 }
511 }
512 } else {
512 } else {
513 return false;
513 return false;
514 }
514 }
515 };
515 };
516
516
517 var mergeopt = function(_class, options, overwrite){
518 options = options || {};
519 overwrite = overwrite || {};
520 return $.extend(true, {}, _class.options_default, options, overwrite);
521 };
522
517 var ajax_error_msg = function (jqXHR) {
523 var ajax_error_msg = function (jqXHR) {
518 // Return a JSON error message if there is one,
524 // Return a JSON error message if there is one,
519 // otherwise the basic HTTP status text.
525 // otherwise the basic HTTP status text.
520 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
526 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
521 return jqXHR.responseJSON.message;
527 return jqXHR.responseJSON.message;
522 } else {
528 } else {
523 return jqXHR.statusText;
529 return jqXHR.statusText;
524 }
530 }
525 }
531 }
526 var log_ajax_error = function (jqXHR, status, error) {
532 var log_ajax_error = function (jqXHR, status, error) {
527 // log ajax failures with informative messages
533 // log ajax failures with informative messages
528 var msg = "API request failed (" + jqXHR.status + "): ";
534 var msg = "API request failed (" + jqXHR.status + "): ";
529 console.log(jqXHR);
535 console.log(jqXHR);
530 msg += ajax_error_msg(jqXHR);
536 msg += ajax_error_msg(jqXHR);
531 console.log(msg);
537 console.log(msg);
532 };
538 };
533
539
534 var utils = {
540 var utils = {
535 regex_split : regex_split,
541 regex_split : regex_split,
536 uuid : uuid,
542 uuid : uuid,
537 fixConsole : fixConsole,
543 fixConsole : fixConsole,
538 fixCarriageReturn : fixCarriageReturn,
544 fixCarriageReturn : fixCarriageReturn,
539 autoLinkUrls : autoLinkUrls,
545 autoLinkUrls : autoLinkUrls,
540 points_to_pixels : points_to_pixels,
546 points_to_pixels : points_to_pixels,
541 get_body_data : get_body_data,
547 get_body_data : get_body_data,
542 parse_url : parse_url,
548 parse_url : parse_url,
543 url_path_join : url_path_join,
549 url_path_join : url_path_join,
544 url_join_encode : url_join_encode,
550 url_join_encode : url_join_encode,
545 encode_uri_components : encode_uri_components,
551 encode_uri_components : encode_uri_components,
546 splitext : splitext,
552 splitext : splitext,
547 escape_html : escape_html,
553 escape_html : escape_html,
548 always_new : always_new,
554 always_new : always_new,
549 to_absolute_cursor_pos : to_absolute_cursor_pos,
555 to_absolute_cursor_pos : to_absolute_cursor_pos,
550 from_absolute_cursor_pos : from_absolute_cursor_pos,
556 from_absolute_cursor_pos : from_absolute_cursor_pos,
551 browser : browser,
557 browser : browser,
552 platform: platform,
558 platform: platform,
553 is_or_has : is_or_has,
559 is_or_has : is_or_has,
554 is_focused : is_focused,
560 is_focused : is_focused,
561 mergeopt: mergeopt,
555 ajax_error_msg : ajax_error_msg,
562 ajax_error_msg : ajax_error_msg,
556 log_ajax_error : log_ajax_error,
563 log_ajax_error : log_ajax_error,
557 };
564 };
558
565
559 // Backwards compatability.
566 // Backwards compatability.
560 IPython.utils = utils;
567 IPython.utils = utils;
561
568
562 return utils;
569 return utils;
563 });
570 });
@@ -1,563 +1,557 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 // TODO: remove IPython dependency here
9 // TODO: remove IPython dependency here
10 "use strict";
10 "use strict";
11
11
12 // monkey patch CM to be able to syntax highlight cell magics
12 // monkey patch CM to be able to syntax highlight cell magics
13 // bug reported upstream,
13 // bug reported upstream,
14 // see https://github.com/marijnh/CodeMirror2/issues/670
14 // see https://github.com/marijnh/CodeMirror2/issues/670
15 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
15 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
16 CodeMirror.modes.null = function() {
16 CodeMirror.modes.null = function() {
17 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
17 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
18 };
18 };
19 }
19 }
20
20
21 CodeMirror.patchedGetMode = function(config, mode){
21 CodeMirror.patchedGetMode = function(config, mode){
22 var cmmode = CodeMirror.getMode(config, mode);
22 var cmmode = CodeMirror.getMode(config, mode);
23 if(cmmode.indent === null) {
23 if(cmmode.indent === null) {
24 console.log('patch mode "' , mode, '" on the fly');
24 console.log('patch mode "' , mode, '" on the fly');
25 cmmode.indent = function(){return 0;};
25 cmmode.indent = function(){return 0;};
26 }
26 }
27 return cmmode;
27 return cmmode;
28 };
28 };
29 // end monkey patching CodeMirror
29 // end monkey patching CodeMirror
30
30
31 var Cell = function (options) {
31 var Cell = function (options) {
32 // Constructor
32 // Constructor
33 //
33 //
34 // The Base `Cell` class from which to inherit.
34 // The Base `Cell` class from which to inherit.
35 //
35 //
36 // Parameters:
36 // Parameters:
37 // options: dictionary
37 // options: dictionary
38 // Dictionary of keyword arguments.
38 // Dictionary of keyword arguments.
39 // events: $(Events) instance
39 // events: $(Events) instance
40 // config: dictionary
40 // config: dictionary
41 // keyboard_manager: KeyboardManager instance
41 // keyboard_manager: KeyboardManager instance
42 options = options || {};
42 options = options || {};
43 this.keyboard_manager = options.keyboard_manager;
43 this.keyboard_manager = options.keyboard_manager;
44 this.events = options.events;
44 this.events = options.events;
45 var config = this.mergeopt(Cell, options.config);
45 var config = utils.mergeopt(Cell, options.config);
46 // superclass default overwrite our default
46 // superclass default overwrite our default
47
47
48 this.placeholder = config.placeholder || '';
48 this.placeholder = config.placeholder || '';
49 this.read_only = config.cm_config.readOnly;
49 this.read_only = config.cm_config.readOnly;
50 this.selected = false;
50 this.selected = false;
51 this.rendered = false;
51 this.rendered = false;
52 this.mode = 'command';
52 this.mode = 'command';
53 this.metadata = {};
53 this.metadata = {};
54 // load this from metadata later ?
54 // load this from metadata later ?
55 this.user_highlight = 'auto';
55 this.user_highlight = 'auto';
56 this.cm_config = config.cm_config;
56 this.cm_config = config.cm_config;
57 this.cell_id = utils.uuid();
57 this.cell_id = utils.uuid();
58 this._options = config;
58 this._options = config;
59
59
60 // For JS VM engines optimization, attributes should be all set (even
60 // For JS VM engines optimization, attributes should be all set (even
61 // to null) in the constructor, and if possible, if different subclass
61 // to null) in the constructor, and if possible, if different subclass
62 // have new attributes with same name, they should be created in the
62 // have new attributes with same name, they should be created in the
63 // same order. Easiest is to create and set to null in parent class.
63 // same order. Easiest is to create and set to null in parent class.
64
64
65 this.element = null;
65 this.element = null;
66 this.cell_type = this.cell_type || null;
66 this.cell_type = this.cell_type || null;
67 this.code_mirror = null;
67 this.code_mirror = null;
68
68
69 this.create_element();
69 this.create_element();
70 if (this.element !== null) {
70 if (this.element !== null) {
71 this.element.data("cell", this);
71 this.element.data("cell", this);
72 this.bind_events();
72 this.bind_events();
73 this.init_classes();
73 this.init_classes();
74 }
74 }
75 };
75 };
76
76
77 Cell.options_default = {
77 Cell.options_default = {
78 cm_config : {
78 cm_config : {
79 indentUnit : 4,
79 indentUnit : 4,
80 readOnly: false,
80 readOnly: false,
81 theme: "default",
81 theme: "default",
82 extraKeys: {
82 extraKeys: {
83 "Cmd-Right":"goLineRight",
83 "Cmd-Right":"goLineRight",
84 "End":"goLineRight",
84 "End":"goLineRight",
85 "Cmd-Left":"goLineLeft"
85 "Cmd-Left":"goLineLeft"
86 }
86 }
87 }
87 }
88 };
88 };
89
89
90 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
90 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
91 // by disabling drag/drop altogether on Safari
91 // by disabling drag/drop altogether on Safari
92 // https://github.com/marijnh/CodeMirror/issues/332
92 // https://github.com/marijnh/CodeMirror/issues/332
93 if (utils.browser[0] == "Safari") {
93 if (utils.browser[0] == "Safari") {
94 Cell.options_default.cm_config.dragDrop = false;
94 Cell.options_default.cm_config.dragDrop = false;
95 }
95 }
96
96
97 Cell.prototype.mergeopt = function(_class, options, overwrite){
98 options = options || {};
99 overwrite = overwrite || {};
100 return $.extend(true, {}, _class.options_default, options, overwrite);
101 };
102
103 /**
97 /**
104 * Empty. Subclasses must implement create_element.
98 * Empty. Subclasses must implement create_element.
105 * This should contain all the code to create the DOM element in notebook
99 * This should contain all the code to create the DOM element in notebook
106 * and will be called by Base Class constructor.
100 * and will be called by Base Class constructor.
107 * @method create_element
101 * @method create_element
108 */
102 */
109 Cell.prototype.create_element = function () {
103 Cell.prototype.create_element = function () {
110 };
104 };
111
105
112 Cell.prototype.init_classes = function () {
106 Cell.prototype.init_classes = function () {
113 // Call after this.element exists to initialize the css classes
107 // Call after this.element exists to initialize the css classes
114 // related to selected, rendered and mode.
108 // related to selected, rendered and mode.
115 if (this.selected) {
109 if (this.selected) {
116 this.element.addClass('selected');
110 this.element.addClass('selected');
117 } else {
111 } else {
118 this.element.addClass('unselected');
112 this.element.addClass('unselected');
119 }
113 }
120 if (this.rendered) {
114 if (this.rendered) {
121 this.element.addClass('rendered');
115 this.element.addClass('rendered');
122 } else {
116 } else {
123 this.element.addClass('unrendered');
117 this.element.addClass('unrendered');
124 }
118 }
125 if (this.mode === 'edit') {
119 if (this.mode === 'edit') {
126 this.element.addClass('edit_mode');
120 this.element.addClass('edit_mode');
127 } else {
121 } else {
128 this.element.addClass('command_mode');
122 this.element.addClass('command_mode');
129 }
123 }
130 };
124 };
131
125
132 /**
126 /**
133 * Subclasses can implement override bind_events.
127 * Subclasses can implement override bind_events.
134 * Be carefull to call the parent method when overwriting as it fires event.
128 * Be carefull to call the parent method when overwriting as it fires event.
135 * this will be triggerd after create_element in constructor.
129 * this will be triggerd after create_element in constructor.
136 * @method bind_events
130 * @method bind_events
137 */
131 */
138 Cell.prototype.bind_events = function () {
132 Cell.prototype.bind_events = function () {
139 var that = this;
133 var that = this;
140 // We trigger events so that Cell doesn't have to depend on Notebook.
134 // We trigger events so that Cell doesn't have to depend on Notebook.
141 that.element.click(function (event) {
135 that.element.click(function (event) {
142 if (!that.selected) {
136 if (!that.selected) {
143 that.events.trigger('select.Cell', {'cell':that});
137 that.events.trigger('select.Cell', {'cell':that});
144 }
138 }
145 });
139 });
146 that.element.focusin(function (event) {
140 that.element.focusin(function (event) {
147 if (!that.selected) {
141 if (!that.selected) {
148 that.events.trigger('select.Cell', {'cell':that});
142 that.events.trigger('select.Cell', {'cell':that});
149 }
143 }
150 });
144 });
151 if (this.code_mirror) {
145 if (this.code_mirror) {
152 this.code_mirror.on("change", function(cm, change) {
146 this.code_mirror.on("change", function(cm, change) {
153 that.events.trigger("set_dirty.Notebook", {value: true});
147 that.events.trigger("set_dirty.Notebook", {value: true});
154 });
148 });
155 }
149 }
156 if (this.code_mirror) {
150 if (this.code_mirror) {
157 this.code_mirror.on('focus', function(cm, change) {
151 this.code_mirror.on('focus', function(cm, change) {
158 that.events.trigger('edit_mode.Cell', {cell: that});
152 that.events.trigger('edit_mode.Cell', {cell: that});
159 });
153 });
160 }
154 }
161 if (this.code_mirror) {
155 if (this.code_mirror) {
162 this.code_mirror.on('blur', function(cm, change) {
156 this.code_mirror.on('blur', function(cm, change) {
163 that.events.trigger('command_mode.Cell', {cell: that});
157 that.events.trigger('command_mode.Cell', {cell: that});
164 });
158 });
165 }
159 }
166 };
160 };
167
161
168 /**
162 /**
169 * This method gets called in CodeMirror's onKeyDown/onKeyPress
163 * This method gets called in CodeMirror's onKeyDown/onKeyPress
170 * handlers and is used to provide custom key handling.
164 * handlers and is used to provide custom key handling.
171 *
165 *
172 * To have custom handling, subclasses should override this method, but still call it
166 * To have custom handling, subclasses should override this method, but still call it
173 * in order to process the Edit mode keyboard shortcuts.
167 * in order to process the Edit mode keyboard shortcuts.
174 *
168 *
175 * @method handle_codemirror_keyevent
169 * @method handle_codemirror_keyevent
176 * @param {CodeMirror} editor - The codemirror instance bound to the cell
170 * @param {CodeMirror} editor - The codemirror instance bound to the cell
177 * @param {event} event - key press event which either should or should not be handled by CodeMirror
171 * @param {event} event - key press event which either should or should not be handled by CodeMirror
178 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
172 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
179 */
173 */
180 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
174 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
181 var shortcuts = this.keyboard_manager.edit_shortcuts;
175 var shortcuts = this.keyboard_manager.edit_shortcuts;
182
176
183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
177 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
184 // manager will handle it
178 // manager will handle it
185 if (shortcuts.handles(event)) { return true; }
179 if (shortcuts.handles(event)) { return true; }
186
180
187 return false;
181 return false;
188 };
182 };
189
183
190
184
191 /**
185 /**
192 * Triger typsetting of math by mathjax on current cell element
186 * Triger typsetting of math by mathjax on current cell element
193 * @method typeset
187 * @method typeset
194 */
188 */
195 Cell.prototype.typeset = function () {
189 Cell.prototype.typeset = function () {
196 if (window.MathJax) {
190 if (window.MathJax) {
197 var cell_math = this.element.get(0);
191 var cell_math = this.element.get(0);
198 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
192 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
199 }
193 }
200 };
194 };
201
195
202 /**
196 /**
203 * handle cell level logic when a cell is selected
197 * handle cell level logic when a cell is selected
204 * @method select
198 * @method select
205 * @return is the action being taken
199 * @return is the action being taken
206 */
200 */
207 Cell.prototype.select = function () {
201 Cell.prototype.select = function () {
208 if (!this.selected) {
202 if (!this.selected) {
209 this.element.addClass('selected');
203 this.element.addClass('selected');
210 this.element.removeClass('unselected');
204 this.element.removeClass('unselected');
211 this.selected = true;
205 this.selected = true;
212 return true;
206 return true;
213 } else {
207 } else {
214 return false;
208 return false;
215 }
209 }
216 };
210 };
217
211
218 /**
212 /**
219 * handle cell level logic when a cell is unselected
213 * handle cell level logic when a cell is unselected
220 * @method unselect
214 * @method unselect
221 * @return is the action being taken
215 * @return is the action being taken
222 */
216 */
223 Cell.prototype.unselect = function () {
217 Cell.prototype.unselect = function () {
224 if (this.selected) {
218 if (this.selected) {
225 this.element.addClass('unselected');
219 this.element.addClass('unselected');
226 this.element.removeClass('selected');
220 this.element.removeClass('selected');
227 this.selected = false;
221 this.selected = false;
228 return true;
222 return true;
229 } else {
223 } else {
230 return false;
224 return false;
231 }
225 }
232 };
226 };
233
227
234 /**
228 /**
235 * handle cell level logic when a cell is rendered
229 * handle cell level logic when a cell is rendered
236 * @method render
230 * @method render
237 * @return is the action being taken
231 * @return is the action being taken
238 */
232 */
239 Cell.prototype.render = function () {
233 Cell.prototype.render = function () {
240 if (!this.rendered) {
234 if (!this.rendered) {
241 this.element.addClass('rendered');
235 this.element.addClass('rendered');
242 this.element.removeClass('unrendered');
236 this.element.removeClass('unrendered');
243 this.rendered = true;
237 this.rendered = true;
244 return true;
238 return true;
245 } else {
239 } else {
246 return false;
240 return false;
247 }
241 }
248 };
242 };
249
243
250 /**
244 /**
251 * handle cell level logic when a cell is unrendered
245 * handle cell level logic when a cell is unrendered
252 * @method unrender
246 * @method unrender
253 * @return is the action being taken
247 * @return is the action being taken
254 */
248 */
255 Cell.prototype.unrender = function () {
249 Cell.prototype.unrender = function () {
256 if (this.rendered) {
250 if (this.rendered) {
257 this.element.addClass('unrendered');
251 this.element.addClass('unrendered');
258 this.element.removeClass('rendered');
252 this.element.removeClass('rendered');
259 this.rendered = false;
253 this.rendered = false;
260 return true;
254 return true;
261 } else {
255 } else {
262 return false;
256 return false;
263 }
257 }
264 };
258 };
265
259
266 /**
260 /**
267 * Delegates keyboard shortcut handling to either IPython keyboard
261 * Delegates keyboard shortcut handling to either IPython keyboard
268 * manager when in command mode, or CodeMirror when in edit mode
262 * manager when in command mode, or CodeMirror when in edit mode
269 *
263 *
270 * @method handle_keyevent
264 * @method handle_keyevent
271 * @param {CodeMirror} editor - The codemirror instance bound to the cell
265 * @param {CodeMirror} editor - The codemirror instance bound to the cell
272 * @param {event} - key event to be handled
266 * @param {event} - key event to be handled
273 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
267 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
274 */
268 */
275 Cell.prototype.handle_keyevent = function (editor, event) {
269 Cell.prototype.handle_keyevent = function (editor, event) {
276
270
277 // console.log('CM', this.mode, event.which, event.type)
271 // console.log('CM', this.mode, event.which, event.type)
278
272
279 if (this.mode === 'command') {
273 if (this.mode === 'command') {
280 return true;
274 return true;
281 } else if (this.mode === 'edit') {
275 } else if (this.mode === 'edit') {
282 return this.handle_codemirror_keyevent(editor, event);
276 return this.handle_codemirror_keyevent(editor, event);
283 }
277 }
284 };
278 };
285
279
286 /**
280 /**
287 * @method at_top
281 * @method at_top
288 * @return {Boolean}
282 * @return {Boolean}
289 */
283 */
290 Cell.prototype.at_top = function () {
284 Cell.prototype.at_top = function () {
291 var cm = this.code_mirror;
285 var cm = this.code_mirror;
292 var cursor = cm.getCursor();
286 var cursor = cm.getCursor();
293 if (cursor.line === 0 && cursor.ch === 0) {
287 if (cursor.line === 0 && cursor.ch === 0) {
294 return true;
288 return true;
295 }
289 }
296 return false;
290 return false;
297 };
291 };
298
292
299 /**
293 /**
300 * @method at_bottom
294 * @method at_bottom
301 * @return {Boolean}
295 * @return {Boolean}
302 * */
296 * */
303 Cell.prototype.at_bottom = function () {
297 Cell.prototype.at_bottom = function () {
304 var cm = this.code_mirror;
298 var cm = this.code_mirror;
305 var cursor = cm.getCursor();
299 var cursor = cm.getCursor();
306 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
300 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
307 return true;
301 return true;
308 }
302 }
309 return false;
303 return false;
310 };
304 };
311
305
312 /**
306 /**
313 * enter the command mode for the cell
307 * enter the command mode for the cell
314 * @method command_mode
308 * @method command_mode
315 * @return is the action being taken
309 * @return is the action being taken
316 */
310 */
317 Cell.prototype.command_mode = function () {
311 Cell.prototype.command_mode = function () {
318 if (this.mode !== 'command') {
312 if (this.mode !== 'command') {
319 this.element.addClass('command_mode');
313 this.element.addClass('command_mode');
320 this.element.removeClass('edit_mode');
314 this.element.removeClass('edit_mode');
321 this.mode = 'command';
315 this.mode = 'command';
322 return true;
316 return true;
323 } else {
317 } else {
324 return false;
318 return false;
325 }
319 }
326 };
320 };
327
321
328 /**
322 /**
329 * enter the edit mode for the cell
323 * enter the edit mode for the cell
330 * @method command_mode
324 * @method command_mode
331 * @return is the action being taken
325 * @return is the action being taken
332 */
326 */
333 Cell.prototype.edit_mode = function () {
327 Cell.prototype.edit_mode = function () {
334 if (this.mode !== 'edit') {
328 if (this.mode !== 'edit') {
335 this.element.addClass('edit_mode');
329 this.element.addClass('edit_mode');
336 this.element.removeClass('command_mode');
330 this.element.removeClass('command_mode');
337 this.mode = 'edit';
331 this.mode = 'edit';
338 return true;
332 return true;
339 } else {
333 } else {
340 return false;
334 return false;
341 }
335 }
342 };
336 };
343
337
344 /**
338 /**
345 * Focus the cell in the DOM sense
339 * Focus the cell in the DOM sense
346 * @method focus_cell
340 * @method focus_cell
347 */
341 */
348 Cell.prototype.focus_cell = function () {
342 Cell.prototype.focus_cell = function () {
349 this.element.focus();
343 this.element.focus();
350 };
344 };
351
345
352 /**
346 /**
353 * Focus the editor area so a user can type
347 * Focus the editor area so a user can type
354 *
348 *
355 * NOTE: If codemirror is focused via a mouse click event, you don't want to
349 * NOTE: If codemirror is focused via a mouse click event, you don't want to
356 * call this because it will cause a page jump.
350 * call this because it will cause a page jump.
357 * @method focus_editor
351 * @method focus_editor
358 */
352 */
359 Cell.prototype.focus_editor = function () {
353 Cell.prototype.focus_editor = function () {
360 this.refresh();
354 this.refresh();
361 this.code_mirror.focus();
355 this.code_mirror.focus();
362 };
356 };
363
357
364 /**
358 /**
365 * Refresh codemirror instance
359 * Refresh codemirror instance
366 * @method refresh
360 * @method refresh
367 */
361 */
368 Cell.prototype.refresh = function () {
362 Cell.prototype.refresh = function () {
369 this.code_mirror.refresh();
363 this.code_mirror.refresh();
370 };
364 };
371
365
372 /**
366 /**
373 * should be overritten by subclass
367 * should be overritten by subclass
374 * @method get_text
368 * @method get_text
375 */
369 */
376 Cell.prototype.get_text = function () {
370 Cell.prototype.get_text = function () {
377 };
371 };
378
372
379 /**
373 /**
380 * should be overritten by subclass
374 * should be overritten by subclass
381 * @method set_text
375 * @method set_text
382 * @param {string} text
376 * @param {string} text
383 */
377 */
384 Cell.prototype.set_text = function (text) {
378 Cell.prototype.set_text = function (text) {
385 };
379 };
386
380
387 /**
381 /**
388 * should be overritten by subclass
382 * should be overritten by subclass
389 * serialise cell to json.
383 * serialise cell to json.
390 * @method toJSON
384 * @method toJSON
391 **/
385 **/
392 Cell.prototype.toJSON = function () {
386 Cell.prototype.toJSON = function () {
393 var data = {};
387 var data = {};
394 data.metadata = this.metadata;
388 data.metadata = this.metadata;
395 data.cell_type = this.cell_type;
389 data.cell_type = this.cell_type;
396 return data;
390 return data;
397 };
391 };
398
392
399
393
400 /**
394 /**
401 * should be overritten by subclass
395 * should be overritten by subclass
402 * @method fromJSON
396 * @method fromJSON
403 **/
397 **/
404 Cell.prototype.fromJSON = function (data) {
398 Cell.prototype.fromJSON = function (data) {
405 if (data.metadata !== undefined) {
399 if (data.metadata !== undefined) {
406 this.metadata = data.metadata;
400 this.metadata = data.metadata;
407 }
401 }
408 this.celltoolbar.rebuild();
402 this.celltoolbar.rebuild();
409 };
403 };
410
404
411
405
412 /**
406 /**
413 * can the cell be split into two cells
407 * can the cell be split into two cells
414 * @method is_splittable
408 * @method is_splittable
415 **/
409 **/
416 Cell.prototype.is_splittable = function () {
410 Cell.prototype.is_splittable = function () {
417 return true;
411 return true;
418 };
412 };
419
413
420
414
421 /**
415 /**
422 * can the cell be merged with other cells
416 * can the cell be merged with other cells
423 * @method is_mergeable
417 * @method is_mergeable
424 **/
418 **/
425 Cell.prototype.is_mergeable = function () {
419 Cell.prototype.is_mergeable = function () {
426 return true;
420 return true;
427 };
421 };
428
422
429
423
430 /**
424 /**
431 * @return {String} - the text before the cursor
425 * @return {String} - the text before the cursor
432 * @method get_pre_cursor
426 * @method get_pre_cursor
433 **/
427 **/
434 Cell.prototype.get_pre_cursor = function () {
428 Cell.prototype.get_pre_cursor = function () {
435 var cursor = this.code_mirror.getCursor();
429 var cursor = this.code_mirror.getCursor();
436 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
430 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
437 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
431 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
438 return text;
432 return text;
439 };
433 };
440
434
441
435
442 /**
436 /**
443 * @return {String} - the text after the cursor
437 * @return {String} - the text after the cursor
444 * @method get_post_cursor
438 * @method get_post_cursor
445 **/
439 **/
446 Cell.prototype.get_post_cursor = function () {
440 Cell.prototype.get_post_cursor = function () {
447 var cursor = this.code_mirror.getCursor();
441 var cursor = this.code_mirror.getCursor();
448 var last_line_num = this.code_mirror.lineCount()-1;
442 var last_line_num = this.code_mirror.lineCount()-1;
449 var last_line_len = this.code_mirror.getLine(last_line_num).length;
443 var last_line_len = this.code_mirror.getLine(last_line_num).length;
450 var end = {line:last_line_num, ch:last_line_len};
444 var end = {line:last_line_num, ch:last_line_len};
451 var text = this.code_mirror.getRange(cursor, end);
445 var text = this.code_mirror.getRange(cursor, end);
452 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
446 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
453 return text;
447 return text;
454 };
448 };
455
449
456 /**
450 /**
457 * Show/Hide CodeMirror LineNumber
451 * Show/Hide CodeMirror LineNumber
458 * @method show_line_numbers
452 * @method show_line_numbers
459 *
453 *
460 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
454 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
461 **/
455 **/
462 Cell.prototype.show_line_numbers = function (value) {
456 Cell.prototype.show_line_numbers = function (value) {
463 this.code_mirror.setOption('lineNumbers', value);
457 this.code_mirror.setOption('lineNumbers', value);
464 this.code_mirror.refresh();
458 this.code_mirror.refresh();
465 };
459 };
466
460
467 /**
461 /**
468 * Toggle CodeMirror LineNumber
462 * Toggle CodeMirror LineNumber
469 * @method toggle_line_numbers
463 * @method toggle_line_numbers
470 **/
464 **/
471 Cell.prototype.toggle_line_numbers = function () {
465 Cell.prototype.toggle_line_numbers = function () {
472 var val = this.code_mirror.getOption('lineNumbers');
466 var val = this.code_mirror.getOption('lineNumbers');
473 this.show_line_numbers(!val);
467 this.show_line_numbers(!val);
474 };
468 };
475
469
476 /**
470 /**
477 * Force codemirror highlight mode
471 * Force codemirror highlight mode
478 * @method force_highlight
472 * @method force_highlight
479 * @param {object} - CodeMirror mode
473 * @param {object} - CodeMirror mode
480 **/
474 **/
481 Cell.prototype.force_highlight = function(mode) {
475 Cell.prototype.force_highlight = function(mode) {
482 this.user_highlight = mode;
476 this.user_highlight = mode;
483 this.auto_highlight();
477 this.auto_highlight();
484 };
478 };
485
479
486 /**
480 /**
487 * Try to autodetect cell highlight mode, or use selected mode
481 * Try to autodetect cell highlight mode, or use selected mode
488 * @methods _auto_highlight
482 * @methods _auto_highlight
489 * @private
483 * @private
490 * @param {String|object|undefined} - CodeMirror mode | 'auto'
484 * @param {String|object|undefined} - CodeMirror mode | 'auto'
491 **/
485 **/
492 Cell.prototype._auto_highlight = function (modes) {
486 Cell.prototype._auto_highlight = function (modes) {
493 //Here we handle manually selected modes
487 //Here we handle manually selected modes
494 var mode;
488 var mode;
495 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
489 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
496 {
490 {
497 mode = this.user_highlight;
491 mode = this.user_highlight;
498 CodeMirror.autoLoadMode(this.code_mirror, mode);
492 CodeMirror.autoLoadMode(this.code_mirror, mode);
499 this.code_mirror.setOption('mode', mode);
493 this.code_mirror.setOption('mode', mode);
500 return;
494 return;
501 }
495 }
502 var current_mode = this.code_mirror.getOption('mode', mode);
496 var current_mode = this.code_mirror.getOption('mode', mode);
503 var first_line = this.code_mirror.getLine(0);
497 var first_line = this.code_mirror.getLine(0);
504 // loop on every pairs
498 // loop on every pairs
505 for(mode in modes) {
499 for(mode in modes) {
506 var regs = modes[mode].reg;
500 var regs = modes[mode].reg;
507 // only one key every time but regexp can't be keys...
501 // only one key every time but regexp can't be keys...
508 for(var i=0; i<regs.length; i++) {
502 for(var i=0; i<regs.length; i++) {
509 // here we handle non magic_modes
503 // here we handle non magic_modes
510 if(first_line.match(regs[i]) !== null) {
504 if(first_line.match(regs[i]) !== null) {
511 if(current_mode == mode){
505 if(current_mode == mode){
512 return;
506 return;
513 }
507 }
514 if (mode.search('magic_') !== 0) {
508 if (mode.search('magic_') !== 0) {
515 this.code_mirror.setOption('mode', mode);
509 this.code_mirror.setOption('mode', mode);
516 CodeMirror.autoLoadMode(this.code_mirror, mode);
510 CodeMirror.autoLoadMode(this.code_mirror, mode);
517 return;
511 return;
518 }
512 }
519 var open = modes[mode].open || "%%";
513 var open = modes[mode].open || "%%";
520 var close = modes[mode].close || "%%end";
514 var close = modes[mode].close || "%%end";
521 var mmode = mode;
515 var mmode = mode;
522 mode = mmode.substr(6);
516 mode = mmode.substr(6);
523 if(current_mode == mode){
517 if(current_mode == mode){
524 return;
518 return;
525 }
519 }
526 CodeMirror.autoLoadMode(this.code_mirror, mode);
520 CodeMirror.autoLoadMode(this.code_mirror, mode);
527 // create on the fly a mode that swhitch between
521 // create on the fly a mode that swhitch between
528 // plain/text and smth else otherwise `%%` is
522 // plain/text and smth else otherwise `%%` is
529 // source of some highlight issues.
523 // source of some highlight issues.
530 // we use patchedGetMode to circumvent a bug in CM
524 // we use patchedGetMode to circumvent a bug in CM
531 CodeMirror.defineMode(mmode , function(config) {
525 CodeMirror.defineMode(mmode , function(config) {
532 return CodeMirror.multiplexingMode(
526 return CodeMirror.multiplexingMode(
533 CodeMirror.patchedGetMode(config, 'text/plain'),
527 CodeMirror.patchedGetMode(config, 'text/plain'),
534 // always set someting on close
528 // always set someting on close
535 {open: open, close: close,
529 {open: open, close: close,
536 mode: CodeMirror.patchedGetMode(config, mode),
530 mode: CodeMirror.patchedGetMode(config, mode),
537 delimStyle: "delimit"
531 delimStyle: "delimit"
538 }
532 }
539 );
533 );
540 });
534 });
541 this.code_mirror.setOption('mode', mmode);
535 this.code_mirror.setOption('mode', mmode);
542 return;
536 return;
543 }
537 }
544 }
538 }
545 }
539 }
546 // fallback on default
540 // fallback on default
547 var default_mode;
541 var default_mode;
548 try {
542 try {
549 default_mode = this._options.cm_config.mode;
543 default_mode = this._options.cm_config.mode;
550 } catch(e) {
544 } catch(e) {
551 default_mode = 'text/plain';
545 default_mode = 'text/plain';
552 }
546 }
553 if( current_mode === default_mode){
547 if( current_mode === default_mode){
554 return;
548 return;
555 }
549 }
556 this.code_mirror.setOption('mode', default_mode);
550 this.code_mirror.setOption('mode', default_mode);
557 };
551 };
558
552
559 // Backwards compatibility.
553 // Backwards compatibility.
560 IPython.Cell = Cell;
554 IPython.Cell = Cell;
561
555
562 return {'Cell': Cell};
556 return {'Cell': Cell};
563 });
557 });
@@ -1,525 +1,525 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/keyboard',
8 'base/js/keyboard',
9 'notebook/js/cell',
9 'notebook/js/cell',
10 'notebook/js/outputarea',
10 'notebook/js/outputarea',
11 'notebook/js/completer',
11 'notebook/js/completer',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
13 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
14 "use strict";
14 "use strict";
15 var Cell = cell.Cell;
15 var Cell = cell.Cell;
16
16
17 /* local util for codemirror */
17 /* local util for codemirror */
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
19
19
20 /**
20 /**
21 *
21 *
22 * function to delete until previous non blanking space character
22 * function to delete until previous non blanking space character
23 * or first multiple of 4 tabstop.
23 * or first multiple of 4 tabstop.
24 * @private
24 * @private
25 */
25 */
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
30 var tabsize = cm.getOption('tabSize');
30 var tabsize = cm.getOption('tabSize');
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
33 var select = cm.getRange(from,cur);
33 var select = cm.getRange(from,cur);
34 if( select.match(/^\ +$/) !== null){
34 if( select.match(/^\ +$/) !== null){
35 cm.replaceRange("",from,cur);
35 cm.replaceRange("",from,cur);
36 } else {
36 } else {
37 cm.deleteH(-1,"char");
37 cm.deleteH(-1,"char");
38 }
38 }
39 };
39 };
40
40
41 var keycodes = keyboard.keycodes;
41 var keycodes = keyboard.keycodes;
42
42
43 var CodeCell = function (kernel, options) {
43 var CodeCell = function (kernel, options) {
44 // Constructor
44 // Constructor
45 //
45 //
46 // A Cell conceived to write code.
46 // A Cell conceived to write code.
47 //
47 //
48 // Parameters:
48 // Parameters:
49 // kernel: Kernel instance
49 // kernel: Kernel instance
50 // The kernel doesn't have to be set at creation time, in that case
50 // The kernel doesn't have to be set at creation time, in that case
51 // it will be null and set_kernel has to be called later.
51 // it will be null and set_kernel has to be called later.
52 // options: dictionary
52 // options: dictionary
53 // Dictionary of keyword arguments.
53 // Dictionary of keyword arguments.
54 // events: $(Events) instance
54 // events: $(Events) instance
55 // config: dictionary
55 // config: dictionary
56 // keyboard_manager: KeyboardManager instance
56 // keyboard_manager: KeyboardManager instance
57 // notebook: Notebook instance
57 // notebook: Notebook instance
58 // tooltip: Tooltip instance
58 // tooltip: Tooltip instance
59 this.kernel = kernel || null;
59 this.kernel = kernel || null;
60 this.notebook = options.notebook;
60 this.notebook = options.notebook;
61 this.collapsed = false;
61 this.collapsed = false;
62 this.events = options.events;
62 this.events = options.events;
63 this.tooltip = options.tooltip;
63 this.tooltip = options.tooltip;
64 this.config = options.config;
64 this.config = options.config;
65
65
66 // create all attributed in constructor function
66 // create all attributed in constructor function
67 // even if null for V8 VM optimisation
67 // even if null for V8 VM optimisation
68 this.input_prompt_number = null;
68 this.input_prompt_number = null;
69 this.celltoolbar = null;
69 this.celltoolbar = null;
70 this.output_area = null;
70 this.output_area = null;
71 this.last_msg_id = null;
71 this.last_msg_id = null;
72 this.completer = null;
72 this.completer = null;
73
73
74
74
75 var cm_overwrite_options = {
75 var cm_overwrite_options = {
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 };
77 };
78
78
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
79 var config = utils.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
80 Cell.apply(this,[{
80 Cell.apply(this,[{
81 config: config,
81 config: config,
82 keyboard_manager: options.keyboard_manager,
82 keyboard_manager: options.keyboard_manager,
83 events: this.events}]);
83 events: this.events}]);
84
84
85 // Attributes we want to override in this subclass.
85 // Attributes we want to override in this subclass.
86 this.cell_type = "code";
86 this.cell_type = "code";
87
87
88 var that = this;
88 var that = this;
89 this.element.focusout(
89 this.element.focusout(
90 function() { that.auto_highlight(); }
90 function() { that.auto_highlight(); }
91 );
91 );
92 };
92 };
93
93
94 CodeCell.options_default = {
94 CodeCell.options_default = {
95 cm_config : {
95 cm_config : {
96 extraKeys: {
96 extraKeys: {
97 "Tab" : "indentMore",
97 "Tab" : "indentMore",
98 "Shift-Tab" : "indentLess",
98 "Shift-Tab" : "indentLess",
99 "Backspace" : "delSpaceToPrevTabStop",
99 "Backspace" : "delSpaceToPrevTabStop",
100 "Cmd-/" : "toggleComment",
100 "Cmd-/" : "toggleComment",
101 "Ctrl-/" : "toggleComment"
101 "Ctrl-/" : "toggleComment"
102 },
102 },
103 mode: 'ipython',
103 mode: 'ipython',
104 theme: 'ipython',
104 theme: 'ipython',
105 matchBrackets: true,
105 matchBrackets: true,
106 // don't auto-close strings because of CodeMirror #2385
106 // don't auto-close strings because of CodeMirror #2385
107 autoCloseBrackets: "()[]{}"
107 autoCloseBrackets: "()[]{}"
108 }
108 }
109 };
109 };
110
110
111 CodeCell.msg_cells = {};
111 CodeCell.msg_cells = {};
112
112
113 CodeCell.prototype = new Cell();
113 CodeCell.prototype = new Cell();
114
114
115 /**
115 /**
116 * @method auto_highlight
116 * @method auto_highlight
117 */
117 */
118 CodeCell.prototype.auto_highlight = function () {
118 CodeCell.prototype.auto_highlight = function () {
119 this._auto_highlight(this.config.cell_magic_highlight);
119 this._auto_highlight(this.config.cell_magic_highlight);
120 };
120 };
121
121
122 /** @method create_element */
122 /** @method create_element */
123 CodeCell.prototype.create_element = function () {
123 CodeCell.prototype.create_element = function () {
124 Cell.prototype.create_element.apply(this, arguments);
124 Cell.prototype.create_element.apply(this, arguments);
125
125
126 var cell = $('<div></div>').addClass('cell code_cell');
126 var cell = $('<div></div>').addClass('cell code_cell');
127 cell.attr('tabindex','2');
127 cell.attr('tabindex','2');
128
128
129 var input = $('<div></div>').addClass('input');
129 var input = $('<div></div>').addClass('input');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
131 var inner_cell = $('<div/>').addClass('inner_cell');
131 var inner_cell = $('<div/>').addClass('inner_cell');
132 this.celltoolbar = new celltoolbar.CellToolbar({
132 this.celltoolbar = new celltoolbar.CellToolbar({
133 cell: this,
133 cell: this,
134 notebook: this.notebook});
134 notebook: this.notebook});
135 inner_cell.append(this.celltoolbar.element);
135 inner_cell.append(this.celltoolbar.element);
136 var input_area = $('<div/>').addClass('input_area');
136 var input_area = $('<div/>').addClass('input_area');
137 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
137 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
138 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
138 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
139 inner_cell.append(input_area);
139 inner_cell.append(input_area);
140 input.append(prompt).append(inner_cell);
140 input.append(prompt).append(inner_cell);
141
141
142 var widget_area = $('<div/>')
142 var widget_area = $('<div/>')
143 .addClass('widget-area')
143 .addClass('widget-area')
144 .hide();
144 .hide();
145 this.widget_area = widget_area;
145 this.widget_area = widget_area;
146 var widget_prompt = $('<div/>')
146 var widget_prompt = $('<div/>')
147 .addClass('prompt')
147 .addClass('prompt')
148 .appendTo(widget_area);
148 .appendTo(widget_area);
149 var widget_subarea = $('<div/>')
149 var widget_subarea = $('<div/>')
150 .addClass('widget-subarea')
150 .addClass('widget-subarea')
151 .appendTo(widget_area);
151 .appendTo(widget_area);
152 this.widget_subarea = widget_subarea;
152 this.widget_subarea = widget_subarea;
153 var widget_clear_buton = $('<button />')
153 var widget_clear_buton = $('<button />')
154 .addClass('close')
154 .addClass('close')
155 .html('&times;')
155 .html('&times;')
156 .click(function() {
156 .click(function() {
157 widget_area.slideUp('', function(){ widget_subarea.html(''); });
157 widget_area.slideUp('', function(){ widget_subarea.html(''); });
158 })
158 })
159 .appendTo(widget_prompt);
159 .appendTo(widget_prompt);
160
160
161 var output = $('<div></div>');
161 var output = $('<div></div>');
162 cell.append(input).append(widget_area).append(output);
162 cell.append(input).append(widget_area).append(output);
163 this.element = cell;
163 this.element = cell;
164 this.output_area = new outputarea.OutputArea({
164 this.output_area = new outputarea.OutputArea({
165 selector: output,
165 selector: output,
166 prompt_area: true,
166 prompt_area: true,
167 events: this.events,
167 events: this.events,
168 keyboard_manager: this.keyboard_manager});
168 keyboard_manager: this.keyboard_manager});
169 this.completer = new completer.Completer(this, this.events);
169 this.completer = new completer.Completer(this, this.events);
170 };
170 };
171
171
172 /** @method bind_events */
172 /** @method bind_events */
173 CodeCell.prototype.bind_events = function () {
173 CodeCell.prototype.bind_events = function () {
174 Cell.prototype.bind_events.apply(this);
174 Cell.prototype.bind_events.apply(this);
175 var that = this;
175 var that = this;
176
176
177 this.element.focusout(
177 this.element.focusout(
178 function() { that.auto_highlight(); }
178 function() { that.auto_highlight(); }
179 );
179 );
180 };
180 };
181
181
182
182
183 /**
183 /**
184 * This method gets called in CodeMirror's onKeyDown/onKeyPress
184 * This method gets called in CodeMirror's onKeyDown/onKeyPress
185 * handlers and is used to provide custom key handling. Its return
185 * handlers and is used to provide custom key handling. Its return
186 * value is used to determine if CodeMirror should ignore the event:
186 * value is used to determine if CodeMirror should ignore the event:
187 * true = ignore, false = don't ignore.
187 * true = ignore, false = don't ignore.
188 * @method handle_codemirror_keyevent
188 * @method handle_codemirror_keyevent
189 */
189 */
190 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
190 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
191
191
192 var that = this;
192 var that = this;
193 // whatever key is pressed, first, cancel the tooltip request before
193 // whatever key is pressed, first, cancel the tooltip request before
194 // they are sent, and remove tooltip if any, except for tab again
194 // they are sent, and remove tooltip if any, except for tab again
195 var tooltip_closed = null;
195 var tooltip_closed = null;
196 if (event.type === 'keydown' && event.which != keycodes.tab ) {
196 if (event.type === 'keydown' && event.which != keycodes.tab ) {
197 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
197 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
198 }
198 }
199
199
200 var cur = editor.getCursor();
200 var cur = editor.getCursor();
201 if (event.keyCode === keycodes.enter){
201 if (event.keyCode === keycodes.enter){
202 this.auto_highlight();
202 this.auto_highlight();
203 }
203 }
204
204
205 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
205 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
206 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
206 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
207 // browser and keyboard layout !
207 // browser and keyboard layout !
208 // Pressing '(' , request tooltip, don't forget to reappend it
208 // Pressing '(' , request tooltip, don't forget to reappend it
209 // The second argument says to hide the tooltip if the docstring
209 // The second argument says to hide the tooltip if the docstring
210 // is actually empty
210 // is actually empty
211 this.tooltip.pending(that, true);
211 this.tooltip.pending(that, true);
212 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
212 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
213 // If tooltip is active, cancel it. The call to
213 // If tooltip is active, cancel it. The call to
214 // remove_and_cancel_tooltip above doesn't pass, force=true.
214 // remove_and_cancel_tooltip above doesn't pass, force=true.
215 // Because of this it won't actually close the tooltip
215 // Because of this it won't actually close the tooltip
216 // if it is in sticky mode. Thus, we have to check again if it is open
216 // if it is in sticky mode. Thus, we have to check again if it is open
217 // and close it with force=true.
217 // and close it with force=true.
218 if (!this.tooltip._hidden) {
218 if (!this.tooltip._hidden) {
219 this.tooltip.remove_and_cancel_tooltip(true);
219 this.tooltip.remove_and_cancel_tooltip(true);
220 }
220 }
221 // If we closed the tooltip, don't let CM or the global handlers
221 // If we closed the tooltip, don't let CM or the global handlers
222 // handle this event.
222 // handle this event.
223 event.stop();
223 event.stop();
224 return true;
224 return true;
225 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
225 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
226 if (editor.somethingSelected()){
226 if (editor.somethingSelected()){
227 var anchor = editor.getCursor("anchor");
227 var anchor = editor.getCursor("anchor");
228 var head = editor.getCursor("head");
228 var head = editor.getCursor("head");
229 if( anchor.line != head.line){
229 if( anchor.line != head.line){
230 return false;
230 return false;
231 }
231 }
232 }
232 }
233 this.tooltip.request(that);
233 this.tooltip.request(that);
234 event.stop();
234 event.stop();
235 return true;
235 return true;
236 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
236 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
237 // Tab completion.
237 // Tab completion.
238 this.tooltip.remove_and_cancel_tooltip();
238 this.tooltip.remove_and_cancel_tooltip();
239 if (editor.somethingSelected()) {
239 if (editor.somethingSelected()) {
240 return false;
240 return false;
241 }
241 }
242 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
242 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
243 if (pre_cursor.trim() === "") {
243 if (pre_cursor.trim() === "") {
244 // Don't autocomplete if the part of the line before the cursor
244 // Don't autocomplete if the part of the line before the cursor
245 // is empty. In this case, let CodeMirror handle indentation.
245 // is empty. In this case, let CodeMirror handle indentation.
246 return false;
246 return false;
247 } else {
247 } else {
248 event.stop();
248 event.stop();
249 this.completer.startCompletion();
249 this.completer.startCompletion();
250 return true;
250 return true;
251 }
251 }
252 }
252 }
253
253
254 // keyboard event wasn't one of those unique to code cells, let's see
254 // keyboard event wasn't one of those unique to code cells, let's see
255 // if it's one of the generic ones (i.e. check edit mode shortcuts)
255 // if it's one of the generic ones (i.e. check edit mode shortcuts)
256 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
256 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
257 };
257 };
258
258
259 // Kernel related calls.
259 // Kernel related calls.
260
260
261 CodeCell.prototype.set_kernel = function (kernel) {
261 CodeCell.prototype.set_kernel = function (kernel) {
262 this.kernel = kernel;
262 this.kernel = kernel;
263 };
263 };
264
264
265 /**
265 /**
266 * Execute current code cell to the kernel
266 * Execute current code cell to the kernel
267 * @method execute
267 * @method execute
268 */
268 */
269 CodeCell.prototype.execute = function () {
269 CodeCell.prototype.execute = function () {
270 this.output_area.clear_output();
270 this.output_area.clear_output();
271
271
272 // Clear widget area
272 // Clear widget area
273 this.widget_subarea.html('');
273 this.widget_subarea.html('');
274 this.widget_subarea.height('');
274 this.widget_subarea.height('');
275 this.widget_area.height('');
275 this.widget_area.height('');
276 this.widget_area.hide();
276 this.widget_area.hide();
277
277
278 this.set_input_prompt('*');
278 this.set_input_prompt('*');
279 this.element.addClass("running");
279 this.element.addClass("running");
280 if (this.last_msg_id) {
280 if (this.last_msg_id) {
281 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
281 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
282 }
282 }
283 var callbacks = this.get_callbacks();
283 var callbacks = this.get_callbacks();
284
284
285 var old_msg_id = this.last_msg_id;
285 var old_msg_id = this.last_msg_id;
286 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
286 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
287 if (old_msg_id) {
287 if (old_msg_id) {
288 delete CodeCell.msg_cells[old_msg_id];
288 delete CodeCell.msg_cells[old_msg_id];
289 }
289 }
290 CodeCell.msg_cells[this.last_msg_id] = this;
290 CodeCell.msg_cells[this.last_msg_id] = this;
291 };
291 };
292
292
293 /**
293 /**
294 * Construct the default callbacks for
294 * Construct the default callbacks for
295 * @method get_callbacks
295 * @method get_callbacks
296 */
296 */
297 CodeCell.prototype.get_callbacks = function () {
297 CodeCell.prototype.get_callbacks = function () {
298 return {
298 return {
299 shell : {
299 shell : {
300 reply : $.proxy(this._handle_execute_reply, this),
300 reply : $.proxy(this._handle_execute_reply, this),
301 payload : {
301 payload : {
302 set_next_input : $.proxy(this._handle_set_next_input, this),
302 set_next_input : $.proxy(this._handle_set_next_input, this),
303 page : $.proxy(this._open_with_pager, this)
303 page : $.proxy(this._open_with_pager, this)
304 }
304 }
305 },
305 },
306 iopub : {
306 iopub : {
307 output : $.proxy(this.output_area.handle_output, this.output_area),
307 output : $.proxy(this.output_area.handle_output, this.output_area),
308 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
308 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
309 },
309 },
310 input : $.proxy(this._handle_input_request, this)
310 input : $.proxy(this._handle_input_request, this)
311 };
311 };
312 };
312 };
313
313
314 CodeCell.prototype._open_with_pager = function (payload) {
314 CodeCell.prototype._open_with_pager = function (payload) {
315 this.events.trigger('open_with_text.Pager', payload);
315 this.events.trigger('open_with_text.Pager', payload);
316 };
316 };
317
317
318 /**
318 /**
319 * @method _handle_execute_reply
319 * @method _handle_execute_reply
320 * @private
320 * @private
321 */
321 */
322 CodeCell.prototype._handle_execute_reply = function (msg) {
322 CodeCell.prototype._handle_execute_reply = function (msg) {
323 this.set_input_prompt(msg.content.execution_count);
323 this.set_input_prompt(msg.content.execution_count);
324 this.element.removeClass("running");
324 this.element.removeClass("running");
325 this.events.trigger('set_dirty.Notebook', {value: true});
325 this.events.trigger('set_dirty.Notebook', {value: true});
326 };
326 };
327
327
328 /**
328 /**
329 * @method _handle_set_next_input
329 * @method _handle_set_next_input
330 * @private
330 * @private
331 */
331 */
332 CodeCell.prototype._handle_set_next_input = function (payload) {
332 CodeCell.prototype._handle_set_next_input = function (payload) {
333 var data = {'cell': this, 'text': payload.text};
333 var data = {'cell': this, 'text': payload.text};
334 this.events.trigger('set_next_input.Notebook', data);
334 this.events.trigger('set_next_input.Notebook', data);
335 };
335 };
336
336
337 /**
337 /**
338 * @method _handle_input_request
338 * @method _handle_input_request
339 * @private
339 * @private
340 */
340 */
341 CodeCell.prototype._handle_input_request = function (msg) {
341 CodeCell.prototype._handle_input_request = function (msg) {
342 this.output_area.append_raw_input(msg);
342 this.output_area.append_raw_input(msg);
343 };
343 };
344
344
345
345
346 // Basic cell manipulation.
346 // Basic cell manipulation.
347
347
348 CodeCell.prototype.select = function () {
348 CodeCell.prototype.select = function () {
349 var cont = Cell.prototype.select.apply(this);
349 var cont = Cell.prototype.select.apply(this);
350 if (cont) {
350 if (cont) {
351 this.code_mirror.refresh();
351 this.code_mirror.refresh();
352 this.auto_highlight();
352 this.auto_highlight();
353 }
353 }
354 return cont;
354 return cont;
355 };
355 };
356
356
357 CodeCell.prototype.render = function () {
357 CodeCell.prototype.render = function () {
358 var cont = Cell.prototype.render.apply(this);
358 var cont = Cell.prototype.render.apply(this);
359 // Always execute, even if we are already in the rendered state
359 // Always execute, even if we are already in the rendered state
360 return cont;
360 return cont;
361 };
361 };
362
362
363 CodeCell.prototype.unrender = function () {
363 CodeCell.prototype.unrender = function () {
364 // CodeCell is always rendered
364 // CodeCell is always rendered
365 return false;
365 return false;
366 };
366 };
367
367
368 CodeCell.prototype.select_all = function () {
368 CodeCell.prototype.select_all = function () {
369 var start = {line: 0, ch: 0};
369 var start = {line: 0, ch: 0};
370 var nlines = this.code_mirror.lineCount();
370 var nlines = this.code_mirror.lineCount();
371 var last_line = this.code_mirror.getLine(nlines-1);
371 var last_line = this.code_mirror.getLine(nlines-1);
372 var end = {line: nlines-1, ch: last_line.length};
372 var end = {line: nlines-1, ch: last_line.length};
373 this.code_mirror.setSelection(start, end);
373 this.code_mirror.setSelection(start, end);
374 };
374 };
375
375
376
376
377 CodeCell.prototype.collapse_output = function () {
377 CodeCell.prototype.collapse_output = function () {
378 this.collapsed = true;
378 this.collapsed = true;
379 this.output_area.collapse();
379 this.output_area.collapse();
380 };
380 };
381
381
382
382
383 CodeCell.prototype.expand_output = function () {
383 CodeCell.prototype.expand_output = function () {
384 this.collapsed = false;
384 this.collapsed = false;
385 this.output_area.expand();
385 this.output_area.expand();
386 this.output_area.unscroll_area();
386 this.output_area.unscroll_area();
387 };
387 };
388
388
389 CodeCell.prototype.scroll_output = function () {
389 CodeCell.prototype.scroll_output = function () {
390 this.output_area.expand();
390 this.output_area.expand();
391 this.output_area.scroll_if_long();
391 this.output_area.scroll_if_long();
392 };
392 };
393
393
394 CodeCell.prototype.toggle_output = function () {
394 CodeCell.prototype.toggle_output = function () {
395 this.collapsed = Boolean(1 - this.collapsed);
395 this.collapsed = Boolean(1 - this.collapsed);
396 this.output_area.toggle_output();
396 this.output_area.toggle_output();
397 };
397 };
398
398
399 CodeCell.prototype.toggle_output_scroll = function () {
399 CodeCell.prototype.toggle_output_scroll = function () {
400 this.output_area.toggle_scroll();
400 this.output_area.toggle_scroll();
401 };
401 };
402
402
403
403
404 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
404 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
405 var ns;
405 var ns;
406 if (prompt_value === undefined) {
406 if (prompt_value === undefined) {
407 ns = "&nbsp;";
407 ns = "&nbsp;";
408 } else {
408 } else {
409 ns = encodeURIComponent(prompt_value);
409 ns = encodeURIComponent(prompt_value);
410 }
410 }
411 return 'In&nbsp;[' + ns + ']:';
411 return 'In&nbsp;[' + ns + ']:';
412 };
412 };
413
413
414 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
414 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
415 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
415 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
416 for(var i=1; i < lines_number; i++) {
416 for(var i=1; i < lines_number; i++) {
417 html.push(['...:']);
417 html.push(['...:']);
418 }
418 }
419 return html.join('<br/>');
419 return html.join('<br/>');
420 };
420 };
421
421
422 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
422 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
423
423
424
424
425 CodeCell.prototype.set_input_prompt = function (number) {
425 CodeCell.prototype.set_input_prompt = function (number) {
426 var nline = 1;
426 var nline = 1;
427 if (this.code_mirror !== undefined) {
427 if (this.code_mirror !== undefined) {
428 nline = this.code_mirror.lineCount();
428 nline = this.code_mirror.lineCount();
429 }
429 }
430 this.input_prompt_number = number;
430 this.input_prompt_number = number;
431 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
431 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
432 // This HTML call is okay because the user contents are escaped.
432 // This HTML call is okay because the user contents are escaped.
433 this.element.find('div.input_prompt').html(prompt_html);
433 this.element.find('div.input_prompt').html(prompt_html);
434 };
434 };
435
435
436
436
437 CodeCell.prototype.clear_input = function () {
437 CodeCell.prototype.clear_input = function () {
438 this.code_mirror.setValue('');
438 this.code_mirror.setValue('');
439 };
439 };
440
440
441
441
442 CodeCell.prototype.get_text = function () {
442 CodeCell.prototype.get_text = function () {
443 return this.code_mirror.getValue();
443 return this.code_mirror.getValue();
444 };
444 };
445
445
446
446
447 CodeCell.prototype.set_text = function (code) {
447 CodeCell.prototype.set_text = function (code) {
448 return this.code_mirror.setValue(code);
448 return this.code_mirror.setValue(code);
449 };
449 };
450
450
451
451
452 CodeCell.prototype.clear_output = function (wait) {
452 CodeCell.prototype.clear_output = function (wait) {
453 this.output_area.clear_output(wait);
453 this.output_area.clear_output(wait);
454 this.set_input_prompt();
454 this.set_input_prompt();
455 };
455 };
456
456
457
457
458 // JSON serialization
458 // JSON serialization
459
459
460 CodeCell.prototype.fromJSON = function (data) {
460 CodeCell.prototype.fromJSON = function (data) {
461 Cell.prototype.fromJSON.apply(this, arguments);
461 Cell.prototype.fromJSON.apply(this, arguments);
462 if (data.cell_type === 'code') {
462 if (data.cell_type === 'code') {
463 if (data.input !== undefined) {
463 if (data.input !== undefined) {
464 this.set_text(data.input);
464 this.set_text(data.input);
465 // make this value the starting point, so that we can only undo
465 // make this value the starting point, so that we can only undo
466 // to this state, instead of a blank cell
466 // to this state, instead of a blank cell
467 this.code_mirror.clearHistory();
467 this.code_mirror.clearHistory();
468 this.auto_highlight();
468 this.auto_highlight();
469 }
469 }
470 if (data.prompt_number !== undefined) {
470 if (data.prompt_number !== undefined) {
471 this.set_input_prompt(data.prompt_number);
471 this.set_input_prompt(data.prompt_number);
472 } else {
472 } else {
473 this.set_input_prompt();
473 this.set_input_prompt();
474 }
474 }
475 this.output_area.trusted = data.trusted || false;
475 this.output_area.trusted = data.trusted || false;
476 this.output_area.fromJSON(data.outputs);
476 this.output_area.fromJSON(data.outputs);
477 if (data.collapsed !== undefined) {
477 if (data.collapsed !== undefined) {
478 if (data.collapsed) {
478 if (data.collapsed) {
479 this.collapse_output();
479 this.collapse_output();
480 } else {
480 } else {
481 this.expand_output();
481 this.expand_output();
482 }
482 }
483 }
483 }
484 }
484 }
485 };
485 };
486
486
487
487
488 CodeCell.prototype.toJSON = function () {
488 CodeCell.prototype.toJSON = function () {
489 var data = Cell.prototype.toJSON.apply(this);
489 var data = Cell.prototype.toJSON.apply(this);
490 data.input = this.get_text();
490 data.input = this.get_text();
491 // is finite protect against undefined and '*' value
491 // is finite protect against undefined and '*' value
492 if (isFinite(this.input_prompt_number)) {
492 if (isFinite(this.input_prompt_number)) {
493 data.prompt_number = this.input_prompt_number;
493 data.prompt_number = this.input_prompt_number;
494 }
494 }
495 var outputs = this.output_area.toJSON();
495 var outputs = this.output_area.toJSON();
496 data.outputs = outputs;
496 data.outputs = outputs;
497 data.language = 'python';
497 data.language = 'python';
498 data.trusted = this.output_area.trusted;
498 data.trusted = this.output_area.trusted;
499 data.collapsed = this.collapsed;
499 data.collapsed = this.collapsed;
500 return data;
500 return data;
501 };
501 };
502
502
503 /**
503 /**
504 * handle cell level logic when a cell is unselected
504 * handle cell level logic when a cell is unselected
505 * @method unselect
505 * @method unselect
506 * @return is the action being taken
506 * @return is the action being taken
507 */
507 */
508 CodeCell.prototype.unselect = function () {
508 CodeCell.prototype.unselect = function () {
509 var cont = Cell.prototype.unselect.apply(this);
509 var cont = Cell.prototype.unselect.apply(this);
510 if (cont) {
510 if (cont) {
511 // When a code cell is usnelected, make sure that the corresponding
511 // When a code cell is usnelected, make sure that the corresponding
512 // tooltip and completer to that cell is closed.
512 // tooltip and completer to that cell is closed.
513 this.tooltip.remove_and_cancel_tooltip(true);
513 this.tooltip.remove_and_cancel_tooltip(true);
514 if (this.completer !== null) {
514 if (this.completer !== null) {
515 this.completer.close();
515 this.completer.close();
516 }
516 }
517 }
517 }
518 return cont;
518 return cont;
519 };
519 };
520
520
521 // Backwards compatability.
521 // Backwards compatability.
522 IPython.CodeCell = CodeCell;
522 IPython.CodeCell = CodeCell;
523
523
524 return {'CodeCell': CodeCell};
524 return {'CodeCell': CodeCell};
525 });
525 });
@@ -1,453 +1,454 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'base/js/utils',
6 'jquery',
7 'jquery',
7 'notebook/js/cell',
8 'notebook/js/cell',
8 'base/js/security',
9 'base/js/security',
9 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
10 'notebook/js/celltoolbar',
11 'notebook/js/celltoolbar',
11 'components/marked/lib/marked',
12 'components/marked/lib/marked',
12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 ], function(IPython, utils, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 "use strict";
14 "use strict";
14 var Cell = cell.Cell;
15 var Cell = cell.Cell;
15
16
16 var TextCell = function (options) {
17 var TextCell = function (options) {
17 // Constructor
18 // Constructor
18 //
19 //
19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 // and cell type is 'text' cell start as not redered.
21 // and cell type is 'text' cell start as not redered.
21 //
22 //
22 // Parameters:
23 // Parameters:
23 // options: dictionary
24 // options: dictionary
24 // Dictionary of keyword arguments.
25 // Dictionary of keyword arguments.
25 // events: $(Events) instance
26 // events: $(Events) instance
26 // config: dictionary
27 // config: dictionary
27 // keyboard_manager: KeyboardManager instance
28 // keyboard_manager: KeyboardManager instance
28 // notebook: Notebook instance
29 // notebook: Notebook instance
29 options = options || {};
30 options = options || {};
30
31
31 // in all TextCell/Cell subclasses
32 // in all TextCell/Cell subclasses
32 // do not assign most of members here, just pass it down
33 // do not assign most of members here, just pass it down
33 // in the options dict potentially overwriting what you wish.
34 // in the options dict potentially overwriting what you wish.
34 // they will be assigned in the base class.
35 // they will be assigned in the base class.
35 this.notebook = options.notebook;
36 this.notebook = options.notebook;
36 this.events = options.events;
37 this.events = options.events;
37 this.config = options.config;
38 this.config = options.config;
38
39
39 // we cannot put this as a class key as it has handle to "this".
40 // we cannot put this as a class key as it has handle to "this".
40 var cm_overwrite_options = {
41 var cm_overwrite_options = {
41 onKeyEvent: $.proxy(this.handle_keyevent,this)
42 onKeyEvent: $.proxy(this.handle_keyevent,this)
42 };
43 };
43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
44 var config = utils.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
44 Cell.apply(this, [{
45 Cell.apply(this, [{
45 config: config,
46 config: config,
46 keyboard_manager: options.keyboard_manager,
47 keyboard_manager: options.keyboard_manager,
47 events: this.events}]);
48 events: this.events}]);
48
49
49 this.cell_type = this.cell_type || 'text';
50 this.cell_type = this.cell_type || 'text';
50 mathjaxutils = mathjaxutils;
51 mathjaxutils = mathjaxutils;
51 this.rendered = false;
52 this.rendered = false;
52 };
53 };
53
54
54 TextCell.prototype = new Cell();
55 TextCell.prototype = new Cell();
55
56
56 TextCell.options_default = {
57 TextCell.options_default = {
57 cm_config : {
58 cm_config : {
58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
59 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
59 mode: 'htmlmixed',
60 mode: 'htmlmixed',
60 lineWrapping : true,
61 lineWrapping : true,
61 }
62 }
62 };
63 };
63
64
64
65
65 /**
66 /**
66 * Create the DOM element of the TextCell
67 * Create the DOM element of the TextCell
67 * @method create_element
68 * @method create_element
68 * @private
69 * @private
69 */
70 */
70 TextCell.prototype.create_element = function () {
71 TextCell.prototype.create_element = function () {
71 Cell.prototype.create_element.apply(this, arguments);
72 Cell.prototype.create_element.apply(this, arguments);
72
73
73 var cell = $("<div>").addClass('cell text_cell');
74 var cell = $("<div>").addClass('cell text_cell');
74 cell.attr('tabindex','2');
75 cell.attr('tabindex','2');
75
76
76 var prompt = $('<div/>').addClass('prompt input_prompt');
77 var prompt = $('<div/>').addClass('prompt input_prompt');
77 cell.append(prompt);
78 cell.append(prompt);
78 var inner_cell = $('<div/>').addClass('inner_cell');
79 var inner_cell = $('<div/>').addClass('inner_cell');
79 this.celltoolbar = new celltoolbar.CellToolbar({
80 this.celltoolbar = new celltoolbar.CellToolbar({
80 cell: this,
81 cell: this,
81 notebook: this.notebook});
82 notebook: this.notebook});
82 inner_cell.append(this.celltoolbar.element);
83 inner_cell.append(this.celltoolbar.element);
83 var input_area = $('<div/>').addClass('input_area');
84 var input_area = $('<div/>').addClass('input_area');
84 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
85 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
85 // The tabindex=-1 makes this div focusable.
86 // The tabindex=-1 makes this div focusable.
86 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
87 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
87 .attr('tabindex','-1');
88 .attr('tabindex','-1');
88 inner_cell.append(input_area).append(render_area);
89 inner_cell.append(input_area).append(render_area);
89 cell.append(inner_cell);
90 cell.append(inner_cell);
90 this.element = cell;
91 this.element = cell;
91 };
92 };
92
93
93
94
94 /**
95 /**
95 * Bind the DOM evet to cell actions
96 * Bind the DOM evet to cell actions
96 * Need to be called after TextCell.create_element
97 * Need to be called after TextCell.create_element
97 * @private
98 * @private
98 * @method bind_event
99 * @method bind_event
99 */
100 */
100 TextCell.prototype.bind_events = function () {
101 TextCell.prototype.bind_events = function () {
101 Cell.prototype.bind_events.apply(this);
102 Cell.prototype.bind_events.apply(this);
102 var that = this;
103 var that = this;
103
104
104 this.element.dblclick(function () {
105 this.element.dblclick(function () {
105 if (that.selected === false) {
106 if (that.selected === false) {
106 this.events.trigger('select.Cell', {'cell':that});
107 this.events.trigger('select.Cell', {'cell':that});
107 }
108 }
108 var cont = that.unrender();
109 var cont = that.unrender();
109 if (cont) {
110 if (cont) {
110 that.focus_editor();
111 that.focus_editor();
111 }
112 }
112 });
113 });
113 };
114 };
114
115
115 // Cell level actions
116 // Cell level actions
116
117
117 TextCell.prototype.select = function () {
118 TextCell.prototype.select = function () {
118 var cont = Cell.prototype.select.apply(this);
119 var cont = Cell.prototype.select.apply(this);
119 if (cont) {
120 if (cont) {
120 if (this.mode === 'edit') {
121 if (this.mode === 'edit') {
121 this.code_mirror.refresh();
122 this.code_mirror.refresh();
122 }
123 }
123 }
124 }
124 return cont;
125 return cont;
125 };
126 };
126
127
127 TextCell.prototype.unrender = function () {
128 TextCell.prototype.unrender = function () {
128 if (this.read_only) return;
129 if (this.read_only) return;
129 var cont = Cell.prototype.unrender.apply(this);
130 var cont = Cell.prototype.unrender.apply(this);
130 if (cont) {
131 if (cont) {
131 var text_cell = this.element;
132 var text_cell = this.element;
132 var output = text_cell.find("div.text_cell_render");
133 var output = text_cell.find("div.text_cell_render");
133 output.hide();
134 output.hide();
134 text_cell.find('div.input_area').show();
135 text_cell.find('div.input_area').show();
135 if (this.get_text() === this.placeholder) {
136 if (this.get_text() === this.placeholder) {
136 this.set_text('');
137 this.set_text('');
137 }
138 }
138 this.refresh();
139 this.refresh();
139 }
140 }
140 if (this.celltoolbar.ui_controls_list.length) {
141 if (this.celltoolbar.ui_controls_list.length) {
141 this.celltoolbar.show();
142 this.celltoolbar.show();
142 }
143 }
143 return cont;
144 return cont;
144 };
145 };
145
146
146 TextCell.prototype.execute = function () {
147 TextCell.prototype.execute = function () {
147 this.render();
148 this.render();
148 };
149 };
149
150
150 /**
151 /**
151 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
152 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
152 * @method get_text
153 * @method get_text
153 * @retrun {string} CodeMirror current text value
154 * @retrun {string} CodeMirror current text value
154 */
155 */
155 TextCell.prototype.get_text = function() {
156 TextCell.prototype.get_text = function() {
156 return this.code_mirror.getValue();
157 return this.code_mirror.getValue();
157 };
158 };
158
159
159 /**
160 /**
160 * @param {string} text - Codemiror text value
161 * @param {string} text - Codemiror text value
161 * @see TextCell#get_text
162 * @see TextCell#get_text
162 * @method set_text
163 * @method set_text
163 * */
164 * */
164 TextCell.prototype.set_text = function(text) {
165 TextCell.prototype.set_text = function(text) {
165 this.code_mirror.setValue(text);
166 this.code_mirror.setValue(text);
166 this.unrender();
167 this.unrender();
167 this.code_mirror.refresh();
168 this.code_mirror.refresh();
168 };
169 };
169
170
170 /**
171 /**
171 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
172 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
172 * @method get_rendered
173 * @method get_rendered
173 * */
174 * */
174 TextCell.prototype.get_rendered = function() {
175 TextCell.prototype.get_rendered = function() {
175 return this.element.find('div.text_cell_render').html();
176 return this.element.find('div.text_cell_render').html();
176 };
177 };
177
178
178 /**
179 /**
179 * @method set_rendered
180 * @method set_rendered
180 */
181 */
181 TextCell.prototype.set_rendered = function(text) {
182 TextCell.prototype.set_rendered = function(text) {
182 this.element.find('div.text_cell_render').html(text);
183 this.element.find('div.text_cell_render').html(text);
183 this.celltoolbar.hide();
184 this.celltoolbar.hide();
184 };
185 };
185
186
186
187
187 /**
188 /**
188 * Create Text cell from JSON
189 * Create Text cell from JSON
189 * @param {json} data - JSON serialized text-cell
190 * @param {json} data - JSON serialized text-cell
190 * @method fromJSON
191 * @method fromJSON
191 */
192 */
192 TextCell.prototype.fromJSON = function (data) {
193 TextCell.prototype.fromJSON = function (data) {
193 Cell.prototype.fromJSON.apply(this, arguments);
194 Cell.prototype.fromJSON.apply(this, arguments);
194 if (data.cell_type === this.cell_type) {
195 if (data.cell_type === this.cell_type) {
195 if (data.source !== undefined) {
196 if (data.source !== undefined) {
196 this.set_text(data.source);
197 this.set_text(data.source);
197 // make this value the starting point, so that we can only undo
198 // make this value the starting point, so that we can only undo
198 // to this state, instead of a blank cell
199 // to this state, instead of a blank cell
199 this.code_mirror.clearHistory();
200 this.code_mirror.clearHistory();
200 // TODO: This HTML needs to be treated as potentially dangerous
201 // TODO: This HTML needs to be treated as potentially dangerous
201 // user input and should be handled before set_rendered.
202 // user input and should be handled before set_rendered.
202 this.set_rendered(data.rendered || '');
203 this.set_rendered(data.rendered || '');
203 this.rendered = false;
204 this.rendered = false;
204 this.render();
205 this.render();
205 }
206 }
206 }
207 }
207 };
208 };
208
209
209 /** Generate JSON from cell
210 /** Generate JSON from cell
210 * @return {object} cell data serialised to json
211 * @return {object} cell data serialised to json
211 */
212 */
212 TextCell.prototype.toJSON = function () {
213 TextCell.prototype.toJSON = function () {
213 var data = Cell.prototype.toJSON.apply(this);
214 var data = Cell.prototype.toJSON.apply(this);
214 data.source = this.get_text();
215 data.source = this.get_text();
215 if (data.source == this.placeholder) {
216 if (data.source == this.placeholder) {
216 data.source = "";
217 data.source = "";
217 }
218 }
218 return data;
219 return data;
219 };
220 };
220
221
221
222
222 var MarkdownCell = function (options) {
223 var MarkdownCell = function (options) {
223 // Constructor
224 // Constructor
224 //
225 //
225 // Parameters:
226 // Parameters:
226 // options: dictionary
227 // options: dictionary
227 // Dictionary of keyword arguments.
228 // Dictionary of keyword arguments.
228 // events: $(Events) instance
229 // events: $(Events) instance
229 // config: dictionary
230 // config: dictionary
230 // keyboard_manager: KeyboardManager instance
231 // keyboard_manager: KeyboardManager instance
231 // notebook: Notebook instance
232 // notebook: Notebook instance
232 options = options || {};
233 options = options || {};
233 var config = this.mergeopt(MarkdownCell, options.config);
234 var config = utils.mergeopt(MarkdownCell, options.config);
234 TextCell.apply(this, [$.extend({}, options, {config: config})]);
235 TextCell.apply(this, [$.extend({}, options, {config: config})]);
235
236
236 this.cell_type = 'markdown';
237 this.cell_type = 'markdown';
237 };
238 };
238
239
239 MarkdownCell.options_default = {
240 MarkdownCell.options_default = {
240 cm_config: {
241 cm_config: {
241 mode: 'ipythongfm'
242 mode: 'ipythongfm'
242 },
243 },
243 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
244 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
244 };
245 };
245
246
246 MarkdownCell.prototype = new TextCell();
247 MarkdownCell.prototype = new TextCell();
247
248
248 /**
249 /**
249 * @method render
250 * @method render
250 */
251 */
251 MarkdownCell.prototype.render = function () {
252 MarkdownCell.prototype.render = function () {
252 var cont = TextCell.prototype.render.apply(this);
253 var cont = TextCell.prototype.render.apply(this);
253 if (cont) {
254 if (cont) {
254 var text = this.get_text();
255 var text = this.get_text();
255 var math = null;
256 var math = null;
256 if (text === "") { text = this.placeholder; }
257 if (text === "") { text = this.placeholder; }
257 var text_and_math = mathjaxutils.remove_math(text);
258 var text_and_math = mathjaxutils.remove_math(text);
258 text = text_and_math[0];
259 text = text_and_math[0];
259 math = text_and_math[1];
260 math = text_and_math[1];
260 var html = marked.parser(marked.lexer(text));
261 var html = marked.parser(marked.lexer(text));
261 html = mathjaxutils.replace_math(html, math);
262 html = mathjaxutils.replace_math(html, math);
262 html = security.sanitize_html(html);
263 html = security.sanitize_html(html);
263 html = $($.parseHTML(html));
264 html = $($.parseHTML(html));
264 // links in markdown cells should open in new tabs
265 // links in markdown cells should open in new tabs
265 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
266 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
266 this.set_rendered(html);
267 this.set_rendered(html);
267 this.element.find('div.input_area').hide();
268 this.element.find('div.input_area').hide();
268 this.element.find("div.text_cell_render").show();
269 this.element.find("div.text_cell_render").show();
269 this.typeset();
270 this.typeset();
270 }
271 }
271 return cont;
272 return cont;
272 };
273 };
273
274
274
275
275 var RawCell = function (options) {
276 var RawCell = function (options) {
276 // Constructor
277 // Constructor
277 //
278 //
278 // Parameters:
279 // Parameters:
279 // options: dictionary
280 // options: dictionary
280 // Dictionary of keyword arguments.
281 // Dictionary of keyword arguments.
281 // events: $(Events) instance
282 // events: $(Events) instance
282 // config: dictionary
283 // config: dictionary
283 // keyboard_manager: KeyboardManager instance
284 // keyboard_manager: KeyboardManager instance
284 // notebook: Notebook instance
285 // notebook: Notebook instance
285 options = options || {};
286 options = options || {};
286 var config = this.mergeopt(RawCell, options.config);
287 var config = utils.mergeopt(RawCell, options.config);
287 TextCell.apply(this, [$.extend({}, options, {config: config})]);
288 TextCell.apply(this, [$.extend({}, options, {config: config})]);
288
289
289 // RawCell should always hide its rendered div
290 // RawCell should always hide its rendered div
290 this.element.find('div.text_cell_render').hide();
291 this.element.find('div.text_cell_render').hide();
291 this.cell_type = 'raw';
292 this.cell_type = 'raw';
292 };
293 };
293
294
294 RawCell.options_default = {
295 RawCell.options_default = {
295 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
296 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
296 "It will not be rendered in the notebook. " +
297 "It will not be rendered in the notebook. " +
297 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
298 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
298 };
299 };
299
300
300 RawCell.prototype = new TextCell();
301 RawCell.prototype = new TextCell();
301
302
302 /** @method bind_events **/
303 /** @method bind_events **/
303 RawCell.prototype.bind_events = function () {
304 RawCell.prototype.bind_events = function () {
304 TextCell.prototype.bind_events.apply(this);
305 TextCell.prototype.bind_events.apply(this);
305 var that = this;
306 var that = this;
306 this.element.focusout(function() {
307 this.element.focusout(function() {
307 that.auto_highlight();
308 that.auto_highlight();
308 that.render();
309 that.render();
309 });
310 });
310
311
311 this.code_mirror.on('focus', function() { that.unrender(); });
312 this.code_mirror.on('focus', function() { that.unrender(); });
312 };
313 };
313
314
314 /**
315 /**
315 * Trigger autodetection of highlight scheme for current cell
316 * Trigger autodetection of highlight scheme for current cell
316 * @method auto_highlight
317 * @method auto_highlight
317 */
318 */
318 RawCell.prototype.auto_highlight = function () {
319 RawCell.prototype.auto_highlight = function () {
319 this._auto_highlight(this.config.raw_cell_highlight);
320 this._auto_highlight(this.config.raw_cell_highlight);
320 };
321 };
321
322
322 /** @method render **/
323 /** @method render **/
323 RawCell.prototype.render = function () {
324 RawCell.prototype.render = function () {
324 var cont = TextCell.prototype.render.apply(this);
325 var cont = TextCell.prototype.render.apply(this);
325 if (cont){
326 if (cont){
326 var text = this.get_text();
327 var text = this.get_text();
327 if (text === "") { text = this.placeholder; }
328 if (text === "") { text = this.placeholder; }
328 this.set_text(text);
329 this.set_text(text);
329 this.element.removeClass('rendered');
330 this.element.removeClass('rendered');
330 }
331 }
331 return cont;
332 return cont;
332 };
333 };
333
334
334
335
335 var HeadingCell = function (options) {
336 var HeadingCell = function (options) {
336 // Constructor
337 // Constructor
337 //
338 //
338 // Parameters:
339 // Parameters:
339 // options: dictionary
340 // options: dictionary
340 // Dictionary of keyword arguments.
341 // Dictionary of keyword arguments.
341 // events: $(Events) instance
342 // events: $(Events) instance
342 // config: dictionary
343 // config: dictionary
343 // keyboard_manager: KeyboardManager instance
344 // keyboard_manager: KeyboardManager instance
344 // notebook: Notebook instance
345 // notebook: Notebook instance
345 options = options || {};
346 options = options || {};
346 var config = this.mergeopt(HeadingCell, options.config);
347 var config = utils.mergeopt(HeadingCell, options.config);
347 TextCell.apply(this, [$.extend({}, options, {config: config})]);
348 TextCell.apply(this, [$.extend({}, options, {config: config})]);
348
349
349 this.level = 1;
350 this.level = 1;
350 this.cell_type = 'heading';
351 this.cell_type = 'heading';
351 };
352 };
352
353
353 HeadingCell.options_default = {
354 HeadingCell.options_default = {
354 cm_config: {
355 cm_config: {
355 theme: 'heading-1'
356 theme: 'heading-1'
356 },
357 },
357 placeholder: "Type Heading Here"
358 placeholder: "Type Heading Here"
358 };
359 };
359
360
360 HeadingCell.prototype = new TextCell();
361 HeadingCell.prototype = new TextCell();
361
362
362 /** @method fromJSON */
363 /** @method fromJSON */
363 HeadingCell.prototype.fromJSON = function (data) {
364 HeadingCell.prototype.fromJSON = function (data) {
364 if (data.level !== undefined){
365 if (data.level !== undefined){
365 this.level = data.level;
366 this.level = data.level;
366 }
367 }
367 TextCell.prototype.fromJSON.apply(this, arguments);
368 TextCell.prototype.fromJSON.apply(this, arguments);
368 this.code_mirror.setOption("theme", "heading-"+this.level);
369 this.code_mirror.setOption("theme", "heading-"+this.level);
369 };
370 };
370
371
371
372
372 /** @method toJSON */
373 /** @method toJSON */
373 HeadingCell.prototype.toJSON = function () {
374 HeadingCell.prototype.toJSON = function () {
374 var data = TextCell.prototype.toJSON.apply(this);
375 var data = TextCell.prototype.toJSON.apply(this);
375 data.level = this.get_level();
376 data.level = this.get_level();
376 return data;
377 return data;
377 };
378 };
378
379
379 /**
380 /**
380 * Change heading level of cell, and re-render
381 * Change heading level of cell, and re-render
381 * @method set_level
382 * @method set_level
382 */
383 */
383 HeadingCell.prototype.set_level = function (level) {
384 HeadingCell.prototype.set_level = function (level) {
384 this.level = level;
385 this.level = level;
385 this.code_mirror.setOption("theme", "heading-"+level);
386 this.code_mirror.setOption("theme", "heading-"+level);
386
387
387 if (this.rendered) {
388 if (this.rendered) {
388 this.rendered = false;
389 this.rendered = false;
389 this.render();
390 this.render();
390 }
391 }
391 };
392 };
392
393
393 /** The depth of header cell, based on html (h1 to h6)
394 /** The depth of header cell, based on html (h1 to h6)
394 * @method get_level
395 * @method get_level
395 * @return {integer} level - for 1 to 6
396 * @return {integer} level - for 1 to 6
396 */
397 */
397 HeadingCell.prototype.get_level = function () {
398 HeadingCell.prototype.get_level = function () {
398 return this.level;
399 return this.level;
399 };
400 };
400
401
401
402
402 HeadingCell.prototype.get_rendered = function () {
403 HeadingCell.prototype.get_rendered = function () {
403 var r = this.element.find("div.text_cell_render");
404 var r = this.element.find("div.text_cell_render");
404 return r.children().first().html();
405 return r.children().first().html();
405 };
406 };
406
407
407 HeadingCell.prototype.render = function () {
408 HeadingCell.prototype.render = function () {
408 var cont = TextCell.prototype.render.apply(this);
409 var cont = TextCell.prototype.render.apply(this);
409 if (cont) {
410 if (cont) {
410 var text = this.get_text();
411 var text = this.get_text();
411 var math = null;
412 var math = null;
412 // Markdown headings must be a single line
413 // Markdown headings must be a single line
413 text = text.replace(/\n/g, ' ');
414 text = text.replace(/\n/g, ' ');
414 if (text === "") { text = this.placeholder; }
415 if (text === "") { text = this.placeholder; }
415 text = new Array(this.level + 1).join("#") + " " + text;
416 text = new Array(this.level + 1).join("#") + " " + text;
416 var text_and_math = mathjaxutils.remove_math(text);
417 var text_and_math = mathjaxutils.remove_math(text);
417 text = text_and_math[0];
418 text = text_and_math[0];
418 math = text_and_math[1];
419 math = text_and_math[1];
419 var html = marked.parser(marked.lexer(text));
420 var html = marked.parser(marked.lexer(text));
420 html = mathjaxutils.replace_math(html, math);
421 html = mathjaxutils.replace_math(html, math);
421 html = security.sanitize_html(html);
422 html = security.sanitize_html(html);
422 var h = $($.parseHTML(html));
423 var h = $($.parseHTML(html));
423 // add id and linkback anchor
424 // add id and linkback anchor
424 var hash = h.text().replace(/ /g, '-');
425 var hash = h.text().replace(/ /g, '-');
425 h.attr('id', hash);
426 h.attr('id', hash);
426 h.append(
427 h.append(
427 $('<a/>')
428 $('<a/>')
428 .addClass('anchor-link')
429 .addClass('anchor-link')
429 .attr('href', '#' + hash)
430 .attr('href', '#' + hash)
430 .text('¶')
431 .text('¶')
431 );
432 );
432 this.set_rendered(h);
433 this.set_rendered(h);
433 this.element.find('div.input_area').hide();
434 this.element.find('div.input_area').hide();
434 this.element.find("div.text_cell_render").show();
435 this.element.find("div.text_cell_render").show();
435 this.typeset();
436 this.typeset();
436 }
437 }
437 return cont;
438 return cont;
438 };
439 };
439
440
440 // Backwards compatability.
441 // Backwards compatability.
441 IPython.TextCell = TextCell;
442 IPython.TextCell = TextCell;
442 IPython.MarkdownCell = MarkdownCell;
443 IPython.MarkdownCell = MarkdownCell;
443 IPython.RawCell = RawCell;
444 IPython.RawCell = RawCell;
444 IPython.HeadingCell = HeadingCell;
445 IPython.HeadingCell = HeadingCell;
445
446
446 var textcell = {
447 var textcell = {
447 'TextCell': TextCell,
448 'TextCell': TextCell,
448 'MarkdownCell': MarkdownCell,
449 'MarkdownCell': MarkdownCell,
449 'RawCell': RawCell,
450 'RawCell': RawCell,
450 'HeadingCell': HeadingCell,
451 'HeadingCell': HeadingCell,
451 };
452 };
452 return textcell;
453 return textcell;
453 });
454 });
General Comments 0
You need to be logged in to leave comments. Login now