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);
}