|
4 | 4 | define([
|
5 | 5 | 'codemirror/lib/codemirror',
|
6 | 6 | 'moment',
|
| 7 | + 'underscore', |
7 | 8 | // silently upgrades CodeMirror
|
8 | 9 | 'codemirror/mode/meta',
|
9 |
| -], function(CodeMirror, moment){ |
| 10 | +], function(CodeMirror, moment, _){ |
10 | 11 | "use strict";
|
11 | 12 |
|
12 | 13 | // keep track of which extensions have been loaded already
|
@@ -192,165 +193,222 @@ define([
|
192 | 193 | return uuid;
|
193 | 194 | };
|
194 | 195 |
|
195 |
| - |
196 |
| - //Fix raw text to parse correctly in crazy XML |
197 |
| - function xmlencode(string) { |
198 |
| - return string.replace(/\&/g,'&'+'amp;') |
199 |
| - .replace(/</g,'&'+'lt;') |
200 |
| - .replace(/>/g,'&'+'gt;') |
201 |
| - .replace(/\'/g,'&'+'apos;') |
202 |
| - .replace(/\"/g,'&'+'quot;') |
203 |
| - .replace(/`/g,'&'+'#96;'); |
| 196 | + var _ANSI_COLORS = [ |
| 197 | + "ansi-black", |
| 198 | + "ansi-red", |
| 199 | + "ansi-green", |
| 200 | + "ansi-yellow", |
| 201 | + "ansi-blue", |
| 202 | + "ansi-magenta", |
| 203 | + "ansi-cyan", |
| 204 | + "ansi-white", |
| 205 | + "ansi-black-intense", |
| 206 | + "ansi-red-intense", |
| 207 | + "ansi-green-intense", |
| 208 | + "ansi-yellow-intense", |
| 209 | + "ansi-blue-intense", |
| 210 | + "ansi-magenta-intense", |
| 211 | + "ansi-cyan-intense", |
| 212 | + "ansi-white-intense", |
| 213 | + ]; |
| 214 | + |
| 215 | + function _parseNumbers(text) { |
| 216 | + var numbers = text.split(";"); |
| 217 | + numbers = numbers.map(text => text ? Number.parseInt(text) : 0); |
| 218 | + if (numbers.some(Number.isNaN)) { |
| 219 | + numbers = []; // Ignored: Invalid color specification |
| 220 | + } |
| 221 | + return numbers; |
204 | 222 | }
|
205 | 223 |
|
206 |
| - |
207 |
| - //Map from terminal commands to CSS classes |
208 |
| - var ansi_colormap = { |
209 |
| - "01":"ansibold", |
210 |
| - |
211 |
| - "30":"ansiblack", |
212 |
| - "31":"ansired", |
213 |
| - "32":"ansigreen", |
214 |
| - "33":"ansiyellow", |
215 |
| - "34":"ansiblue", |
216 |
| - "35":"ansipurple", |
217 |
| - "36":"ansicyan", |
218 |
| - "37":"ansigray", |
219 |
| - |
220 |
| - "40":"ansibgblack", |
221 |
| - "41":"ansibgred", |
222 |
| - "42":"ansibggreen", |
223 |
| - "43":"ansibgyellow", |
224 |
| - "44":"ansibgblue", |
225 |
| - "45":"ansibgpurple", |
226 |
| - "46":"ansibgcyan", |
227 |
| - "47":"ansibggray" |
228 |
| - }; |
229 |
| - |
230 |
| - function _process_numbers(attrs, numbers) { |
231 |
| - // process ansi escapes |
| 224 | + function _getExtendedColors(numbers) { |
| 225 | + var r, g, b; |
232 | 226 | var n = numbers.shift();
|
233 |
| - if (ansi_colormap[n]) { |
234 |
| - if ( ! attrs["class"] ) { |
235 |
| - attrs["class"] = ansi_colormap[n]; |
236 |
| - } else { |
237 |
| - attrs["class"] += " " + ansi_colormap[n]; |
238 |
| - } |
239 |
| - } else if (n == "38" || n == "48") { |
240 |
| - // VT100 256 color or 24 bit RGB |
241 |
| - if (numbers.length < 2) { |
242 |
| - console.log("Not enough fields for VT100 color", numbers); |
243 |
| - return; |
| 227 | + if (n === 2 && numbers.length >= 3) { |
| 228 | + // 24-bit RGB |
| 229 | + r = numbers.shift(); |
| 230 | + g = numbers.shift(); |
| 231 | + b = numbers.shift(); |
| 232 | + if ([r, g, b].some(c => c < 0 || 255 < c)) { |
| 233 | + throw new RangeError(); |
244 | 234 | }
|
245 |
| - |
246 |
| - var index_or_rgb = numbers.shift(); |
247 |
| - var r,g,b; |
248 |
| - if (index_or_rgb == "5") { |
249 |
| - // 256 color |
250 |
| - var idx = parseInt(numbers.shift(), 10); |
251 |
| - if (idx < 16) { |
252 |
| - // indexed ANSI |
253 |
| - // ignore bright / non-bright distinction |
254 |
| - idx = idx % 8; |
255 |
| - var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()]; |
256 |
| - if ( ! attrs["class"] ) { |
257 |
| - attrs["class"] = ansiclass; |
258 |
| - } else { |
259 |
| - attrs["class"] += " " + ansiclass; |
260 |
| - } |
261 |
| - return; |
262 |
| - } else if (idx < 232) { |
263 |
| - // 216 color 6x6x6 RGB |
264 |
| - idx = idx - 16; |
265 |
| - b = idx % 6; |
266 |
| - g = Math.floor(idx / 6) % 6; |
267 |
| - r = Math.floor(idx / 36) % 6; |
268 |
| - // convert to rgb |
269 |
| - r = (r * 51); |
270 |
| - g = (g * 51); |
271 |
| - b = (b * 51); |
272 |
| - } else { |
273 |
| - // grayscale |
274 |
| - idx = idx - 231; |
275 |
| - // it's 1-24 and should *not* include black or white, |
276 |
| - // so a 26 point scale |
277 |
| - r = g = b = Math.floor(idx * 256 / 26); |
278 |
| - } |
279 |
| - } else if (index_or_rgb == "2") { |
280 |
| - // Simple 24 bit RGB |
281 |
| - if (numbers.length > 3) { |
282 |
| - console.log("Not enough fields for RGB", numbers); |
283 |
| - return; |
284 |
| - } |
285 |
| - r = numbers.shift(); |
286 |
| - g = numbers.shift(); |
287 |
| - b = numbers.shift(); |
| 235 | + } else if (n === 5 && numbers.length >= 1) { |
| 236 | + // 256 colors |
| 237 | + var idx = numbers.shift(); |
| 238 | + if (idx < 0) { |
| 239 | + throw new RangeError(); |
| 240 | + } else if (idx < 16) { |
| 241 | + // 16 default terminal colors |
| 242 | + return idx; |
| 243 | + } else if (idx < 232) { |
| 244 | + // 6x6x6 color cube, see http://stackoverflow.com/a/27165165/500098 |
| 245 | + r = Math.floor((idx - 16) / 36); |
| 246 | + r = r > 0 ? 55 + r * 40 : 0; |
| 247 | + g = Math.floor(((idx - 16) % 36) / 6); |
| 248 | + g = g > 0 ? 55 + g * 40 : 0; |
| 249 | + b = (idx - 16) % 6; |
| 250 | + b = b > 0 ? 55 + b * 40 : 0; |
| 251 | + } else if (idx < 256) { |
| 252 | + // grayscale, see http://stackoverflow.com/a/27165165/500098 |
| 253 | + r = g = b = (idx - 232) * 10 + 8; |
288 | 254 | } else {
|
289 |
| - console.log("unrecognized control", numbers); |
290 |
| - return; |
291 |
| - } |
292 |
| - if (r !== undefined) { |
293 |
| - // apply the rgb color |
294 |
| - var line; |
295 |
| - if (n == "38") { |
296 |
| - line = "color: "; |
297 |
| - } else { |
298 |
| - line = "background-color: "; |
299 |
| - } |
300 |
| - line = line + "rgb(" + r + "," + g + "," + b + ");"; |
301 |
| - if ( !attrs.style ) { |
302 |
| - attrs.style = line; |
303 |
| - } else { |
304 |
| - attrs.style += " " + line; |
305 |
| - } |
| 255 | + throw new RangeError(); |
306 | 256 | }
|
| 257 | + } else { |
| 258 | + throw new RangeError(); |
307 | 259 | }
|
| 260 | + return [r, g, b]; |
308 | 261 | }
|
309 | 262 |
|
310 | 263 | function _ansispan(str) {
|
311 |
| - // ansispan function adapted from github.com/mmalecki/ansispan (MIT License) |
312 |
| - // regular ansi escapes (using the table above) |
313 |
| - var is_open = false; |
314 |
| - return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) { |
315 |
| - if (!pattern || prefix === '39') { |
316 |
| - // [(01|22|39|)m close spans |
317 |
| - if (is_open) { |
318 |
| - is_open = false; |
319 |
| - return "</span>"; |
320 |
| - } else { |
321 |
| - return ""; |
322 |
| - } |
| 264 | + var ansi_re = /\x1b\[(.*?)([@-~])/g; |
| 265 | + var fg = []; |
| 266 | + var bg = []; |
| 267 | + var bold = false; |
| 268 | + var match; |
| 269 | + var out = []; |
| 270 | + var numbers = []; |
| 271 | + var start = 0; |
| 272 | + |
| 273 | + str += "\x1b[m"; // Ensure markup for trailing text |
| 274 | + while ((match = ansi_re.exec(str))) { |
| 275 | + if (match[2] === "m") { |
| 276 | + numbers = _parseNumbers(match[1]); |
323 | 277 | } else {
|
324 |
| - is_open = true; |
| 278 | + // Ignored: Not a color code |
| 279 | + } |
| 280 | + var chunk = str.substring(start, match.index); |
| 281 | + if (chunk) { |
| 282 | + if (bold && 0 <= fg && fg < 8) { |
| 283 | + fg += 8; // Bold text uses "intense" colors |
| 284 | + } |
| 285 | + var classes = []; |
| 286 | + var styles = []; |
325 | 287 |
|
326 |
| - // consume sequence of color escapes |
327 |
| - var numbers = pattern.match(/\d+/g); |
328 |
| - var attrs = {}; |
329 |
| - while (numbers.length > 0) { |
330 |
| - _process_numbers(attrs, numbers); |
| 288 | + if (typeof fg === "number") { |
| 289 | + classes.push(_ANSI_COLORS[fg] + "-fg"); |
| 290 | + } else if (fg.length) { |
| 291 | + styles.push(`color: rgb(${fg})`); |
331 | 292 | }
|
332 | 293 |
|
333 |
| - var span = "<span "; |
334 |
| - Object.keys(attrs).map(function (attr) { |
335 |
| - span = span + " " + attr + '="' + attrs[attr] + '"'; |
336 |
| - }); |
337 |
| - return span + ">"; |
| 294 | + if (typeof bg === "number") { |
| 295 | + classes.push(_ANSI_COLORS[bg] + "-bg"); |
| 296 | + } else if (bg.length) { |
| 297 | + styles.push(`background-color: rgb(${bg})`); |
| 298 | + } |
| 299 | + |
| 300 | + if (bold) { |
| 301 | + classes.push("ansi-bold"); |
| 302 | + } |
| 303 | + |
| 304 | + if (classes.length || styles.length) { |
| 305 | + out.push("<span"); |
| 306 | + if (classes.length) { |
| 307 | + out.push(` class="${classes.join(" ")}"`); |
| 308 | + } |
| 309 | + if (styles.length) { |
| 310 | + out.push(` style="${styles.join("; ")}"`); |
| 311 | + } |
| 312 | + out.push(">"); |
| 313 | + out.push(chunk); |
| 314 | + out.push("</span>"); |
| 315 | + } else { |
| 316 | + out.push(chunk); |
| 317 | + } |
338 | 318 | }
|
339 |
| - }); |
| 319 | + start = ansi_re.lastIndex; |
| 320 | + |
| 321 | + while (numbers.length) { |
| 322 | + var n = numbers.shift(); |
| 323 | + switch (n) { |
| 324 | + case 0: |
| 325 | + fg = bg = []; |
| 326 | + bold = false; |
| 327 | + break; |
| 328 | + case 1: |
| 329 | + case 5: |
| 330 | + bold = true; |
| 331 | + break; |
| 332 | + case 21: |
| 333 | + case 22: |
| 334 | + bold = false; |
| 335 | + break; |
| 336 | + case 30: |
| 337 | + case 31: |
| 338 | + case 32: |
| 339 | + case 33: |
| 340 | + case 34: |
| 341 | + case 35: |
| 342 | + case 36: |
| 343 | + case 37: |
| 344 | + fg = n - 30; |
| 345 | + break; |
| 346 | + case 38: |
| 347 | + try { |
| 348 | + fg = _getExtendedColors(numbers); |
| 349 | + } catch(e) { |
| 350 | + numbers.length = 0; |
| 351 | + } |
| 352 | + break; |
| 353 | + case 39: |
| 354 | + fg = []; |
| 355 | + break; |
| 356 | + case 40: |
| 357 | + case 41: |
| 358 | + case 42: |
| 359 | + case 43: |
| 360 | + case 44: |
| 361 | + case 45: |
| 362 | + case 46: |
| 363 | + case 47: |
| 364 | + bg = n - 40; |
| 365 | + break; |
| 366 | + case 48: |
| 367 | + try { |
| 368 | + bg = _getExtendedColors(numbers); |
| 369 | + } catch(e) { |
| 370 | + numbers.length = 0; |
| 371 | + } |
| 372 | + break; |
| 373 | + case 49: |
| 374 | + bg = []; |
| 375 | + break; |
| 376 | + case 90: |
| 377 | + case 91: |
| 378 | + case 92: |
| 379 | + case 93: |
| 380 | + case 94: |
| 381 | + case 95: |
| 382 | + case 96: |
| 383 | + case 97: |
| 384 | + fg = n - 90 + 8; |
| 385 | + break; |
| 386 | + case 100: |
| 387 | + case 101: |
| 388 | + case 102: |
| 389 | + case 103: |
| 390 | + case 104: |
| 391 | + case 105: |
| 392 | + case 106: |
| 393 | + case 107: |
| 394 | + bg = n - 100 + 8; |
| 395 | + break; |
| 396 | + default: |
| 397 | + // Unknown codes are ignored |
| 398 | + } |
| 399 | + } |
| 400 | + } |
| 401 | + return out.join(""); |
340 | 402 | }
|
341 | 403 |
|
342 |
| - // Transform ANSI color escape codes into HTML <span> tags with css |
343 |
| - // classes listed in the above ansi_colormap object. The actual color used |
344 |
| - // are set in the css file. |
| 404 | + // Transform ANSI color escape codes into HTML <span> tags with CSS |
| 405 | + // classes such as "ansi-green-intense-fg". |
| 406 | + // The actual colors used are set in the CSS file. |
| 407 | + // This is supposed to have the same behavior as nbconvert.filters.ansi2html() |
345 | 408 | function fixConsole(txt) {
|
346 |
| - txt = xmlencode(txt); |
| 409 | + txt = _.escape(txt); |
347 | 410 |
|
348 |
| - // Strip all ANSI codes that are not color related. Matches |
349 |
| - // all ANSI codes that do not end with "m". |
350 |
| - var ignored_re = /(?=(\033\[[?\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g; |
351 |
| - txt = txt.replace(ignored_re, ""); |
352 |
| - |
353 |
| - // color ansi codes |
| 411 | + // color ansi codes (and remove non-color escape sequences) |
354 | 412 | txt = _ansispan(txt);
|
355 | 413 | return txt;
|
356 | 414 | }
|
|
0 commit comments