(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, query:$('#webconsole_query') } $('#rack-webconsole form').submit(function(e){ e.preventDefault(); }); // colors var colors = { 'black': "#eeeeee", 'red': "#ff6c60", 'green': "#a8ff60", 'yellow': "#ffffb6", 'blue': "#96cbfe", 'magenta': "#ff73fd", 'cyan': "#c6c5fe", 'white': "#eeeeee" } var boldColors = { 'black': "#7c7c7c", 'red': "#ffb6b0", 'green': "#ceffac", 'yellow': "#ffffcb", 'blue': "#b5dcfe", 'magenta': "#ff9cfe", 'cyan': "#dfdffe", 'white': "#ffffff" } function subColor(color, elem) { 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 parseBashString(str, into) { $.each(ansiparse(str), function(idx, elem) { if (elem.text.length == 1 && elem.text[0] == '\n') { into.append("
"); return true; } var domElem = $(''); domElem.text(elem.text); domElem.html(domElem.text().replace(/\n/g, "
")); 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); }); } $("#rack-webconsole form input").keyup(function(event) { function escapeHTML(string) { return(string.replace(/&/g,'&'). replace(/>/g,'>'). replace(/"); parseBashString(escapeHTML(data.prompt), result); if (!data.multi_line) { var mresult = $("
"); parseBashString(escapeHTML(data.result), mresult); result.append(mresult); } $("#rack-webconsole .results").append(result); $("#rack-webconsole .results_wrapper").scrollTop( $("#rack-webconsole .results").height() ); } }); webconsole.query.val(''); } // up if (event.which == 38) { if (webconsole.pointer < 0) { webconsole.query.val(''); } else { if (webconsole.pointer == webconsole.history.length) { webconsole.pointer = webconsole.history.length - 1; } webconsole.query.val(webconsole.history[webconsole.pointer]); webconsole.pointer--; } } // down if (event.which == 40) { if (webconsole.pointer == webconsole.history.length) { webconsole.query.val(''); } else { if (webconsole.pointer < 0) { webconsole.pointer = 0; } webconsole.query.val(webconsole.history[webconsole.pointer]); webconsole.pointer++; } } }); $(document).ready(function() { $(this).keypress(function(event) { if ($KEY_CODE.indexOf(event.which) >= 0) { $("#rack-webconsole").slideToggle('fast', function() { if ($(this).is(':visible')) { $("#rack-webconsole form input").focus(); $("#rack-webconsole .results_wrapper").scrollTop( $("#rack-webconsole .results").height() ); } else { $("#rack-webconsole form input").blur(); } }); event.preventDefault(); } }); }); })(jQuery);