const TERM_X_MIN = 60; const TERM_Y_MIN = 10; // the following 2 values are empirical determined in chromium const TERM_CELL_HEIGHT = 16; const TERM_CELL_WIDTH = 8; const ANSI_NO = 0; const ANSI_ESC = 1; const ANSI_CSI1 = 2; const ANSI_CSI2 = 3; const ANSI_CSI3 = 4; const ANSI_CUU = 5; const ANSI_CUD = 6; const ANSI_CUF = 7; const ANSI_CUB = 8; const ANSI_CNL = 9; const ANSI_CPL = 10; const ANSI_CHA = 11; const ANSI_CUP = 12; const ANSI_ED = 13; const ANSI_EL = 14; const ANSI_SCP = 15; const ANSI_RCP = 16; const ANSI_DECTCEM = 17; const ANSI_DSR = 18; const ANSI_SGR = 19; var Terminal = function() { } Terminal.prototype.xSize; Terminal.prototype.ySize; Terminal.prototype.xPosition = 0; Terminal.prototype.yPosition = 0; Terminal.prototype.xPositionSaved = 0; Terminal.prototype.yPositionSaved = 0; Terminal.prototype.tickId; Terminal.prototype.staticNoShift = 4; Terminal.prototype.color = "white"; Terminal.prototype.backgroundColor = "black"; Terminal.prototype.bold = false; Terminal.prototype.displayCursor = true; Terminal.prototype.calculateSize = function(width, height) { this.xSize = parseInt(width / (TERM_CELL_WIDTH)); if (this.xSize < TERM_X_MIN) this.xSize = TERM_X_MIN; this.ySize = parseInt(height / (TERM_CELL_HEIGHT)); if (this.ySize < TERM_Y_MIN) this.ySize = TERM_Y_MIN; console.log("Terminal resolution is: " + this.xSize + "x" + this.ySize); } Terminal.prototype.getBasicHTML = function() { var code = ""; code += ""; for (var i = 0; i < this.ySize; i++) { code += ""; for (var j = 0; j < this.xSize; j++) code += "" code += ""; } code += "
"; code += this.getInputHTML(); return code; } Terminal.prototype.getInputHTML = function() { var code = "\
\ \
"; return code; } Terminal.prototype.init = function() { window.onkeypress = this.handleKeyPress; window.onkeydown = this.handleKeyDown; this.tickId = window.setInterval(this.tick, 10); document.getElementById("input").focus(); this.cursorOn(); } Terminal.prototype.tick = function() { } Terminal.prototype.globalLineFeed = function() { for (var i = this.staticNoShift + 1; i < this.ySize; i++) { for (var j = 0; j < this.xSize; j++) { var to = document.getElementsByClassName((i - 1) + "c" + j)[0]; var from = document.getElementsByClassName(i + "c" + j)[0]; to.innerHTML = from.innerHTML; to.style.color = from.style.color; to.style.fontWeight = from.style.fontWeight; to.style.backgroundColor = from.style.backgroundColor; } } for (var j = 0; j < this.xSize; j++) { var to = document.getElementsByClassName((this.ySize - 1) + "c" + j)[0]; to.innerHTML = " "; to.style.color = this.color; to.style.backgroundColor = this.backgroundColor; if (this.bold) { to.style.fontWeight = "bold"; } else { to.style.fontWeight = "normal"; } } if (--this.yPosition < 0) this.yPosition = 0; } Terminal.prototype.cursorOff = function() { var cell = document.getElementById("cursor"); if (cell != undefined) { cell.style.borderColor = this.backgroundColor; cell.id = undefined; } } Terminal.prototype.cursorOn = function() { var cell = document.getElementsByClassName(this.yPosition + "c" + this.xPosition)[0]; if (this.displayCursor) { cell.style.borderColor = this.color; cell.id = "cursor"; } } Terminal.prototype.normalOutputChar = function(char) { this.cursorOff(); if (char == "\n") { if (++this.yPosition >= this.ySize) this.globalLineFeed(); this.xPosition = 0; } else { var cell = document.getElementsByClassName(this.yPosition + "c" + this.xPosition++)[0]; cell.innerHTML = char; if (this.bold) cell.style.fontWeight = "bold"; else cell.style.fontWeight = "normal"; cell.style.color = this.color; cell.style.backgroundColor = this.backgroundColor; cell.style.boderColor = this.backgroundColor; if (this.xPosition >= this.xSize) { this.xPosition = 0; this.yPosition++; } if (this.yPosition >= this.ySize) { this.globalLineFeed(); } } this.cursorOn(); } Terminal.prototype.normalOutput = function(text) { for (var i = 0; i < text.length; i++) { this.normalOutputChar(text.charAt(i)); } } Terminal.prototype.output = function(text) { var tmp = ""; var param1 = ""; var param2 = ""; var state = ANSI_NO; for (var i = 0; i < text.length; i++) { switch(state) { case ANSI_NO: // normal text if (text[i] == "\033") state = ANSI_ESC; // maybe a ANSI-sequence else this.normalOutputChar(text[i]); break; case ANSI_ESC: if (text[i] == "[") state = ANSI_CSI1; // maybe a CSI-sequence else { this.normalOutputChar("\033"); state = ANSI_NO; } break; case ANSI_CSI1: if (isNumber(text[i])) { param1 += text[i]; break; } if (param1.length > 0 && text[i] == ";") { tmp = ";" state = ANSI_CSI2; break; } if (param1.length > 0) { state = ANSI_CSI3; i--; break; } if (text[i] == "?") { state = ANSI_DECTCEM; i--; break; } if (text[i] == "s") { state = ANSI_SCP; i--; break; } if (text[i] == "u") { state = ANSI_RCP; i--; break; } if (text[i] == "J") { state = ANSI_ED; i--; if (param1.length == 0) param1 = "0"; break; } this.normalOutput("\033[" + param1 + text[i]) param1 = ""; state = ANSI_NO; break; case ANSI_CSI2: if (isNumber(text[i])) { param2 += text[i]; break; } if (param2.length > 0) { state = ANSI_CSI3; break; } this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; tmp = ""; param2 = ""; state = ANSI_NO; break; case ANSI_CSI3: switch(text[i--]) { case "A": state = ANSI_CUU; break; case "B": state = ANSI_CUD; break; case "C": state = ANSI_CUF; break; case "D": state = ANSI_CUB; break; case "E": state = ANSI_CNL; break; case "F": state = ANSI_CPL; break; case "G": state = ANSI_CHA; break; case "H": state = ANSI_CUP; break; case "J": state = ANSI_ED; break; case "K": state = ANSI_EL; break; case "m": state = ANSI_SGR; break; case "n": state = ANSI_DSR; break; default: state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[++i]); param1 = ""; param2 = ""; tmp = ""; } break; case ANSI_CUU: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } this.cursorOff(); this.yPosition -= parseInt(param1); if (this.yPosition < 0) this.yPosition = 0; this.cursorOn(); state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_CUD: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } this.cursorOff(); this.yPosition += parseInt(param1); if (this.yPosition >= this.ySize) this.yPosition = this.ySize - 1; this.cursorOn(); state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_CUF: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } this.cursorOff(); this.xPosition += parseInt(param1); if (this.xPosition >= this.xSize) this.xPosition = this.xSize - 1; this.cursorOn(); state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_CUB: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } this.cursorOff(); this.xPosition -= parseInt(param1); if (this.xPosition < 0) this.xPosition = 0; this.cursorOn(); state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_CNL: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } this.cursorOff(); this.xPosition = 0; this.yPosition += parseInt(param1); if (this.yPosition >= this.ySize) this.yPosition = this.ySize - 1; this.cursorOn(); state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_CPL: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } this.cursorOff(); this.xPosition = 0; this.yPosition -= parseInt(param1); if (this.yPosition < 0) this.yPosition = 0; this.cursorOn(); state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_CHA: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } this.cursorOff(); this.xPosition = parseInt(param1) - 1; if (this.xPosition >= this.xSize) this.xPosition = this.xSize - 1; if (this.xPosition < 0) this.xPosition = 0; this.cursorOn(); state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_CUP: if (tmp == "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } this.cursorOff(); this.xPosition = parseInt(param2) - 1; if (this.xPosition >= this.xSize) this.xPosition = this.xSize - 1; if (this.xPosition < 0) this.xPosition = 0; this.yPosition = parseInt(param1) - 1; if (this.yPosition >= this.ySize) this.yPosition = this.ySize - 1; if (this.yPosition < 0) this.yPosition = 0; this.cursorOn(); state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_ED: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } switch (parseInt(param1)) { case 0: var x = this.xPosition; var y = this.yPosition; var string = ""; for (var i = 0; i < this.xSize * (this.ySize - y - 1) + this.xSize - x; i++) string += " "; this.normalOutput(string); this.xPosition = x; this.yPosition = y; break; case 1: var x = this.xPosition; var y = this.yPosition; var string = ""; for (var i = 0; i < this.xSize * (y - 1) + x; i++) string += " "; this.xPosition = 0; this.yPosition = 0; this.normalOutput(string); this.xPosition = x; this.yPosition = y; break; case 2: var x = this.xPosition; var y = this.yPosition; var string = ""; for (var i = 0; i < this.xSize * this.ySize; i++) string += " "; this.xPosition = 0; this.yPosition = 0; this.normalOutput(string); this.xPosition = x; this.yPosition = y; break; default: this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); } state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_EL: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } switch (parseInt(param1)) { case 0: var x = this.xPosition; var y = this.yPosition; var string = ""; for (var i = 0; i < this.xSize - x; i++) string += " "; this.normalOutput(string); this.xPosition = x; this.yPosition = y; break; case 1: var x = this.xPosition; var string = ""; for (var i = 0; i < x; i++) string += " "; this.xPosition = 0; this.normalOutput(string); this.xPosition = x; break; case 2: var x = this.xPosition; var y = this.yPosition; var string = ""; for (var i = 0; i < this.xSize; i++) string += " "; this.xPosition = 0; this.normalOutput(string); this.xPosition = x; this.yPosition = y; break; default: this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); } state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; case ANSI_SCP: state = ANSI_NO; this.xPositionSaved = this.xPosition; this.yPositionSaved = this.yPosition; break; case ANSI_RCP: state = ANSI_NO; this.xPosition = this.xPositionSaved; this.yPosition = this.yPositionSaved; break; case ANSI_DECTCEM: tmp += text[i]; if (tmp.length < 4) break; if (tmp == "?25l") { this.displayCursor = false; tmp = ""; state = ANSI_NO; } if (tmp == "?25h") { this.displayCursor = true; tmp = ""; state = ANSI_NO; } break; case ANSI_SGR: if (tmp != "") { state = ANSI_NO; this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); param1 = ""; param2 = ""; tmp = ""; break; } switch(param1) { case "0": this.color = "white"; this.backgroundColor = "black"; this.bold = false; break; case "1": this.bold = true; break; case "30": this.color = "black"; break; case "31": this.color = "red"; break; case "32": this.color = "green"; break; case "33": this.color = "yellow"; break; case "34": this.color = "blue"; break; case "35": this.color = "magenta"; break; case "36": this.color = "cyan"; break; case "37": this.color = "white"; break; case "40": this.backgroundColor = "black"; break; case "41": this.backgroundColor = "red"; break; case "42": this.backgroundColor = "green"; break; case "43": this.backgroundColor = "yellow"; break; case "44": this.backgroundColor = "blue"; break; case "45": this.backgroundColor = "magenta"; break; case "46": this.backgroundColor = "cyan"; break; case "47": this.backgroundColor = "white"; break; default: this.normalOutput("\033[" + param1 + tmp + param2 + text[i]); } state = ANSI_NO; param1 = ""; param2 = ""; tmp = ""; break; } } console.log(state); if (tmp.length || param1.length || param2.length) this.normalOutput("\033[" + param1 + tmp + param2); }