/* Javascript Terminal Copyright (c) 2011 Fabrice Bellard Redistribution or commercial use is prohibited without the author's permission. */ "use strict"; function Term(aa, ba, term_handler) { this.w = aa; this.h = ba; this.cur_h = ba; this.tot_h = 1000; this.y_base = 0; this.y_disp = 0; this.x = 0; this.y = 0; this.cursorstate = 0; this.handler = term_handler; this.convert_lf_to_crlf = false; this.state = 0; this.output_queue = ""; this.bg_colors = ["#000000", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff"]; this.fg_colors = ["#000000", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff"]; this.def_attr = (7 << 3) | 0; this.cur_attr = this.def_attr; this.is_mac = (navigator.userAgent.indexOf("Mac") >= 0) ? true: false; this.key_rep_state = 0; this.key_rep_str = ""; } Term.prototype.open = function() { var y, da, i, ea, c; this.lines = new Array(); c = 32 | (this.def_attr << 16); for (y = 0; y < this.cur_h; y++) { da = new Array(); for (i = 0; i < this.w; i++) da[i] = c; this.lines[y] = da; } document.writeln(''); for (y = 0; y < this.h; y++) { document.writeln(''); } document.writeln('
'); this.refresh(0, this.h - 1); document.addEventListener("keydown", this.keyDownHandler.bind(this), true); document.addEventListener("keypress", this.keyPressHandler.bind(this), true); ea = this; setInterval(function() { ea.cursor_timer_cb(); }, 1000); }; Term.prototype.refresh = function(fa, ga) { var ha, y, da, ia, c, w, i, ja, ka, la, ma, na, oa; for (y = fa; y <= ga; y++) { oa = y + this.y_disp; if (oa >= this.cur_h) oa -= this.cur_h; da = this.lines[oa]; ia = ""; w = this.w; if (y == this.y && this.cursor_state && this.y_disp == this.y_base) { ja = this.x; } else { ja = -1; } la = this.def_attr; for (i = 0; i < w; i++) { c = da[i]; ka = c >> 16; c &= 65535; if (i == ja) { ka = -1; } if (ka != la) { if (la != this.def_attr) ia += ''; if (ka != this.def_attr) { if (ka == -1) { ia += ''; } else { ia += ''; } } } switch (c) { case 32: ia += " "; break; case 38: ia += "&"; break; case 60: ia += "<"; break; case 62: ia += ">"; break; default: if (c < 32) { ia += " "; } else { ia += String.fromCharCode(c); } break; } la = ka; } if (la != this.def_attr) { ia += ''; } ha = document.getElementById("tline" + y); ha.innerHTML = ia; } }; Term.prototype.cursor_timer_cb = function() { this.cursor_state ^= 1; this.refresh(this.y, this.y); }; Term.prototype.show_cursor = function() { if (!this.cursor_state) { this.cursor_state = 1; this.refresh(this.y, this.y); } }; Term.prototype.scroll = function() { var y, da, x, c, oa; if (this.cur_h < this.tot_h) { this.cur_h++; } if (++this.y_base == this.cur_h) this.y_base = 0; this.y_disp = this.y_base; c = 32 | (this.def_attr << 16); da = new Array(); for (x = 0; x < this.w; x++) da[x] = c; oa = this.y_base + this.h - 1; if (oa >= this.cur_h) oa -= this.cur_h; this.lines[oa] = da; }; Term.prototype.scroll_disp = function(n) { var i, oa; if (n >= 0) { for (i = 0; i < n; i++) { if (this.y_disp == this.y_base) break; if (++this.y_disp == this.cur_h) this.y_disp = 0; } } else { n = -n; oa = this.y_base + this.h; if (oa >= this.cur_h) oa -= this.cur_h; for (i = 0; i < n; i++) { if (this.y_disp == oa) break; if (--this.y_disp < 0) this.y_disp = this.cur_h - 1; } } this.refresh(0, this.h - 1); }; Term.prototype.write = function(pa) { function qa(y) { fa = Math.min(fa, y); ga = Math.max(ga, y); } function ra(s, x, y) { var l, i, c, oa; oa = s.y_base + y; if (oa >= s.cur_h) oa -= s.cur_h; l = s.lines[oa]; c = 32 | (s.def_attr << 16); for (i = x; i < s.w; i++) l[i] = c; qa(y); } function sa(s, ta) { var j, n; if (ta.length == 0) { s.cur_attr = s.def_attr; } else { for (j = 0; j < ta.length; j++) { n = ta[j]; if (n >= 30 && n <= 37) { s.cur_attr = (s.cur_attr & ~ (7 << 3)) | ((n - 30) << 3); } else if (n >= 40 && n <= 47) { s.cur_attr = (s.cur_attr & ~7) | (n - 40); } else if (n == 0) { s.cur_attr = s.def_attr; } } } } var ua = 0; var va = 1; var wa = 2; var i, c, fa, ga, l, n, j, oa; fa = this.h; ga = -1; qa(this.y); if (this.y_base != this.y_disp) { this.y_disp = this.y_base; fa = 0; ga = this.h - 1; } for (i = 0; i < pa.length; i++) { c = pa.charCodeAt(i); switch (this.state) { case ua: switch (c) { case 10: if (this.convert_lf_to_crlf) { this.x = 0; } this.y++; if (this.y >= this.h) { this.y--; this.scroll(); fa = 0; ga = this.h - 1; } break; case 13: this.x = 0; break; case 8: if (this.x > 0) { this.x--; } break; case 9: n = (this.x + 8) & ~7; if (n <= this.w) { this.x = n; } break; case 27: this.state = va; break; default: if (c >= 32) { if (this.x >= this.w) { this.x = 0; this.y++; if (this.y >= this.h) { this.y--; this.scroll(); fa = 0; ga = this.h - 1; } } oa = this.y + this.y_base; if (oa >= this.cur_h) oa -= this.cur_h; this.lines[oa][this.x] = (c & 65535) | (this.cur_attr << 16); this.x++; qa(this.y); } break; } break; case va: if (c == 91) { this.esc_params = new Array(); this.cur_param = 0; this.state = wa; } else { this.state = ua; } break; case wa: if (c >= 48 && c <= 57) { this.cur_param = this.cur_param * 10 + c - 48; } else { this.esc_params[this.esc_params.length] = this.cur_param; this.cur_param = 0; if (c == 59) break; this.state = ua; switch (c) { case 65: n = this.esc_params[0]; if (n < 1) n = 1; this.y -= n; if (this.y < 0) this.y = 0; break; case 66: n = this.esc_params[0]; if (n < 1) n = 1; this.y += n; if (this.y >= this.h) this.y = this.h - 1; break; case 67: n = this.esc_params[0]; if (n < 1) n = 1; this.x += n; if (this.x >= this.w - 1) this.x = this.w - 1; break; case 68: n = this.esc_params[0]; if (n < 1) n = 1; this.x -= n; if (this.x < 0) this.x = 0; break; case 72: { var xa, oa; oa = this.esc_params[0] - 1; if (this.esc_params.length >= 2) xa = this.esc_params[1] - 1; else xa = 0; if (oa < 0) oa = 0; else if (oa >= this.h) oa = this.h - 1; if (xa < 0) xa = 0; else if (xa >= this.w) xa = this.w - 1; this.x = xa; this.y = oa; } break; case 74: ra(this, this.x, this.y); for (j = this.y + 1; j < this.h; j++) ra(this, 0, j); break; case 75: ra(this, this.x, this.y); break; case 109: sa(this, this.esc_params); break; case 110: this.queue_chars("\x1b[" + (this.y + 1) + ";" + (this.x + 1) + "R"); break; default: break; } } break; } } qa(this.y); if (ga >= fa) this.refresh(fa, ga); }; Term.prototype.writeln = function(pa) { this.write(pa + '\r\n'); }; Term.prototype.keyDownHandler = function(ya) { var pa; pa = ""; switch (ya.keyCode) { case 8: pa = ""; break; case 9: pa = "\t"; break; case 13: pa = "\r"; break; case 27: pa = "\x1b"; break; case 37: pa = "\x1b[D"; break; case 39: pa = "\x1b[C"; break; case 38: if (ya.ctrlKey) { this.scroll_disp( - 1); } else { pa = "\x1b[A"; } break; case 40: if (ya.ctrlKey) { this.scroll_disp(1); } else { pa = "\x1b[B"; } break; case 46: pa = "\x1b[3~"; break; case 45: pa = "\x1b[2~"; break; case 36: pa = "\x1bOH"; break; case 35: pa = "\x1bOF"; break; case 33: if (ya.ctrlKey) { this.scroll_disp( - (this.h - 1)); } else { pa = "\x1b[5~"; } break; case 34: if (ya.ctrlKey) { this.scroll_disp(this.h - 1); } else { pa = "\x1b[6~"; } break; default: if (ya.ctrlKey) { if (ya.keyCode >= 65 && ya.keyCode <= 90) { pa = String.fromCharCode(ya.keyCode - 64); } else if (ya.keyCode == 32) { pa = String.fromCharCode(0); } } else if ((!this.is_mac && ya.altKey) || (this.is_mac && ya.metaKey)) { if (ya.keyCode >= 65 && ya.keyCode <= 90) { pa = "\x1b" + String.fromCharCode(ya.keyCode + 32); } } break; } if (pa) { if (ya.stopPropagation) ya.stopPropagation(); if (ya.preventDefault) ya.preventDefault(); this.show_cursor(); this.key_rep_state = 1; this.key_rep_str = pa; this.handler(pa); return false; } else { this.key_rep_state = 0; return true; } }; Term.prototype.keyPressHandler = function(ya) { var pa, za; if (ya.stopPropagation) ya.stopPropagation(); if (ya.preventDefault) ya.preventDefault(); pa = ""; if (! ("charCode" in ya)) { za = ya.keyCode; if (this.key_rep_state == 1) { this.key_rep_state = 2; return false; } else if (this.key_rep_state == 2) { this.show_cursor(); this.handler(this.key_rep_str); return false; } } else { za = ya.charCode; } if (za != 0) { if (!ya.ctrlKey && ((!this.is_mac && !ya.altKey) || (this.is_mac && !ya.metaKey))) { pa = String.fromCharCode(za); } } if (pa) { this.show_cursor(); this.handler(pa); return false; } else { return true; } }; Term.prototype.queue_chars = function(pa) { this.output_queue += pa; if (this.output_queue) setTimeout(this.outputHandler.bind(this), 0); }; Term.prototype.outputHandler = function() { if (this.output_queue) { this.handler(this.output_queue); this.output_queue = ""; } };