1461 lines
44 KiB
JavaScript
1461 lines
44 KiB
JavaScript
|
/*
|
||
|
* Javascript terminal
|
||
|
*
|
||
|
* Copyright (c) 2011-2020 Fabrice Bellard
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
* of this software and associated documentation files (the "Software"), to deal
|
||
|
* in the Software without restriction, including without limitation the rights
|
||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
* copies of the Software, and to permit persons to whom the Software is
|
||
|
* furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in
|
||
|
* all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
* THE SOFTWARE.
|
||
|
*/
|
||
|
"use strict";
|
||
|
|
||
|
function Term(options)
|
||
|
{
|
||
|
var width, height, tot_height, scrollback;
|
||
|
|
||
|
function dummy_key_handler()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
width = options.cols ? options.cols : 80;
|
||
|
height = options.rows ? options.rows : 25;
|
||
|
scrollback = options.scrollback ? options.scrollback : 0;
|
||
|
this.font_size = options.fontSize ? options.fontSize : 15;
|
||
|
|
||
|
this.w = width;
|
||
|
this.h = height;
|
||
|
this.cur_h = height; /* current height of the scroll back buffer */
|
||
|
tot_height = height + scrollback;
|
||
|
this.tot_h = tot_height; /* maximum height of the scroll back buffer */
|
||
|
|
||
|
/* y_base and y_disp are index in the circular buffer lines of
|
||
|
length cur_h. They are defined modulo tot_h, i.e. they wrap
|
||
|
when cur_h = tot_h. If cur_h < tot_h, y_base is always equal to
|
||
|
cur_h - h. */
|
||
|
this.y_base = 0; /* position of the current top screen line in the
|
||
|
scroll back buffer */
|
||
|
this.y_disp = 0; /* position of the top displayed line in the
|
||
|
scroll back buffer */
|
||
|
/* cursor position */
|
||
|
this.x = 0;
|
||
|
this.y = 0;
|
||
|
this.scroll_top = 0;
|
||
|
this.scroll_bottom = this.h;
|
||
|
this.cursorstate = 0;
|
||
|
this.handler = dummy_key_handler;
|
||
|
this.state = 0;
|
||
|
this.output_queue = "";
|
||
|
this.colors = [
|
||
|
/* normal */
|
||
|
"#000000",
|
||
|
"#aa0000",
|
||
|
"#00aa00",
|
||
|
"#aa5500",
|
||
|
"#0000aa",
|
||
|
"#aa00aa",
|
||
|
"#00aaaa",
|
||
|
"#aaaaaa",
|
||
|
/* bright */
|
||
|
"#555555",
|
||
|
"#ff5555",
|
||
|
"#55ff55",
|
||
|
"#ffff55",
|
||
|
"#5555ff",
|
||
|
"#ff55ff",
|
||
|
"#55ffff",
|
||
|
"#ffffff"
|
||
|
];
|
||
|
/* attributes bits:
|
||
|
0-3: bg
|
||
|
4-7: fg
|
||
|
8: bold
|
||
|
9: inverse
|
||
|
*/
|
||
|
this.def_attr = (7 << 4) | 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 = "";
|
||
|
|
||
|
this.utf8 = true;
|
||
|
this.utf8_state = 0;
|
||
|
this.utf8_val = 0;
|
||
|
|
||
|
this.application_cursor = false;
|
||
|
this.application_keypad = false;
|
||
|
/* if true, emulate some behaviors of the Linux console */
|
||
|
this.linux_console = true;
|
||
|
|
||
|
this.textarea_has_focus = false;
|
||
|
}
|
||
|
|
||
|
Term.prototype.setKeyHandler = function(handler)
|
||
|
{
|
||
|
this.handler = handler;
|
||
|
}
|
||
|
|
||
|
/* return the size of a character in CSS pixels using the selected font */
|
||
|
function term_get_char_size(parent_el, font_size)
|
||
|
{
|
||
|
var el, g, ret;
|
||
|
el = document.createElement("div");
|
||
|
el.classList.add("term", "term_char_size");
|
||
|
el.style.fontSize = font_size + "px";
|
||
|
el.textContent = "W";
|
||
|
parent_el.appendChild(el);
|
||
|
g = el.getBoundingClientRect();
|
||
|
/* the character width & height may not be an integer */
|
||
|
ret = [g.width, g.height];
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
Term.prototype.open = function(parent_el)
|
||
|
{
|
||
|
var y, line, i, term, c, row_el, char_size_ret;
|
||
|
|
||
|
/* set initial content */
|
||
|
this.lines = new Array();
|
||
|
c = 32 | (this.def_attr << 16);
|
||
|
for(y = 0; y < this.cur_h;y++) {
|
||
|
line = new Array();
|
||
|
for(i=0;i<this.w;i++)
|
||
|
line[i] = c;
|
||
|
this.lines[y] = line;
|
||
|
}
|
||
|
|
||
|
char_size_ret = term_get_char_size(parent_el, this.font_size);
|
||
|
/* size of the character in CSS pixels */
|
||
|
this.char_width = char_size_ret[0];
|
||
|
this.char_height = char_size_ret[1];
|
||
|
|
||
|
this.scrollbar_width = 15;
|
||
|
|
||
|
/* size of term_el in CSS pixels */
|
||
|
this.term_width = Math.ceil(this.w * this.char_width) +
|
||
|
this.scrollbar_width;
|
||
|
this.term_height = Math.ceil(this.h * this.char_height);
|
||
|
|
||
|
/* create the terminal window */
|
||
|
this.term_el = document.createElement("div");
|
||
|
this.term_el.className = "term";
|
||
|
/* XXX: could compute the font metrics */
|
||
|
this.term_el.style.fontSize = this.font_size + "px";
|
||
|
this.term_el.style.width = this.term_width + "px";
|
||
|
this.term_el.style.height = this.term_height + "px";
|
||
|
/* allow the terminal to take the focus */
|
||
|
this.term_el.setAttribute("tabindex", "0");
|
||
|
|
||
|
/* scroll bar */
|
||
|
this.scrollbar_el = document.createElement("div");
|
||
|
this.scrollbar_el.className = "term_scrollbar";
|
||
|
this.scrollbar_el.style.width = this.scrollbar_width + "px";
|
||
|
this.term_el.appendChild(this.scrollbar_el);
|
||
|
|
||
|
this.track_el = document.createElement("div");
|
||
|
this.track_el.className = "term_track";
|
||
|
this.track_el.onmousedown = this.mouseMoveHandler.bind(this);
|
||
|
this.scrollbar_el.appendChild(this.track_el);
|
||
|
|
||
|
this.thumb_el = document.createElement("div");
|
||
|
this.thumb_el.className = "term_thumb";
|
||
|
this.thumb_el.onmousedown = this.mouseDownHandler.bind(this);
|
||
|
this.track_el.appendChild(this.thumb_el);
|
||
|
|
||
|
this.end_el = document.createElement("div");
|
||
|
this.end_el.className = "term_end";
|
||
|
this.thumb_el.appendChild(this.end_el);
|
||
|
|
||
|
/* current scrollbar position */
|
||
|
this.thumb_size = -1;
|
||
|
this.thumb_pos = -1;
|
||
|
|
||
|
/* terminal content */
|
||
|
this.content_el = document.createElement("div");
|
||
|
this.content_el.className = "term_content";
|
||
|
this.content_el.style.width = (this.w) + "ch";
|
||
|
this.term_el.appendChild(this.content_el);
|
||
|
|
||
|
this.rows_el = [];
|
||
|
for(y=0;y<this.h;y++) {
|
||
|
row_el = document.createElement("div");
|
||
|
this.rows_el.push(row_el);
|
||
|
this.content_el.appendChild(row_el);
|
||
|
}
|
||
|
|
||
|
/* dummy textarea to get the input events and for the virtual
|
||
|
keyboard on mobile devices */
|
||
|
this.textarea_el = document.createElement("textarea");
|
||
|
this.textarea_el.classList.add("term_textarea");
|
||
|
this.textarea_el.setAttribute("autocorrect", "off");
|
||
|
this.textarea_el.setAttribute("autocapitalize", "off");
|
||
|
this.textarea_el.setAttribute("spellcheck", "false");
|
||
|
this.textarea_el.setAttribute("tabindex", "-1");
|
||
|
this.term_el.appendChild(this.textarea_el);
|
||
|
|
||
|
this.parent_el = parent_el;
|
||
|
parent_el.appendChild(this.term_el);
|
||
|
|
||
|
this.refresh(0, this.h - 1);
|
||
|
|
||
|
/* textarea_el events */
|
||
|
// key handler
|
||
|
this.textarea_el.addEventListener("keydown",
|
||
|
this.keyDownHandler.bind(this), true);
|
||
|
this.textarea_el.addEventListener("keyup",
|
||
|
this.keyUpHandler.bind(this), true);
|
||
|
/* keypress is deprecated, so use input */
|
||
|
this.textarea_el.addEventListener("input",
|
||
|
this.inputHandler.bind(this), true);
|
||
|
this.textarea_el.addEventListener("focus",
|
||
|
this.focusHandler.bind(this), true);
|
||
|
this.textarea_el.addEventListener("blur",
|
||
|
this.blurHandler.bind(this), true);
|
||
|
|
||
|
/* term_el events */
|
||
|
this.term_el.addEventListener("keydown",
|
||
|
this.termKeyDownHandler.bind(this),
|
||
|
true);
|
||
|
this.term_el.addEventListener("paste",
|
||
|
this.pasteHandler.bind(this), true);
|
||
|
this.term_el.addEventListener("mouseup",
|
||
|
this.termMouseUpHandler.bind(this),
|
||
|
true);
|
||
|
this.term_el.addEventListener("wheel",
|
||
|
this.wheelHandler.bind(this), false);
|
||
|
|
||
|
// cursor blinking
|
||
|
term = this;
|
||
|
setInterval(function() { term.cursor_timer_cb(); }, 1000);
|
||
|
|
||
|
this.term_el.focus();
|
||
|
};
|
||
|
|
||
|
Term.prototype.refresh_scrollbar = function ()
|
||
|
{
|
||
|
var total_size, thumb_pos, thumb_size, y, y0;
|
||
|
total_size = this.term_el.clientHeight;
|
||
|
thumb_size = Math.ceil(this.h * total_size / this.cur_h);
|
||
|
/* position of the first line of the scroll back buffer */
|
||
|
y0 = (this.y_base + this.h) % this.cur_h;
|
||
|
y = this.y_disp - y0;
|
||
|
if (y < 0)
|
||
|
y += this.cur_h;
|
||
|
thumb_pos = Math.floor(y * total_size / this.cur_h);
|
||
|
thumb_size = Math.max(thumb_size, 30);
|
||
|
thumb_size = Math.min(thumb_size, total_size);
|
||
|
thumb_pos = Math.min(thumb_pos, total_size - thumb_size);
|
||
|
// console.log("pos=" + thumb_pos + " size=" + thumb_size);
|
||
|
if (thumb_pos != this.thumb_pos || thumb_size != this.thumb_size) {
|
||
|
this.thumb_pos = thumb_pos;
|
||
|
this.thumb_size = thumb_size;
|
||
|
this.thumb_el.style.top = thumb_pos + "px";
|
||
|
this.thumb_el.style.height = thumb_size + "px";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* move the text area at the cursor position so that the browser shows
|
||
|
* the correct position when the virtual keyboard is used */
|
||
|
Term.prototype.move_textarea = function()
|
||
|
{
|
||
|
var x, y, base_x, base_y, pos;
|
||
|
|
||
|
pos = this.term_el.getBoundingClientRect();
|
||
|
base_x = pos.left + window.scrollX;
|
||
|
base_y = pos.top + window.scrollY;
|
||
|
/* position relative to the body */
|
||
|
x = Math.ceil(this.x * this.char_width + base_x);
|
||
|
y = Math.ceil(this.y * this.char_height + base_y);
|
||
|
|
||
|
this.textarea_el.style.width = Math.ceil(this.char_width) + "px";
|
||
|
this.textarea_el.style.height = Math.ceil(this.char_height) + "px";
|
||
|
this.textarea_el.style.left = x + "px";
|
||
|
this.textarea_el.style.top = y + "px";
|
||
|
this.textarea_el.style.zIndex = 1000;
|
||
|
}
|
||
|
|
||
|
Term.prototype.refresh = function(ymin, ymax)
|
||
|
{
|
||
|
var el, y, line, outline, c, w, i, j, cx, attr, last_attr, fg, bg, y1;
|
||
|
var http_link_len, http_link_str, bold, tmp, inverse;
|
||
|
|
||
|
function is_http_link_char(c)
|
||
|
{
|
||
|
var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=`.";
|
||
|
return str.indexOf(String.fromCharCode(c)) >= 0;
|
||
|
}
|
||
|
|
||
|
function right_trim(str, a)
|
||
|
{
|
||
|
var i, n;
|
||
|
n = a.length;
|
||
|
i = str.length;
|
||
|
while (i >= n && str.substr(i - n, n) == a)
|
||
|
i -= n;
|
||
|
return str.substr(0, i);
|
||
|
}
|
||
|
|
||
|
for(y = ymin; y <= ymax; y++) {
|
||
|
/* convert to HTML string */
|
||
|
y1 = y + this.y_disp;
|
||
|
if (y1 >= this.cur_h)
|
||
|
y1 -= this.cur_h;
|
||
|
line = this.lines[y1];
|
||
|
outline = "";
|
||
|
w = this.w;
|
||
|
if (y == this.y && this.cursor_state &&
|
||
|
this.y_disp == this.y_base) {
|
||
|
cx = this.x;
|
||
|
} else {
|
||
|
cx = -1;
|
||
|
}
|
||
|
last_attr = this.def_attr;
|
||
|
http_link_len = 0;
|
||
|
for(i = 0; i < w; i++) {
|
||
|
c = line[i];
|
||
|
attr = c >> 16;
|
||
|
c &= 0xffff;
|
||
|
/* test for http link */
|
||
|
if (c == 0x68 && (w - i) >= 8 && http_link_len == 0) {
|
||
|
/* test http:// or https:// */
|
||
|
if ((line[i + 1] & 0xffff) == 0x74 &&
|
||
|
(line[i + 2] & 0xffff) == 0x74 &&
|
||
|
(line[i + 3] & 0xffff) == 0x70 &&
|
||
|
(((line[i + 4] & 0xffff) == 0x3a &&
|
||
|
(line[i + 5] & 0xffff) == 0x2f &&
|
||
|
(line[i + 6] & 0xffff) == 0x2f) ||
|
||
|
((line[i + 4] & 0xffff) == 0x73 &&
|
||
|
(line[i + 5] & 0xffff) == 0x3a &&
|
||
|
(line[i + 6] & 0xffff) == 0x2f &&
|
||
|
(line[i + 7] & 0xffff) == 0x2f))) {
|
||
|
http_link_str = "";
|
||
|
j = 0;
|
||
|
while ((i + j) < w &&
|
||
|
is_http_link_char(line[i + j] & 0xffff)) {
|
||
|
http_link_str += String.fromCharCode(line[i + j] & 0xffff);
|
||
|
j++;
|
||
|
}
|
||
|
http_link_len = j;
|
||
|
if (last_attr != this.def_attr) {
|
||
|
outline += '</span>';
|
||
|
last_attr = this.def_attr;
|
||
|
}
|
||
|
outline += "<a href='" + http_link_str + "' onclick='return Term.prototype.href(this)'>";
|
||
|
}
|
||
|
}
|
||
|
if (i == cx) {
|
||
|
attr = -1; /* cursor */
|
||
|
}
|
||
|
if (attr != last_attr) {
|
||
|
if (last_attr != this.def_attr)
|
||
|
outline += '</span>';
|
||
|
if (attr != this.def_attr) {
|
||
|
if (attr == -1) {
|
||
|
/* cursor */
|
||
|
outline += '<span class="term_cursor">';
|
||
|
} else {
|
||
|
outline += '<span style="';
|
||
|
fg = (attr >> 4) & 0xf;
|
||
|
bg = attr & 0xf;
|
||
|
bold = (attr >> 8) & 1;
|
||
|
inverse = (attr >> 9) & 1;
|
||
|
if (inverse) {
|
||
|
tmp = fg;
|
||
|
fg = bg;
|
||
|
bg = tmp;
|
||
|
}
|
||
|
if (bold) {
|
||
|
/* metrics are not OK for all fonts, so disabled */
|
||
|
/* outline += 'font-weight:bold;'; */
|
||
|
/* use the bright color */
|
||
|
if (fg < 8)
|
||
|
fg += 8;
|
||
|
}
|
||
|
if (fg != 7) {
|
||
|
outline += 'color:' + this.colors[fg] + ';';
|
||
|
}
|
||
|
if (bg != 0) {
|
||
|
outline += 'background-color:' +
|
||
|
this.colors[bg] + ';';
|
||
|
}
|
||
|
outline += '">';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
switch(c) {
|
||
|
case 32:
|
||
|
outline += " ";
|
||
|
break;
|
||
|
case 38: // '&'
|
||
|
outline += "&";
|
||
|
break;
|
||
|
case 60: // '<'
|
||
|
outline += "<";
|
||
|
break;
|
||
|
case 62: // '>'
|
||
|
outline += ">";
|
||
|
break;
|
||
|
default:
|
||
|
if (c < 32) {
|
||
|
outline += " ";
|
||
|
} else {
|
||
|
outline += String.fromCharCode(c);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
last_attr = attr;
|
||
|
if (http_link_len != 0) {
|
||
|
http_link_len--;
|
||
|
if (http_link_len == 0) {
|
||
|
if (last_attr != this.def_attr) {
|
||
|
outline += '</span>';
|
||
|
last_attr = this.def_attr;
|
||
|
}
|
||
|
outline += "</a>";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (last_attr != this.def_attr) {
|
||
|
outline += '</span>';
|
||
|
}
|
||
|
|
||
|
/* trim trailing spaces for copy/paste */
|
||
|
outline = right_trim(outline, " ");
|
||
|
if (outline == "")
|
||
|
outline = " ";
|
||
|
|
||
|
this.rows_el[y].innerHTML = outline;
|
||
|
}
|
||
|
|
||
|
this.refresh_scrollbar();
|
||
|
this.move_textarea();
|
||
|
};
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/* scroll down or up in the scroll back buffer by n lines */
|
||
|
Term.prototype.scroll_disp = function(n)
|
||
|
{
|
||
|
var i, y1;
|
||
|
/* slow but it does not really matters */
|
||
|
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;
|
||
|
y1 = this.y_base + this.h;
|
||
|
if (y1 >= this.cur_h)
|
||
|
y1 -= this.cur_h;
|
||
|
for(i = 0; i < n; i++) {
|
||
|
if (this.y_disp == y1)
|
||
|
break;
|
||
|
if (--this.y_disp < 0)
|
||
|
this.y_disp = this.cur_h - 1;
|
||
|
}
|
||
|
}
|
||
|
this.refresh(0, this.h - 1);
|
||
|
};
|
||
|
|
||
|
Term.prototype.write = function(str)
|
||
|
{
|
||
|
var s, ymin, ymax;
|
||
|
|
||
|
function update(y)
|
||
|
{
|
||
|
ymin = Math.min(ymin, y);
|
||
|
ymax = Math.max(ymax, y);
|
||
|
}
|
||
|
|
||
|
function get_erase_char()
|
||
|
{
|
||
|
var bg_mask, attr;
|
||
|
bg_mask = 0xf;
|
||
|
attr = (s.def_attr & ~bg_mask) | (s.cur_attr & bg_mask);
|
||
|
return 32 | (attr << 16);
|
||
|
}
|
||
|
|
||
|
function erase_chars(x1, x2, y) {
|
||
|
var l, i, c, y1;
|
||
|
y1 = s.y_base + y;
|
||
|
if (y1 >= s.cur_h)
|
||
|
y1 -= s.cur_h;
|
||
|
l = s.lines[y1];
|
||
|
c = get_erase_char();
|
||
|
for(i = x1; i < x2; i++)
|
||
|
l[i] = c;
|
||
|
update(y);
|
||
|
}
|
||
|
|
||
|
function erase_to_eol(x, y) {
|
||
|
erase_chars(x, s.w, y);
|
||
|
}
|
||
|
|
||
|
function erase_in_line(n) {
|
||
|
switch(n) {
|
||
|
case 0:
|
||
|
erase_to_eol(s.x, s.y);
|
||
|
break;
|
||
|
case 1:
|
||
|
erase_chars(0, s.x + 1, s.y);
|
||
|
break;
|
||
|
case 2:
|
||
|
erase_chars(0, s.w, s.y);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function erase_in_display(n) {
|
||
|
var y;
|
||
|
switch(n) {
|
||
|
case 0:
|
||
|
erase_to_eol(s.x, s.y);
|
||
|
for(y = s.y + 1; y < s.h; y++)
|
||
|
erase_to_eol(0, y);
|
||
|
break;
|
||
|
case 1:
|
||
|
erase_chars(0, s.x + 1, s.y);
|
||
|
for(y = 0; y < s.y; y++) {
|
||
|
erase_to_eol(0, y);
|
||
|
}
|
||
|
break;
|
||
|
case 2:
|
||
|
for(y = 0; y < s.h; y++) {
|
||
|
erase_to_eol(0, y);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
function delete_chars(n)
|
||
|
{
|
||
|
var l, i, c, y1, j;
|
||
|
y1 = s.y + s.y_base;
|
||
|
if (y1 >= s.cur_h)
|
||
|
y1 -= s.cur_h;
|
||
|
l = s.lines[y1];
|
||
|
if (n < 1)
|
||
|
n = 1;
|
||
|
c = get_erase_char();
|
||
|
j = s.x + n;
|
||
|
for(i = s.x; i < s.w; i++) {
|
||
|
if (j < s.w)
|
||
|
l[i] = l[j];
|
||
|
else
|
||
|
l[i] = c;
|
||
|
j++;
|
||
|
}
|
||
|
update(s.y);
|
||
|
}
|
||
|
|
||
|
function insert_chars(n)
|
||
|
{
|
||
|
var l, i, c, y1, x1;
|
||
|
if (n < 1)
|
||
|
n = 1;
|
||
|
if (n > s.w - s.x)
|
||
|
n = s.w - s.x;
|
||
|
y1 = s.y + s.y_base;
|
||
|
if (y1 >= s.cur_h)
|
||
|
y1 -= s.cur_h;
|
||
|
l = s.lines[y1];
|
||
|
x1 = s.x + n;
|
||
|
for(i = s.w - 1; i >= x1; i--)
|
||
|
l[i] = l[i - n];
|
||
|
c = get_erase_char();
|
||
|
for(i = s.x; i < x1; i++)
|
||
|
l[i] = c;
|
||
|
update(s.y);
|
||
|
}
|
||
|
|
||
|
function csi_colors(esc_params)
|
||
|
{
|
||
|
var j, n, fg, bg, mask;
|
||
|
|
||
|
if (esc_params.length == 0) {
|
||
|
s.cur_attr= s.def_attr;
|
||
|
} else {
|
||
|
for(j = 0; j < esc_params.length; j++) {
|
||
|
n = esc_params[j];
|
||
|
if (n >= 30 && n <= 37) {
|
||
|
/* foreground */
|
||
|
fg = n - 30;
|
||
|
s.cur_attr = (s.cur_attr & ~(0xf << 4)) | (fg << 4);
|
||
|
} else if (n >= 40 && n <= 47) {
|
||
|
/* background */
|
||
|
bg = n - 40;
|
||
|
s.cur_attr = (s.cur_attr & ~0xf) | bg;
|
||
|
} else if (n >= 90 && n <= 97) {
|
||
|
/* bright foreground */
|
||
|
fg = n - 90 + 8;
|
||
|
s.cur_attr = (s.cur_attr & ~(0xf << 4)) | (fg << 4);
|
||
|
} else if (n >= 100 && n <= 107) {
|
||
|
/* bright background */
|
||
|
bg = n - 100 + 8;
|
||
|
s.cur_attr = (s.cur_attr & ~0xf) | bg;
|
||
|
} else if (n == 1) {
|
||
|
/* bold + bright */
|
||
|
s.cur_attr |= (1 << 8);
|
||
|
} else if (n == 0) {
|
||
|
/* default attr */
|
||
|
s.cur_attr = s.def_attr;
|
||
|
} else if (n == 7) {
|
||
|
/* inverse */
|
||
|
s.cur_attr |= (1 << 9);
|
||
|
} else if (n == 27) {
|
||
|
/* not inverse */
|
||
|
s.cur_attr &= ~(1 << 9);
|
||
|
} else if (n == 39) {
|
||
|
/* reset fg */
|
||
|
mask = 0x0f << 4;
|
||
|
s.cur_attr = (s.cur_attr & ~mask) | (s.def_attr & mask);
|
||
|
} else if (n == 49) {
|
||
|
/* reset bg */
|
||
|
mask = 0x0f;
|
||
|
s.cur_attr = (s.cur_attr & ~mask) | (s.def_attr & mask);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function empty_line(y, use_erase_char) {
|
||
|
var line, c, y1, x;
|
||
|
if (use_erase_char)
|
||
|
c = get_erase_char();
|
||
|
else
|
||
|
c = 32 | (s.def_attr << 16);
|
||
|
line = new Array();
|
||
|
for(x=0;x<s.w;x++)
|
||
|
line[x] = c;
|
||
|
y1 = s.y_base + y;
|
||
|
if (y1 >= s.cur_h)
|
||
|
y1 -= s.cur_h;
|
||
|
s.lines[y1] = line;
|
||
|
}
|
||
|
|
||
|
function scroll_down(top, bottom, use_erase_char)
|
||
|
{
|
||
|
var y, line, y1, y2;
|
||
|
|
||
|
if (top == 0 && bottom == s.h) {
|
||
|
/* increase height of buffer if possible */
|
||
|
if (s.cur_h < s.tot_h) {
|
||
|
s.cur_h++;
|
||
|
}
|
||
|
/* move down one line */
|
||
|
if (++s.y_base == s.cur_h)
|
||
|
s.y_base = 0;
|
||
|
s.y_disp = s.y_base;
|
||
|
} else {
|
||
|
/* partial scroll */
|
||
|
for(y = top; y < bottom - 1; y++) {
|
||
|
y1 = s.y_base + y;
|
||
|
if (y1 >= s.cur_h)
|
||
|
y1 -= s.cur_h;
|
||
|
y2 = y1 + 1;
|
||
|
if (y2 >= s.cur_h)
|
||
|
y2 -= s.cur_h;
|
||
|
s.lines[y1] = s.lines[y2];
|
||
|
}
|
||
|
}
|
||
|
empty_line(bottom - 1, use_erase_char);
|
||
|
update(top);
|
||
|
update(bottom - 1);
|
||
|
}
|
||
|
|
||
|
function scroll_up(top, bottom, use_erase_char) {
|
||
|
var y, y1, y2;
|
||
|
/* XXX: could scroll in the history */
|
||
|
for(y = bottom - 1; y > top; y--) {
|
||
|
y1 = s.y_base + y;
|
||
|
if (y1 >= s.cur_h)
|
||
|
y1 -= s.cur_h;
|
||
|
y2 = y1 - 1;
|
||
|
if (y2 >= s.cur_h)
|
||
|
y2 -= s.cur_h;
|
||
|
s.lines[y1] = s.lines[y2];
|
||
|
}
|
||
|
empty_line(top, use_erase_char);
|
||
|
update(top);
|
||
|
update(bottom - 1);
|
||
|
}
|
||
|
|
||
|
function down_with_scroll() {
|
||
|
s.y++;
|
||
|
if (s.y == s.scroll_bottom) {
|
||
|
s.y--;
|
||
|
scroll_down(s.scroll_top, s.scroll_bottom, false);
|
||
|
} else if (s.y >= s.h) {
|
||
|
s.y--;
|
||
|
scroll_down(0, s.h, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function up_with_scroll() {
|
||
|
if (s.y == s.scroll_top) {
|
||
|
scroll_up(s.scroll_top, s.scroll_bottom, true);
|
||
|
} else if (s.y == 0) {
|
||
|
scroll_up(0, s.h, true);
|
||
|
} else {
|
||
|
s.y--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function insert_lines(n) {
|
||
|
var y2;
|
||
|
if (n < 1)
|
||
|
n = 1;
|
||
|
if (s.y < s.scroll_bottom)
|
||
|
y2 = s.scroll_bottom;
|
||
|
else
|
||
|
y2 = s.h;
|
||
|
while (n != 0) {
|
||
|
scroll_up(s.y, y2, true);
|
||
|
n--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function delete_lines(n) {
|
||
|
var y2;
|
||
|
if (n < 1)
|
||
|
n = 1;
|
||
|
if (s.y < s.scroll_bottom)
|
||
|
y2 = s.scroll_bottom;
|
||
|
else
|
||
|
y2 = s.h;
|
||
|
while (n != 0) {
|
||
|
scroll_down(s.y, y2, true);
|
||
|
n--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var TTY_STATE_NORM = 0;
|
||
|
var TTY_STATE_ESC = 1;
|
||
|
var TTY_STATE_CSI = 2;
|
||
|
var TTY_STATE_CHARSET = 3;
|
||
|
|
||
|
function handle_char(c) {
|
||
|
var i, l, n, j, y1, y2, x1;
|
||
|
|
||
|
switch(s.state) {
|
||
|
case TTY_STATE_NORM:
|
||
|
switch(c) {
|
||
|
case 10:
|
||
|
down_with_scroll();
|
||
|
break;
|
||
|
case 13:
|
||
|
s.x = 0;
|
||
|
break;
|
||
|
case 8:
|
||
|
if (s.x > 0) {
|
||
|
s.x--;
|
||
|
}
|
||
|
break;
|
||
|
case 9: /* tab */
|
||
|
n = (s.x + 8) & ~7;
|
||
|
if (n <= s.w) {
|
||
|
s.x = n;
|
||
|
}
|
||
|
break;
|
||
|
case 27:
|
||
|
s.state = TTY_STATE_ESC;
|
||
|
break;
|
||
|
default:
|
||
|
if (c >= 32) {
|
||
|
if (s.x >= s.w) {
|
||
|
s.x = 0;
|
||
|
down_with_scroll();
|
||
|
}
|
||
|
y1 = s.y + s.y_base;
|
||
|
if (y1 >= s.cur_h)
|
||
|
y1 -= s.cur_h;
|
||
|
s.lines[y1][s.x] = (c & 0xffff) |
|
||
|
(s.cur_attr << 16);
|
||
|
s.x++;
|
||
|
update(s.y);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case TTY_STATE_ESC:
|
||
|
switch(c) {
|
||
|
case 91: // '['
|
||
|
s.esc_params = new Array();
|
||
|
s.cur_param = 0;
|
||
|
s.esc_prefix = 0;
|
||
|
s.state = TTY_STATE_CSI;
|
||
|
break;
|
||
|
case 40: // '('
|
||
|
case 41: // ')'
|
||
|
s.state = TTY_STATE_CHARSET;
|
||
|
break;
|
||
|
case 61: // '='
|
||
|
s.application_keypad = true;
|
||
|
s.state = TTY_STATE_NORM;
|
||
|
break;
|
||
|
case 62: // '>'
|
||
|
s.application_keypad = false;
|
||
|
s.state = TTY_STATE_NORM;
|
||
|
break;
|
||
|
case 77: // 'M'
|
||
|
up_with_scroll();
|
||
|
s.state = TTY_STATE_NORM;
|
||
|
break;
|
||
|
default:
|
||
|
s.state = TTY_STATE_NORM;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case TTY_STATE_CSI:
|
||
|
if (c >= 48 && c <= 57) { // '0' '9'
|
||
|
/* numeric */
|
||
|
s.cur_param = s.cur_param * 10 + c - 48;
|
||
|
} else {
|
||
|
if (c == 63) { // '?'
|
||
|
s.esc_prefix = c;
|
||
|
break;
|
||
|
}
|
||
|
/* add parsed parameter */
|
||
|
s.esc_params[s.esc_params.length] = s.cur_param;
|
||
|
s.cur_param = 0;
|
||
|
if (c == 59) // ;
|
||
|
break;
|
||
|
s.state = TTY_STATE_NORM;
|
||
|
|
||
|
// console.log("term: csi=" + s.esc_params + " cmd="+c);
|
||
|
switch(c) {
|
||
|
case 64: // '@' insert chars
|
||
|
insert_chars(s.esc_params[0]);
|
||
|
break;
|
||
|
case 65: // 'A' up
|
||
|
n = s.esc_params[0];
|
||
|
if (n < 1)
|
||
|
n = 1;
|
||
|
s.y -= n;
|
||
|
if (s.y < 0)
|
||
|
s.y = 0;
|
||
|
break;
|
||
|
case 66: // 'B' down
|
||
|
n = s.esc_params[0];
|
||
|
if (n < 1)
|
||
|
n = 1;
|
||
|
s.y += n;
|
||
|
if (s.y >= s.h)
|
||
|
s.y = s.h - 1;
|
||
|
break;
|
||
|
case 67: // 'C' right
|
||
|
n = s.esc_params[0];
|
||
|
if (n < 1)
|
||
|
n = 1;
|
||
|
s.x += n;
|
||
|
if (s.x >= s.w - 1)
|
||
|
s.x = s.w - 1;
|
||
|
break;
|
||
|
case 68: // 'D' left
|
||
|
n = s.esc_params[0];
|
||
|
if (n < 1)
|
||
|
n = 1;
|
||
|
s.x -= n;
|
||
|
if (s.x < 0)
|
||
|
s.x = 0;
|
||
|
break;
|
||
|
case 71: /* 'G' cursor character absolute */
|
||
|
x1 = s.esc_params[0] - 1;
|
||
|
if (x1 < 0)
|
||
|
x1 = 0;
|
||
|
else if (x1 >= s.w)
|
||
|
x1 = s.w - 1;
|
||
|
s.x = x1;
|
||
|
break;
|
||
|
case 72: // 'H' goto xy
|
||
|
y1 = s.esc_params[0] - 1;
|
||
|
if (s.esc_params.length >= 2)
|
||
|
x1 = s.esc_params[1] - 1;
|
||
|
else
|
||
|
x1 = 0;
|
||
|
if (y1 < 0)
|
||
|
y1 = 0;
|
||
|
else if (y1 >= s.h)
|
||
|
y1 = s.h - 1;
|
||
|
if (x1 < 0)
|
||
|
x1 = 0;
|
||
|
else if (x1 >= s.w)
|
||
|
x1 = s.w - 1;
|
||
|
s.x = x1;
|
||
|
s.y = y1;
|
||
|
break;
|
||
|
case 74: // 'J' erase in display
|
||
|
erase_in_display(s.esc_params[0]);
|
||
|
break;
|
||
|
case 75: // 'K' erase in line
|
||
|
erase_in_line(s.esc_params[0]);
|
||
|
break;
|
||
|
case 76: // 'L' insert lines
|
||
|
insert_lines(s.esc_params[0]);
|
||
|
break;
|
||
|
case 77: // 'M' insert lines
|
||
|
delete_lines(s.esc_params[0]);
|
||
|
break;
|
||
|
case 80: // 'P'
|
||
|
delete_chars(s.esc_params[0]);
|
||
|
break;
|
||
|
case 100: // 'd' line position absolute
|
||
|
{
|
||
|
y1 = s.esc_params[0] - 1;
|
||
|
if (y1 < 0)
|
||
|
y1 = 0;
|
||
|
else if (y1 >= s.h)
|
||
|
y1 = s.h - 1;
|
||
|
s.y = y1;
|
||
|
}
|
||
|
break;
|
||
|
case 104: // 'h': set mode
|
||
|
if (s.esc_prefix == 63 && s.esc_params[0] == 1) {
|
||
|
s.application_cursor = true;
|
||
|
}
|
||
|
break;
|
||
|
case 108: // 'l': reset mode
|
||
|
if (s.esc_prefix == 63 && s.esc_params[0] == 1) {
|
||
|
s.application_cursor = false;
|
||
|
}
|
||
|
break;
|
||
|
case 109: // 'm': set color
|
||
|
csi_colors(s.esc_params);
|
||
|
break;
|
||
|
case 110: // 'n' return the cursor position
|
||
|
s.queue_chars("\x1b[" + (s.y + 1) + ";" + (s.x + 1) + "R");
|
||
|
break;
|
||
|
case 114: // 'r' set scroll region
|
||
|
y1 = s.esc_params[0] - 1;
|
||
|
if (y1 < 0)
|
||
|
y1 = 0;
|
||
|
else if (y1 >= s.h)
|
||
|
y1 = s.h - 1;
|
||
|
if (s.esc_params.length >= 2)
|
||
|
y2 = s.esc_params[1];
|
||
|
else
|
||
|
y2 = s.h;
|
||
|
if (y2 >= s.h || y2 <= y1)
|
||
|
y2 = s.h;
|
||
|
s.scroll_top = y1;
|
||
|
s.scroll_bottom = y2;
|
||
|
s.x = 0;
|
||
|
s.y = 0;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case TTY_STATE_CHARSET:
|
||
|
/* just ignore */
|
||
|
s.state = TTY_STATE_NORM;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function handle_utf8(c) {
|
||
|
if (s.utf8_state !== 0 && (c >= 0x80 && c < 0xc0)) {
|
||
|
s.utf8_val = (s.utf8_val << 6) | (c & 0x3F);
|
||
|
s.utf8_state--;
|
||
|
if (s.utf8_state === 0) {
|
||
|
handle_char(s.utf8_val);
|
||
|
}
|
||
|
} else if (c >= 0xc0 && c < 0xf8) {
|
||
|
s.utf8_state = 1 + (c >= 0xe0) + (c >= 0xf0);
|
||
|
s.utf8_val = c & ((1 << (6 - s.utf8_state)) - 1);
|
||
|
} else {
|
||
|
s.utf8_state = 0;
|
||
|
handle_char(c);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var i, c, utf8;
|
||
|
|
||
|
/* update region is in ymin ymax */
|
||
|
s = this;
|
||
|
ymin = s.h;
|
||
|
ymax = -1;
|
||
|
update(s.y); // remove the cursor
|
||
|
/* reset top of displayed screen to top of real screen */
|
||
|
if (s.y_base != s.y_disp) {
|
||
|
s.y_disp = s.y_base;
|
||
|
/* force redraw */
|
||
|
ymin = 0;
|
||
|
ymax = s.h - 1;
|
||
|
}
|
||
|
utf8 = s.utf8;
|
||
|
for(i = 0; i < str.length; i++) {
|
||
|
c = str.charCodeAt(i);
|
||
|
if (utf8)
|
||
|
handle_utf8(c);
|
||
|
else
|
||
|
handle_char(c);
|
||
|
}
|
||
|
update(s.y); // show the cursor
|
||
|
|
||
|
if (ymax >= ymin)
|
||
|
s.refresh(ymin, ymax);
|
||
|
};
|
||
|
|
||
|
Term.prototype.writeln = function (str)
|
||
|
{
|
||
|
this.write(str + '\r\n');
|
||
|
};
|
||
|
|
||
|
Term.prototype.interceptBrowserExit = function (ev)
|
||
|
{
|
||
|
/* At least avoid exiting the navigator if Ctrl-Q or Ctrl-W are
|
||
|
* pressed */
|
||
|
if (ev.ctrlKey) {
|
||
|
window.onbeforeunload = function() {
|
||
|
window.onbeforeunload = null;
|
||
|
return "CTRL-W or Ctrl-Q cannot be sent to the emulator.";
|
||
|
};
|
||
|
} else {
|
||
|
window.onbeforeunload = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Term.prototype.keyDownHandler = function (ev)
|
||
|
{
|
||
|
var str;
|
||
|
|
||
|
this.interceptBrowserExit(ev);
|
||
|
|
||
|
str="";
|
||
|
switch(ev.keyCode) {
|
||
|
case 8: /* backspace */
|
||
|
str = "\x7f";
|
||
|
break;
|
||
|
case 9: /* tab */
|
||
|
str = "\x09";
|
||
|
break;
|
||
|
case 13: /* enter */
|
||
|
str = "\x0d";
|
||
|
break;
|
||
|
case 27: /* escape */
|
||
|
str = "\x1b";
|
||
|
break;
|
||
|
case 37: /* left */
|
||
|
if (ev.ctrlKey) {
|
||
|
str = "\x1b[1;5D";
|
||
|
} else if (this.application_cursor) {
|
||
|
str = "\x1bOD";
|
||
|
} else {
|
||
|
str = "\x1b[D";
|
||
|
}
|
||
|
break;
|
||
|
case 39: /* right */
|
||
|
if (ev.ctrlKey) {
|
||
|
str = "\x1b[1;5C";
|
||
|
} else if (this.application_cursor) {
|
||
|
str = "\x1bOC";
|
||
|
} else {
|
||
|
str = "\x1b[C";
|
||
|
}
|
||
|
break;
|
||
|
case 38: /* up */
|
||
|
if (ev.ctrlKey) {
|
||
|
this.scroll_disp(-1);
|
||
|
} else if (this.application_cursor) {
|
||
|
str = "\x1bOA";
|
||
|
} else {
|
||
|
str = "\x1b[A";
|
||
|
}
|
||
|
break;
|
||
|
case 40: /* down */
|
||
|
if (ev.ctrlKey) {
|
||
|
this.scroll_disp(1);
|
||
|
} else if (this.application_cursor) {
|
||
|
str = "\x1bOB";
|
||
|
} else {
|
||
|
str = "\x1b[B";
|
||
|
}
|
||
|
break;
|
||
|
case 46: /* delete */
|
||
|
str = "\x1b[3~";
|
||
|
break;
|
||
|
case 45: /* insert */
|
||
|
str = "\x1b[2~";
|
||
|
break;
|
||
|
case 36: /* home */
|
||
|
if (this.linux_console)
|
||
|
str = "\x1b[1~";
|
||
|
else if (this.application_keypad)
|
||
|
str = "\x1bOH";
|
||
|
else
|
||
|
str = "\x1b[H";
|
||
|
break;
|
||
|
case 35: /* end */
|
||
|
if (this.linux_console)
|
||
|
str = "\x1b[4~";
|
||
|
else if (this.application_keypad)
|
||
|
str = "\x1bOF";
|
||
|
else
|
||
|
str = "\x1b[F";
|
||
|
break;
|
||
|
case 33: /* page up */
|
||
|
if (ev.ctrlKey) {
|
||
|
this.scroll_disp(-(this.h - 1));
|
||
|
} else {
|
||
|
str = "\x1b[5~";
|
||
|
}
|
||
|
break;
|
||
|
case 34: /* page down */
|
||
|
if (ev.ctrlKey) {
|
||
|
this.scroll_disp(this.h - 1);
|
||
|
} else {
|
||
|
str = "\x1b[6~";
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
if (ev.ctrlKey) {
|
||
|
/* ctrl + key */
|
||
|
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
|
||
|
str = String.fromCharCode(ev.keyCode - 64);
|
||
|
} else if (ev.keyCode == 32) {
|
||
|
str = String.fromCharCode(0);
|
||
|
}
|
||
|
} else if ((!this.is_mac && ev.altKey) ||
|
||
|
(this.is_mac && ev.metaKey)) {
|
||
|
/* meta + key (Note: we only send lower case) */
|
||
|
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
|
||
|
str = "\x1b" + String.fromCharCode(ev.keyCode + 32);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
// console.log("keydown: keycode=" + ev.keyCode + " charcode=" + ev.charCode + " str=" + str + " ctrl=" + ev.ctrlKey + " alt=" + ev.altKey + " meta=" + ev.metaKey);
|
||
|
if (str) {
|
||
|
if (ev.stopPropagation)
|
||
|
ev.stopPropagation();
|
||
|
if (ev.preventDefault)
|
||
|
ev.preventDefault();
|
||
|
|
||
|
this.show_cursor();
|
||
|
this.key_rep_state = 1;
|
||
|
this.key_rep_str = str;
|
||
|
this.handler(str);
|
||
|
return false;
|
||
|
} else {
|
||
|
this.key_rep_state = 0;
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Term.prototype.keyUpHandler = function (ev)
|
||
|
{
|
||
|
this.interceptBrowserExit(ev);
|
||
|
};
|
||
|
|
||
|
Term.prototype.to_utf8 = function(s)
|
||
|
{
|
||
|
var i, n = s.length, r, c;
|
||
|
r = "";
|
||
|
for(i = 0; i < n; i++) {
|
||
|
c = s.charCodeAt(i);
|
||
|
if (c < 0x80) {
|
||
|
r += String.fromCharCode(c);
|
||
|
} else if (c < 0x800) {
|
||
|
r += String.fromCharCode((c >> 6) | 0xc0, (c & 0x3f) | 0x80);
|
||
|
} else if (c < 0x10000) {
|
||
|
r += String.fromCharCode((c >> 12) | 0xe0,
|
||
|
((c >> 6) & 0x3f) | 0x80,
|
||
|
(c & 0x3f) | 0x80);
|
||
|
} else {
|
||
|
r += String.fromCharCode((c >> 18) | 0xf0,
|
||
|
((c >> 12) & 0x3f) | 0x80,
|
||
|
((c >> 6) & 0x3f) | 0x80,
|
||
|
(c & 0x3f) | 0x80);
|
||
|
}
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
Term.prototype.inputHandler = function (ev)
|
||
|
{
|
||
|
var str;
|
||
|
str = this.textarea_el.value;
|
||
|
if (str) {
|
||
|
this.textarea_el.value = "";
|
||
|
this.show_cursor();
|
||
|
if (this.utf8)
|
||
|
str = this.to_utf8(str);
|
||
|
this.handler(str);
|
||
|
return false;
|
||
|
} else {
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Term.prototype.termKeyDownHandler = function(ev)
|
||
|
{
|
||
|
this.interceptBrowserExit(ev);
|
||
|
/* give the focus back to the textarea when a key is pressed */
|
||
|
this.textarea_el.focus();
|
||
|
}
|
||
|
|
||
|
Term.prototype.termMouseUpHandler = function(ev)
|
||
|
{
|
||
|
var sel;
|
||
|
/* if no selection, can switch back up to the textarea focus */
|
||
|
sel = window.getSelection();
|
||
|
if (!sel || sel.isCollapsed)
|
||
|
this.textarea_el.focus();
|
||
|
}
|
||
|
|
||
|
Term.prototype.focusHandler = function (ev)
|
||
|
{
|
||
|
this.textarea_has_focus = true;
|
||
|
};
|
||
|
|
||
|
Term.prototype.blurHandler = function (ev)
|
||
|
{
|
||
|
/* allow unloading the page */
|
||
|
window.onbeforeunload = null;
|
||
|
this.textarea_has_focus = false;
|
||
|
};
|
||
|
|
||
|
Term.prototype.pasteHandler = function (ev)
|
||
|
{
|
||
|
var c, str;
|
||
|
if (!this.textarea_has_focus) {
|
||
|
c = ev.clipboardData;
|
||
|
if (c) {
|
||
|
str = c.getData("text/plain");
|
||
|
if (this.utf8)
|
||
|
str = this.to_utf8(str);
|
||
|
this.queue_chars(str);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Term.prototype.wheelHandler = function (ev)
|
||
|
{
|
||
|
if (ev.deltaY < 0)
|
||
|
this.scroll_disp(-3);
|
||
|
else if (ev.deltaY > 0)
|
||
|
this.scroll_disp(3);
|
||
|
ev.stopPropagation();
|
||
|
}
|
||
|
|
||
|
Term.prototype.mouseDownHandler = function (ev)
|
||
|
{
|
||
|
this.thumb_el.onmouseup = this.mouseUpHandler.bind(this);
|
||
|
document.onmousemove = this.mouseMoveHandler.bind(this);
|
||
|
document.onmouseup = this.mouseUpHandler.bind(this);
|
||
|
|
||
|
/* disable potential selection */
|
||
|
document.body.className += " noSelect";
|
||
|
|
||
|
this.mouseMoveHandler(ev);
|
||
|
}
|
||
|
|
||
|
Term.prototype.mouseMoveHandler = function (ev)
|
||
|
{
|
||
|
var total_size, pos, new_y_disp, y, y0;
|
||
|
total_size = this.term_el.clientHeight;
|
||
|
y = ev.clientY - this.track_el.getBoundingClientRect().top;
|
||
|
pos = Math.floor((y - (this.thumb_size / 2)) * this.cur_h / total_size);
|
||
|
new_y_disp = Math.min(Math.max(pos, 0), this.cur_h - this.h);
|
||
|
/* position of the first line of the scroll back buffer */
|
||
|
y0 = (this.y_base + this.h) % this.cur_h;
|
||
|
new_y_disp += y0;
|
||
|
if (new_y_disp >= this.cur_h)
|
||
|
new_y_disp -= this.cur_h;
|
||
|
if (new_y_disp != this.y_disp) {
|
||
|
this.y_disp = new_y_disp;
|
||
|
this.refresh(0, this.h - 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Term.prototype.mouseUpHandler = function (ev)
|
||
|
{
|
||
|
this.thumb_el.onmouseup = null;
|
||
|
document.onmouseup = null;
|
||
|
document.onmousemove = null;
|
||
|
document.body.className = document.body.className.replace(" noSelect", "");
|
||
|
}
|
||
|
|
||
|
/* output queue to send back asynchronous responses */
|
||
|
Term.prototype.queue_chars = function (str)
|
||
|
{
|
||
|
this.output_queue += str;
|
||
|
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 = "";
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Term.prototype.getSize = function ()
|
||
|
{
|
||
|
return [this.w, this.h];
|
||
|
};
|
||
|
|
||
|
/* resize the terminal (size in pixels). Return true if the display
|
||
|
size was modified. */
|
||
|
/* XXX: may be simpler to separate the scrollback buffer from the
|
||
|
screen buffer */
|
||
|
Term.prototype.resizePixel = function (new_width, new_height)
|
||
|
{
|
||
|
var new_w, new_h, y, x, line, c, row_el, d, new_cur_h, e;
|
||
|
|
||
|
if (new_width == this.term_width && new_height == this.term_height)
|
||
|
return false;
|
||
|
new_w = Math.floor((new_width - this.scrollbar_width) /
|
||
|
this.char_width);
|
||
|
new_h = Math.floor(new_height / this.char_height);
|
||
|
if (new_w <= 0 || new_h <= 0 || new_h > this.tot_h)
|
||
|
return false;
|
||
|
|
||
|
this.term_width = new_width;
|
||
|
this.term_height = new_height;
|
||
|
this.term_el.style.width = this.term_width + "px";
|
||
|
this.term_el.style.height = this.term_height + "px";
|
||
|
|
||
|
/* XXX: could keep the EOL positions */
|
||
|
if (new_w < this.w) {
|
||
|
/* reduce the line width */
|
||
|
for(y = 0; y < this.cur_h;y++) {
|
||
|
line = this.lines[y];
|
||
|
line = line.slice(0, new_w);
|
||
|
}
|
||
|
} else if (new_w > this.w) {
|
||
|
/* increase the line width */
|
||
|
c = 32 | (this.def_attr << 16);
|
||
|
for(y = 0; y < this.cur_h;y++) {
|
||
|
line = this.lines[y];
|
||
|
for(x = this.w; x < new_w; x++)
|
||
|
line[x] = c;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.x >= new_w)
|
||
|
this.x = new_w - 1;
|
||
|
|
||
|
d = new_h - this.h;
|
||
|
if (d < 0) {
|
||
|
d = -d;
|
||
|
/* remove displayed lines */
|
||
|
|
||
|
/* strip the DOM terminal content */
|
||
|
for(y = new_h; y < this.h; y++) {
|
||
|
row_el = this.rows_el[y];
|
||
|
this.content_el.removeChild(row_el);
|
||
|
}
|
||
|
this.rows_el = this.rows_el.slice(0, new_h);
|
||
|
|
||
|
/* adjust cursor position if needed */
|
||
|
if (this.y >= new_h) {
|
||
|
if (d > this.y)
|
||
|
d = this.y;
|
||
|
this.y -= d;
|
||
|
this.y_base += d;
|
||
|
if (this.y_base >= this.tot_h)
|
||
|
this.y_base -= this.tot_h;
|
||
|
}
|
||
|
|
||
|
if (this.scroll_bottom > new_h)
|
||
|
this.scroll_bottom = new_h;
|
||
|
/* fail safe for scroll top */
|
||
|
if (this.scroll_top >= this.scroll_bottom)
|
||
|
this.scroll_top = 0;
|
||
|
|
||
|
} else if (d > 0) {
|
||
|
/* add displayed lines */
|
||
|
|
||
|
if (this.cur_h == this.tot_h) {
|
||
|
if (d > this.tot_h - this.h)
|
||
|
d = this.tot_h - this.h;
|
||
|
} else {
|
||
|
if (d > this.y_base)
|
||
|
d = this.y_base;
|
||
|
}
|
||
|
this.y_base -= d;
|
||
|
if (this.y_base < 0)
|
||
|
this.y_base += this.tot_h;
|
||
|
this.y += d;
|
||
|
|
||
|
if (this.scroll_bottom == this.h)
|
||
|
this.scroll_bottom = new_h;
|
||
|
|
||
|
/* extend the DOM terminal content */
|
||
|
for(y = this.h; y < new_h; y++) {
|
||
|
row_el = document.createElement("div");
|
||
|
this.rows_el.push(row_el);
|
||
|
this.content_el.appendChild(row_el);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.cur_h < this.tot_h) {
|
||
|
new_cur_h = this.y_base + new_h;
|
||
|
if (new_cur_h < this.cur_h) {
|
||
|
/* remove lines in the scroll back buffer */
|
||
|
this.lines = this.lines.slice(0, new_cur_h);
|
||
|
} else if (new_cur_h > this.cur_h) {
|
||
|
/* add lines in the scroll back buffer */
|
||
|
c = 32 | (this.def_attr << 16);
|
||
|
for(y = this.cur_h; y < new_cur_h; y++) {
|
||
|
line = new Array();
|
||
|
for(x = 0; x < new_w; x++)
|
||
|
line[x] = c;
|
||
|
this.lines[y] = line;
|
||
|
}
|
||
|
}
|
||
|
this.cur_h = new_cur_h;
|
||
|
}
|
||
|
|
||
|
this.w = new_w;
|
||
|
this.h = new_h;
|
||
|
|
||
|
if (this.y >= this.h)
|
||
|
this.y = this.h - 1;
|
||
|
|
||
|
/* reset display position */
|
||
|
this.y_disp = this.y_base;
|
||
|
/*
|
||
|
console.log("lines.length", this.lines.length, "cur_h", this.cur_h,
|
||
|
"y_base", this.y_base, "h", this.h,
|
||
|
"scroll_bottom", this.scroll_bottom);
|
||
|
*/
|
||
|
this.refresh(0, this.h - 1);
|
||
|
return true;
|
||
|
}
|