Permalink
Browse files

use ansiparse to parse colors faster

  • Loading branch information...
1 parent 2fc5a7d commit 52817d528f5672f121d9f5661a07f570f16aa47a @mrbrdo committed Jun 11, 2012
Showing with 236 additions and 59 deletions.
  1. +236 −59 public/webconsole.js
View
@@ -1,5 +1,193 @@
(function($) {
+/* AnsiParse */
+ansiparse = function (str) {
+ //
+ // I'm terrible at writing parsers.
+ //
+ var matchingControl = null,
+ matchingData = null,
+ matchingText = '',
+ ansiState = [],
+ result = [],
+ state = {},
+ eraseChar;
+
+ //
+ // General workflow for this thing is:
+ // \033\[33mText
+ // | | |
+ // | | matchingText
+ // | matchingData
+ // matchingControl
+ //
+ // In further steps we hope it's all going to be fine. It usually is.
+ //
+
+ //
+ // Erases a char from the output
+ //
+ eraseChar = function () {
+ var index, text;
+ if (matchingText.length) {
+ matchingText = matchingText.substr(0, matchingText.length - 1);
+ }
+ else if (result.length) {
+ index = result.length - 1;
+ text = result[index].text;
+ if (text.length === 1) {
+ //
+ // A result bit was fully deleted, pop it out to simplify the final output
+ //
+ result.pop();
+ }
+ else {
+ result[index].text = text.substr(0, text.length - 1);
+ }
+ }
+ };
+
+ for (var i = 0; i < str.length; i++) {
+ if (matchingControl != null) {
+ if (matchingControl == '\u001B' && str[i] == '\[') {
+ //
+ // We've matched full control code. Lets start matching formating data.
+ //
+
+ //
+ // "emit" matched text with correct state
+ //
+ if (matchingText) {
+ state.text = matchingText;
+ result.push(state);
+ state = {};
+ matchingText = "";
+ }
+
+ matchingControl = null;
+ matchingData = '';
+ }
+ else {
+ //
+ // We failed to match anything - most likely a bad control code. We
+ // go back to matching regular strings.
+ //
+ matchingText += matchingControl + str[i];
+ matchingControl = null;
+ }
+ continue;
+ }
+ else if (matchingData != null) {
+ if (str[i] == ';') {
+ //
+ // `;` separates many formatting codes, for example: `\033[33;43m`
+ // means that both `33` and `43` should be applied.
+ //
+ // TODO: this can be simplified by modifying state here.
+ //
+ ansiState.push(matchingData);
+ matchingData = '';
+ }
+ else if (str[i] == 'm') {
+ //
+ // `m` finished whole formatting code. We can proceed to matching
+ // formatted text.
+ //
+ ansiState.push(matchingData);
+ matchingData = null;
+ matchingText = '';
+
+ //
+ // Convert matched formatting data into user-friendly state object.
+ //
+ // TODO: DRY.
+ //
+ ansiState.forEach(function (ansiCode) {
+ if (ansiparse.foregroundColors[ansiCode]) {
+ state.foreground = ansiparse.foregroundColors[ansiCode];
+ }
+ else if (ansiparse.backgroundColors[ansiCode]) {
+ state.background = ansiparse.backgroundColors[ansiCode];
+ }
+ else if (ansiCode == 39) {
+ delete state.foreground;
+ }
+ else if (ansiCode == 49) {
+ delete state.background;
+ }
+ else if (ansiparse.styles[ansiCode]) {
+ state[ansiparse.styles[ansiCode]] = true;
+ }
+ else if (ansiCode == 22) {
+ state.bold = false;
+ }
+ else if (ansiCode == 23) {
+ state.italic = false;
+ }
+ else if (ansiCode == 24) {
+ state.underline = false;
+ }
+ });
+ ansiState = [];
+ }
+ else {
+ matchingData += str[i];
+ }
+ continue;
+ }
+
+ if (str[i] == '\u001B') {
+ matchingControl = str[i];
+ }
+ else if (str[i] == '\u0008') {
+ eraseChar();
+ }
+ else {
+ matchingText += str[i];
+ }
+ }
+
+ if (matchingText) {
+ state.text = matchingText + (matchingControl ? matchingControl : '');
+ result.push(state);
+ }
+ return result;
+}
+
+ansiparse.foregroundColors = {
+ '30': 'black',
+ '31': 'red',
+ '32': 'green',
+ '33': 'yellow',
+ '34': 'blue',
+ '35': 'magenta',
+ '36': 'cyan',
+ '37': 'white',
+ '90': 'grey'
+};
+
+ansiparse.backgroundColors = {
+ '40': 'black',
+ '41': 'red',
+ '42': 'green',
+ '43': 'yellow',
+ '44': 'blue',
+ '45': 'magenta',
+ '46': 'cyan',
+ '47': 'white'
+};
+
+ansiparse.styles = {
+ '1': 'bold',
+ '3': 'italic',
+ '4': 'underline'
+};
+
+if (typeof module == "object" && typeof window == "undefined") {
+ module.exports = ansiparse;
+}
+
+/* app */
var webconsole = {
history:[],
pointer:0,
@@ -10,72 +198,59 @@
e.preventDefault();
});
- var prevStyle = {
- color: "#ffffff",
- bold: false,
- underline: false
- }
-
// colors
var colors = {
- 30: "#eeeeee",
- 31: "#ff6c60",
- 32: "#a8ff60",
- 33: "#ffffb6",
- 34: "#96cbfe",
- 35: "#ff73fd",
- 36: "#c6c5fe",
- 37: "#eeeeee"
+ 'black': "#eeeeee",
+ 'red': "#ff6c60",
+ 'green': "#a8ff60",
+ 'yellow': "#ffffb6",
+ 'blue': "#96cbfe",
+ 'magenta': "#ff73fd",
+ 'cyan': "#c6c5fe",
+ 'white': "#eeeeee"
}
var boldColors = {
- 30: "#7c7c7c",
- 31: "#ffb6b0",
- 32: "#ceffac",
- 33: "#ffffcb",
- 34: "#b5dcfe",
- 35: "#ff9cfe",
- 36: "#dfdffe",
- 37: "#ffffff"
+ 'black': "#7c7c7c",
+ 'red': "#ffb6b0",
+ 'green': "#ceffac",
+ 'yellow': "#ffffcb",
+ 'blue': "#b5dcfe",
+ 'magenta': "#ff9cfe",
+ 'cyan': "#dfdffe",
+ 'white': "#ffffff"
}
- function resetBashStyle()
+ function subColor(color, elem)
{
- prevStyle = {
- color: colors[37],
- bold: 'normal',
- underline: 'none'
- };
+ if (color == undefined) color = 'white';
+ if (elem.bold && boldColors[color] != undefined) {
+ color = boldColors[color];
+ } else if (!elem.bold && colors[color] != undefined) {
+ color = colors[color];
+ }
+ return color;
}
- function bashColorToHtml(bcolor)
+ function parseBashString(str, into)
{
- // set values
- var all = bcolor.split(/;/g)
- if (all.indexOf("0") > 0) // ignore anything before 0, since 0 resets
- all.splice(0, all.indexOf("0"));
- if (all.indexOf("0") >= 0)
- resetBashStyle();
- if (all.indexOf("1") >= 0)
- prevStyle['bold'] = 'bold';
- if (all.indexOf("4") >= 0)
- prevStyle['underline'] = 'underline';
- if (prevStyle['bold'] == 'bold')
- colorMap = boldColors;
- else
- colorMap = colors;
- $.each(all, function(idx, val) {
- var i = parseInt(val);
- if (i > 10 && colorMap[i] != undefined)
- prevStyle['color'] = colorMap[i];
+ $.each(ansiparse(str), function(idx, elem) {
+ if (elem.text.length == 1 && elem.text[0] == '\n') {
+ into.append("<br>");
+ return true;
+ }
+ var domElem = $('<span></span>');
+ domElem.text(elem.text);
+ domElem.html(domElem.text().replace(/\n/g, "<br>"));
+ domElem.css('color', subColor(elem.foreground, elem));
+ if (elem.bold)
+ domElem.css('font-weight', 'bold');
+ if (elem.italic)
+ domElem.css('font-style', 'italic');
+ if (elem.underline)
+ domElem.css('text-decoration', 'underline');
+ if (elem.background)
+ domElem.css('background-color', subColor(elem.background, elem));
+ into.append(domElem);
});
- return 'color:'+prevStyle['color']+';font-weight:'+prevStyle['bold']+
- ';text-decoration:'+prevStyle['underline'];
- }
- function parseBashString(str)
- {
- str = str.replace(/\u001B\[([0-9;]+)m/g, function(fm, sm) {
- return '</span><span style="'+bashColorToHtml(sm)+'">';
- }).replace(/\n/g, "<br>");
- return '<span>'+str+'</span>';
}
$("#rack-webconsole form input").keyup(function(event) {
function escapeHTML(string) {
@@ -97,10 +272,12 @@
data: ({query: webconsole.query.val(), token: "$TOKEN"}),
success: function (data) {
var query_class = data.previous_multi_line ? 'query_multiline' : 'query';
- var result = "<div class='" + query_class + "'>" +
- parseBashString(escapeHTML(data.prompt)) + "</div>";
+ var result = $("<div class='" + query_class + "'></div>");
+ parseBashString(escapeHTML(data.prompt), result);
if (!data.multi_line) {
- result += "<div class='result'>" + parseBashString(escapeHTML(data.result)) + "</div>";
+ var mresult = $("<div class='result'></div>");
+ parseBashString(escapeHTML(data.result), mresult);
+ result.append(mresult);
}
$("#rack-webconsole .results").append(result);
$("#rack-webconsole .results_wrapper").scrollTop(

0 comments on commit 52817d5

Please sign in to comment.