Last active
December 25, 2015 18:49
-
-
Save cwleonard/7023432 to your computer and use it in GitHub Desktop.
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 = "<"; | |
} else if (temp == '>') { | |
temp = ">"; | |
} 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