Skip to content

Instantly share code, notes, and snippets.

@cwleonard
Last active Dec 25, 2015
Embed
What would you like to do?
Create an emulation of a VT100 terminal on a web page, with ANSI graphics support. Have old ANSI art laying around? Showcase it on the web, dynamically. It doesn't have blink support, yet. You can see it in use here: (http://www.amphibian.com/doors/ansiviewer.jsp) with sample ANSIs from an old BBS I helped run back in the 90's.
var MAX_ROW = 30;
var BLANK = " ";
var BLACK = 0;
var RED = 1;
var GREEN = 2;
var YELLOW = 3;
var BLUE = 4;
var MAGENTA = 5;
var CYAN = 6;
var WHITE = 7;
var COLOR_0 = "#000000";
var COLOR_1 = "#660000";
var COLOR_2 = "#006600";
var COLOR_3 = "#FF9900";
var COLOR_4 = "#000066";
var COLOR_5 = "#990066";
var COLOR_6 = "#009999";
var COLOR_7 = "#999999";
var BRIGHT_COLOR_0 = "#666666";
var BRIGHT_COLOR_1 = "#FF0000";
var BRIGHT_COLOR_2 = "#00FF00";
var BRIGHT_COLOR_3 = "#FFFF00";
var BRIGHT_COLOR_4 = "#0000FF";
var BRIGHT_COLOR_5 = "#FF3399";
var BRIGHT_COLOR_6 = "#66FFFF";
var BRIGHT_COLOR_7 = "#FFFFFF";
/*
* The Console object constructor takes an element name and converts
* that node into a "console", which is the emulated terminal screen.
*/
function Console(elementName) {
this.row = 1;
this.col = 1;
this.colorBright = false;
this.backgroundBright = false;
this.fgColor = WHITE;
this.bgColor = BLACK;
this.timeoutId = null;
this.connected = false;
this.consoleElement = document.getElementById(elementName);
var contents = "";
for (var i = 0; i < MAX_ROW; i++) {
contents += "<div id=\"row" + (i+1) + "\">";
for (var j = 0; j < 80; j++) {
contents += "<span>" + BLANK + "</span>";
}
contents += "</div>";
}
this.consoleElement.innerHTML = contents;
}
Console.prototype.clear = function() {
for (var i = 1; i <= MAX_ROW; i++) {
var r = document.getElementById("row" + i);
for (var j = 0; j < 80; j++) {
var c = r.childNodes[j];
c.style.color = this.getForegroundColor();
c.style.backgroundColor = this.getBackgroundColor();
c.innerHTML = BLANK;
}
}
this.row = 1;
this.col = 1;
}
Console.prototype.ansi = function(cmd) {
var command = "";
var params = "";
if (cmd.length > 0) {
command = cmd.substring(cmd.length - 1);
params = cmd.substring(0, cmd.length - 1);
}
var paramArray = params.split(";");
if (command == "A") {
var x = Number(params);
this.row = this.row - x;
if (this.row < 1) row = 1;
} else
if (command == "B") {
var x = Number(params);
this.row = this.row + x;
if (this.row > MAX_ROW) row = 30;
} else
if (command == "C") {
var x = Number(params);
this.col = this.col + x;
if (this.col > 80) this.col = 80;
} else
if (command == "D") {
var x = Number(params);
this.col = this.col - x;
if (this.col < 1) this.col = 1;
} else
if (command == "E") {
var x = Number(params);
this.row = this.row + x;
this.addNewRows();
} else
if (command == "F") {
var x = Number(params);
this.row = this.row - x;
if (this.row < 1) this.row = 1;
} else
if (command == "H" || command == "f") {
var newRow = 1;
var newCol = 1;
if (paramArray.length >= 1) {
var tempRow = paramArray[0];
if (tempRow == "") {
tempRow = 1;
}
newRow = Number(tempRow);
}
if (paramArray.length >= 2) {
var tempCol = paramArray[1];
if (tempCol == "") {
tempCol = 1;
}
newCol = Number(tempCol);
}
this.row = newRow;
this.col = newCol;
this.addNewRows();
} else
if (command == "J") {
var clearType = 0;
if (params == "0") {
// clear from cursor to end of screen (row 30, column 80)
var r = document.getElementById("row" + this.row);
for (var i = this.col; i < 81; i++) {
var c = r.childNodes[i - 1];
c.style.color = this.getForegroundColor();
c.style.backgroundColor = this.getBackgroundColor();
c.innerHTML = BLANK;
}
for (var i = this.row + 1; i <= MAX_ROW; i++) {
var r = document.getElementById("row" + i);
for (var j = 0; j < 80; j++) {
var c = r.childNodes[j];
c.style.color = this.getForegroundColor();
c.style.backgroundColor = this.getBackgroundColor();
c.innerHTML = BLANK;
}
}
} else if (params == "1") {
// clear from cursor to row 1, col 1 (backwards)
var r = document.getElementById("row" + this.row);
for (var i = this.col; i > 0; i--) {
var c = r.childNodes[i - 1];
c.style.color = this.getForegroundColor();
c.style.backgroundColor = this.getBackgroundColor();
c.innerHTML = BLANK;
}
for (var i = this.row - 1; i > 0; i--) {
var r = document.getElementById("row" + i);
for (var j = 0; j < 80; j++) {
var c = r.childNodes[j];
c.style.color = this.getForegroundColor();
c.style.backgroundColor = this.getBackgroundColor();
c.innerHTML = BLANK;
}
}
} else if (params == "2") {
// clear screen, move cursor to row 1, col 1
this.clear();
}
} else
if (command == "S") {
//alert("scroll up");
} else
if (command == "T") {
//alert("scroll down");
} else
if (command == "m") {
if (paramArray.length == 0) {
this.fgColor = WHITE;
this.bgColor = BLACK;
this.colorBright = false;
this.backgroundBright = false;
} else {
for (var i = 0; i < paramArray.length; i++) {
var p = Number(paramArray[i]);
if (p == 0) {
this.colorBright = false;
this.backgroundBright = false;
this.fgColor = WHITE;
this.bgColor = BLACK;
} else if (p == 1) {
this.colorBright = true;
} else if (p == 22) {
this.colorBright = false;
} else if (p >= 30 && p <= 37) {
this.fgColor = p - 30;
} else if (p >= 40 && p <= 47) {
this.bgColor = p - 40;
}
}
}
} else
if (command == "Z" && params == "99") {
this.connected = false;
document.onkeypress = null;
} else {
alert("unhandled command: " + cmd);
}
}
/*
* Writes text, which can contain ANSI escape sequences to the console.
*/
Console.prototype.write = function(text) {
var ansiBuf = "";
var escSeq = false;
var escSeqCounter = 0;
for (var j = 0; j < text.length && this.connected; j++) {
var temp = text.charAt(j);
var code = text.charCodeAt(j);
if (escSeq) {
// we've already seen an ESC
if (escSeqCounter == 1) {
if (temp == "[") {
escSeqCounter++;
} else {
escSeq = false;
}
} else {
ansiBuf += temp;
escSeqCounter++;
if (code >= 64 && code <= 126) {
escSeq = false;
this.ansi(ansiBuf);
ansiBuf = "";
}
}
} else {
if (code == 27) {
temp = "";
// begin ANSI escape sequence
escSeq = true;
escSeqCounter = 1;
} else if (temp == ' ') {
temp = BLANK;
} else if (temp == '<') {
temp = "&lt;";
} else if (temp == '>') {
temp = "&gt;";
} else if (code == 8) {
temp = "";
this.col--;
} else if (temp == '\n') {
temp = "";
this.row++;
this.col = 1;
this.addNewRows();
} else if (code > 127) {
temp = "&#" + code + ";";
} else if (code < 32) {
temp = "";
}
if (temp != "") {
var r = document.getElementById("row" + this.row);
var c = r.childNodes[this.col - 1];
c.style.color = this.getForegroundColor();
c.style.backgroundColor = this.getBackgroundColor();
c.innerHTML = temp;
this.col++;
if (this.col > 80) {
this.row++;
this.col = 1;
this.addNewRows();
}
}
}
}
}
Console.prototype.addNewRows = function() {
if (this.row > MAX_ROW) {
var howMany = this.row - MAX_ROW;
for (var z = 0; z < howMany; z++) {
var rows = this.consoleElement.childNodes;
for (var i = 0; i < rows.length; i++) {
var rowId = Number(rows[i].id.substring(3));
rowId--;
rows[i].id = "row" + rowId;
if (rowId < 1) {
rows[i].style.display = "none";
}
}
var newRow = document.createElement("DIV");
var spans = "";
for (var j = 0; j < 80; j++) {
spans += "<span onclick=\"whereAmI(this);\">" + BLANK + "</span>";
}
newRow.id = "row" + MAX_ROW;
newRow.innerHTML = spans;
this.consoleElement.appendChild(newRow);
}
this.row = 30;
}
}
Console.prototype.getBackgroundColor = function() {
if (this.backgroundBright) {
return eval("BRIGHT_COLOR_" + this.bgColor);
} else {
return eval("COLOR_" + this.bgColor);
}
}
Console.prototype.getForegroundColor = function() {
if (this.colorBright) {
return eval("BRIGHT_COLOR_" + this.fgColor);
} else {
return eval("COLOR_" + this.fgColor);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment