blob: e80897c846374d32ab029292aac346c40a93fa4b [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
lib.rtdep('lib.colors', 'lib.f', 'lib.UTF8Decoder',
'hterm.VT.CharacterMap');
/**
* Constructor for the VT escape sequence interpreter.
*
* The interpreter operates on a terminal object capable of performing cursor
* move operations, painting characters, etc.
*
* This interpreter is intended to be compatible with xterm, though it
* ignores some of the more esoteric escape sequences.
*
* Control sequences are documented in hterm/doc/ControlSequences.md.
*
* @param {hterm.Terminal} terminal Terminal to use with the interpreter.
*/
hterm.VT = function(terminal) {
/**
* The display terminal object associated with this virtual terminal.
*/
this.terminal = terminal;
terminal.onMouse = this.onTerminalMouse_.bind(this);
this.mouseReport = this.MOUSE_REPORT_DISABLED;
// Parse state left over from the last parse. You should use the parseState
// instance passed into your parse routine, rather than reading
// this.parseState_ directly.
this.parseState_ = new hterm.VT.ParseState(this.parseUnknown_);
// Any "leading modifiers" for the escape sequence, such as '?', ' ', or the
// other modifiers handled in this.parseCSI_.
this.leadingModifier_ = '';
// Any "trailing modifiers". Same character set as a leading modifier,
// except these are found after the numeric arguments.
this.trailingModifier_ = '';
// Whether or not to respect the escape codes for setting terminal width.
this.allowColumnWidthChanges_ = false;
// The amount of time we're willing to wait for the end of an OSC sequence.
this.oscTimeLimit_ = 20000;
// Construct a regular expression to match the known one-byte control chars.
// This is used in parseUnknown_ to quickly scan a string for the next
// control character.
var cc1 = Object.keys(hterm.VT.CC1).map(
function(e) {
return '\\x' + lib.f.zpad(e.charCodeAt().toString(16), 2)
}).join('');
this.cc1Pattern_ = new RegExp('[' + cc1 + ']');
// Decoder to maintain UTF-8 decode state.
this.utf8Decoder_ = new lib.UTF8Decoder();
/**
* Whether to accept the 8-bit control characters.
*
* An 8-bit control character is one with the eighth bit set. These
* didn't work on 7-bit terminals so they all have two byte equivalents.
* Most hosts still only use the two-byte versions.
*
* We ignore 8-bit control codes by default. This is in order to avoid
* issues with "accidental" usage of codes that need to be terminated.
* The "accident" usually involves cat'ing binary data.
*/
this.enable8BitControl = false;
/**
* Whether to allow the OSC 52 sequence to write to the system clipboard.
*/
this.enableClipboardWrite = true;
/**
* Respect the host's attempt to change the cursor blink status using
* the DEC Private mode 12.
*/
this.enableDec12 = false;
/**
* The expected encoding method for data received from the host.
*/
this.characterEncoding = 'utf-8';
/**
* Max length of an unterminated DCS, OSC, PM or APC sequence before we give
* up and ignore the code.
*
* These all end with a String Terminator (ST, '\x9c', ESC '\\') or
* (BEL, '\x07') character, hence the "string sequence" moniker.
*/
this.maxStringSequence = 1024;
/**
* If true, emit warnings when we encounter a control character or escape
* sequence that we don't recognize or explicitly ignore.
*/
this.warnUnimplemented = true;
/**
* The default G0...G3 character maps.
*/
this.G0 = hterm.VT.CharacterMap.maps['B'];
this.G1 = hterm.VT.CharacterMap.maps['0'];
this.G2 = hterm.VT.CharacterMap.maps['B'];
this.G3 = hterm.VT.CharacterMap.maps['B'];
/**
* The 7-bit visible character set.
*
* This is a mapping from inbound data to display glyph. The GL set
* contains the 94 bytes from 0x21 to 0x7e.
*
* The default GL set is 'B', US ASCII.
*/
this.GL = 'G0';
/**
* The 8-bit visible character set.
*
* This is a mapping from inbound data to display glyph. The GR set
* contains the 94 bytes from 0xa1 to 0xfe.
*/
this.GR = 'G0';
// Saved state used in DECSC.
//
// This is a place to store a copy VT state, it is *not* the active state.
this.savedState_ = new hterm.VT.CursorState(this);
};
/**
* No mouse events.
*/
hterm.VT.prototype.MOUSE_REPORT_DISABLED = 0;
/**
* DECSET mode 1000.
*
* Report mouse down/up events only.
*/
hterm.VT.prototype.MOUSE_REPORT_CLICK = 1;
/**
* DECSET mode 1002.
*
* Report mouse down/up and movement while a button is down.
*/
hterm.VT.prototype.MOUSE_REPORT_DRAG = 3;
/**
* ParseState constructor.
*
* This object tracks the current state of the parse. It has fields for the
* current buffer, position in the buffer, and the parse function.
*
* @param {function} defaultFunc The default parser function.
* @param {string} opt_buf Optional string to use as the current buffer.
*/
hterm.VT.ParseState = function(defaultFunction, opt_buf) {
this.defaultFunction = defaultFunction;
this.buf = opt_buf || null;
this.pos = 0;
this.func = defaultFunction;
this.args = [];
};
/**
* Reset the parser function, buffer, and position.
*/
hterm.VT.ParseState.prototype.reset = function(opt_buf) {
this.resetParseFunction();
this.resetBuf(opt_buf || '');
this.resetArguments();
};
/**
* Reset the parser function only.
*/
hterm.VT.ParseState.prototype.resetParseFunction = function() {
this.func = this.defaultFunction;
};
/**
* Reset the buffer and position only.
*
* @param {string} buf Optional new value for buf, defaults to null.
*/
hterm.VT.ParseState.prototype.resetBuf = function(opt_buf) {
this.buf = (typeof opt_buf == 'string') ? opt_buf : null;
this.pos = 0;
};
/**
* Reset the arguments list only.
*
* @param {string} opt_arg_zero Optional initial value for args[0].
*/
hterm.VT.ParseState.prototype.resetArguments = function(opt_arg_zero) {
this.args.length = 0;
if (typeof opt_arg_zero != 'undefined')
this.args[0] = opt_arg_zero;
};
/**
* Get an argument as an integer.
*
* @param {number} argnum The argument number to retrieve.
*/
hterm.VT.ParseState.prototype.iarg = function(argnum, defaultValue) {
var str = this.args[argnum];
if (str) {
var ret = parseInt(str, 10);
// An argument of zero is treated as the default value.
if (ret == 0)
ret = defaultValue;
return ret;
}
return defaultValue;
};
/**
* Advance the parse position.
*
* @param {integer} count The number of bytes to advance.
*/
hterm.VT.ParseState.prototype.advance = function(count) {
this.pos += count;
};
/**
* Return the remaining portion of the buffer without affecting the parse
* position.
*
* @return {string} The remaining portion of the buffer.
*/
hterm.VT.ParseState.prototype.peekRemainingBuf = function() {
return this.buf.substr(this.pos);
};
/**
* Return the next single character in the buffer without affecting the parse
* position.
*
* @return {string} The next character in the buffer.
*/
hterm.VT.ParseState.prototype.peekChar = function() {
return this.buf.substr(this.pos, 1);
};
/**
* Return the next single character in the buffer and advance the parse
* position one byte.
*
* @return {string} The next character in the buffer.
*/
hterm.VT.ParseState.prototype.consumeChar = function() {
return this.buf.substr(this.pos++, 1);
};
/**
* Return true if the buffer is empty, or the position is past the end.
*/
hterm.VT.ParseState.prototype.isComplete = function() {
return this.buf == null || this.buf.length <= this.pos;
};
hterm.VT.CursorState = function(vt) {
this.vt_ = vt;
this.save();
};
hterm.VT.CursorState.prototype.save = function() {
this.cursor = this.vt_.terminal.saveCursor();
this.textAttributes = this.vt_.terminal.getTextAttributes().clone();
this.GL = this.vt_.GL;
this.GR = this.vt_.GR;
this.G0 = this.vt_.G0;
this.G1 = this.vt_.G1;
this.G2 = this.vt_.G2;
this.G3 = this.vt_.G3;
};
hterm.VT.CursorState.prototype.restore = function() {
this.vt_.terminal.restoreCursor(this.cursor);
this.vt_.terminal.setTextAttributes(this.textAttributes.clone());
this.vt_.GL = this.GL;
this.vt_.GR = this.GR;
this.vt_.G0 = this.G0;
this.vt_.G1 = this.G1;
this.vt_.G2 = this.G2;
this.vt_.G3 = this.G3;
};
hterm.VT.prototype.reset = function() {
this.G0 = hterm.VT.CharacterMap.maps['B'];
this.G1 = hterm.VT.CharacterMap.maps['0'];
this.G2 = hterm.VT.CharacterMap.maps['B'];
this.G3 = hterm.VT.CharacterMap.maps['B'];
this.GL = 'G0';
this.GR = 'G0';
this.savedState_ = new hterm.VT.CursorState(this);
this.mouseReport = this.MOUSE_REPORT_DISABLED;
};
/**
* Handle terminal mouse events.
*
* See the "Mouse Tracking" section of [xterm].
*/
hterm.VT.prototype.onTerminalMouse_ = function(e) {
if (this.mouseReport == this.MOUSE_REPORT_DISABLED)
return;
// Temporary storage for our response.
var response;
// Modifier key state.
var mod = 0;
if (e.shiftKey)
mod |= 4;
if (e.metaKey || (this.terminal.keyboard.altIsMeta && e.altKey))
mod |= 8;
if (e.ctrlKey)
mod |= 16;
// TODO(rginda): We should also support mode 1005 and/or 1006 to extend the
// coordinate space. Though, after poking around just a little, I wasn't
// able to get vi or emacs to use either of these modes.
var x = String.fromCharCode(lib.f.clamp(e.terminalColumn + 32, 32, 255));
var y = String.fromCharCode(lib.f.clamp(e.terminalRow + 32, 32, 255));
switch (e.type) {
case 'wheel':
// Mouse wheel is treated as button 1 or 2 plus an additional 64.
b = (((e.deltaY * -1) > 0) ? 0 : 1) + 96;
b |= mod;
response = '\x1b[M' + String.fromCharCode(b) + x + y;
// Keep the terminal from scrolling.
e.preventDefault();
break;
case 'mousedown':
// Buttons are encoded as button number plus 32.
var b = Math.min(e.button, 2) + 32;
// And mix in the modifier keys.
b |= mod;
response = '\x1b[M' + String.fromCharCode(b) + x + y;
break;
case 'mouseup':
// Mouse up has no indication of which button was released.
response = '\x1b[M\x23' + x + y;
break;
case 'mousemove':
if (this.mouseReport == this.MOUSE_REPORT_DRAG) {
// Standard button bits.
b = 32 + Math.min(e.button, 2);
// Add 32 to indicate mouse motion.
b += 32;
// And mix in the modifier keys.
b |= mod;
response = '\x1b[M' + String.fromCharCode(b) + x + y;
}
break;
case 'click':
case 'dblclick':
break;
default:
console.error('Unknown mouse event: ' + e.type, e);
break;
}
if (response)
this.terminal.io.sendString(response);
};
/**
* Interpret a string of characters, displaying the results on the associated
* terminal object.
*
* The buffer will be decoded according to the 'receive-encoding' preference.
*/
hterm.VT.prototype.interpret = function(buf) {
this.parseState_.resetBuf(this.decode(buf));
while (!this.parseState_.isComplete()) {
var func = this.parseState_.func;
var pos = this.parseState_.pos;
var buf = this.parseState_.buf;
this.parseState_.func.call(this, this.parseState_);
if (this.parseState_.func == func && this.parseState_.pos == pos &&
this.parseState_.buf == buf) {
throw 'Parser did not alter the state!';
}
}
};
/**
* Decode a string according to the 'receive-encoding' preference.
*/
hterm.VT.prototype.decode = function(str) {
if (this.characterEncoding == 'utf-8')
return this.decodeUTF8(str);
return str;
};
/**
* Encode a UTF-16 string as UTF-8.
*
* See also: https://en.wikipedia.org/wiki/UTF-16
*/
hterm.VT.prototype.encodeUTF8 = function(str) {
return lib.encodeUTF8(str);
};
/**
* Decode a UTF-8 string into UTF-16.
*/
hterm.VT.prototype.decodeUTF8 = function(str) {
return this.utf8Decoder_.decode(str);
};
/**
* The default parse function.
*
* This will scan the string for the first 1-byte control character (C0/C1
* characters from [CTRL]). Any plain text coming before the code will be
* printed to the terminal, then the control character will be dispatched.
*/
hterm.VT.prototype.parseUnknown_ = function(parseState) {
var self = this;
function print(str) {
if (self[self.GL].GL)
str = self[self.GL].GL(str);
if (self[self.GR].GR)
str = self[self.GR].GR(str);
self.terminal.print(str);
};
// Search for the next contiguous block of plain text.
var buf = parseState.peekRemainingBuf();
var nextControl = buf.search(this.cc1Pattern_);
if (nextControl == 0) {
// We've stumbled right into a control character.
this.dispatch('CC1', buf.substr(0, 1), parseState);
parseState.advance(1);
return;
}
if (nextControl == -1) {
// There are no control characters in this string.
print(buf);
parseState.reset();
return;
}
print(buf.substr(0, nextControl));
this.dispatch('CC1', buf.substr(nextControl, 1), parseState);
parseState.advance(nextControl + 1);
};
/**
* Parse a Control Sequence Introducer code and dispatch it.
*
* See [CSI] for some useful information about these codes.
*/
hterm.VT.prototype.parseCSI_ = function(parseState) {
var ch = parseState.peekChar();
var args = parseState.args;
if (ch >= '@' && ch <= '~') {
// This is the final character.
this.dispatch('CSI', this.leadingModifier_ + this.trailingModifier_ + ch,
parseState);
parseState.resetParseFunction();
} else if (ch == ';') {
// Parameter delimiter.
if (this.trailingModifier_) {
// Parameter delimiter after the trailing modifier. That's a paddlin'.
parseState.resetParseFunction();
} else {
if (!args.length) {
// They omitted the first param, we need to supply it.
args.push('');
}
args.push('');
}
} else if (ch >= '0' && ch <= '9') {
// Next byte in the current parameter.
if (this.trailingModifier_) {
// Numeric parameter after the trailing modifier. That's a paddlin'.
parseState.resetParseFunction();
} else {
if (!args.length) {
args[0] = ch;
} else {
args[args.length - 1] += ch;
}
}
} else if (ch >= ' ' && ch <= '?' && ch != ':') {
// Modifier character.
if (!args.length) {
this.leadingModifier_ += ch;
} else {
this.trailingModifier_ += ch;
}
} else if (this.cc1Pattern_.test(ch)) {
// Control character.
this.dispatch('CC1', ch, parseState);
} else {
// Unexpected character in sequence, bail out.
parseState.resetParseFunction();
}
parseState.advance(1);
};
/**
* Skip over the string until the next String Terminator (ST, 'ESC \') or
* Bell (BEL, '\x07').
*
* The string is accumulated in parseState.args[0]. Make sure to reset the
* arguments (with parseState.resetArguments) before starting the parse.
*
* You can detect that parsing in complete by checking that the parse
* function has changed back to the default parse function.
*
* If we encounter more than maxStringSequence characters, we send back
* the unterminated sequence to be re-parsed with the default parser function.
*
* @return {boolean} If true, parsing is ongoing or complete. If false, we've
* exceeded the max string sequence.
*/
hterm.VT.prototype.parseUntilStringTerminator_ = function(parseState) {
var buf = parseState.peekRemainingBuf();
var nextTerminator = buf.search(/(\x1b\\|\x07)/);
var args = parseState.args;
if (!args.length) {
args[0] = '';
args[1] = new Date();
}
if (nextTerminator == -1) {
// No terminator here, have to wait for the next string.
args[0] += buf;
var abortReason;
if (args[0].length > this.maxStringSequence)
abortReason = 'too long: ' + args[0].length;
if (args[0].indexOf('\x1b') != -1)
abortReason = 'embedded escape: ' + args[0].indexOf('\x1b');
if (new Date() - args[1] > this.oscTimeLimit_)
abortReason = 'timeout expired: ' + new Date() - args[1];
if (abortReason) {
console.log('parseUntilStringTerminator_: aborting: ' + abortReason,
args[0]);
parseState.reset(args[0]);
return false;
}
parseState.advance(buf.length);
return true;
}
if (args[0].length + nextTerminator > this.maxStringSequence) {
// We found the end of the sequence, but we still think it's too long.
parseState.reset(args[0] + buf);
return false;
}
args[0] += buf.substr(0, nextTerminator);
parseState.resetParseFunction();
parseState.advance(nextTerminator +
(buf.substr(nextTerminator, 1) == '\x1b' ? 2 : 1));
return true;
};
/**
* Dispatch to the function that handles a given CC1, ESC, or CSI or VT52 code.
*/
hterm.VT.prototype.dispatch = function(type, code, parseState) {
var handler = hterm.VT[type][code];
if (!handler) {
if (this.warnUnimplemented)
console.warn('Unknown ' + type + ' code: ' + JSON.stringify(code));
return;
}
if (handler == hterm.VT.ignore) {
if (this.warnUnimplemented)
console.warn('Ignored ' + type + ' code: ' + JSON.stringify(code));
return;
}
if (type == 'CC1' && code > '\x7f' && !this.enable8BitControl) {
// It's kind of a hack to put this here, but...
//
// If we're dispatching a 'CC1' code, and it's got the eighth bit set,
// but we're not supposed to handle 8-bit codes? Just ignore it.
//
// This prevents an errant (DCS, '\x90'), (OSC, '\x9d'), (PM, '\x9e') or
// (APC, '\x9f') from locking up the terminal waiting for its expected
// (ST, '\x9c') or (BEL, '\x07').
console.warn('Ignoring 8-bit control code: 0x' +
code.charCodeAt(0).toString(16));
return;
}
handler.apply(this, [parseState, code]);
};
/**
* Set one of the ANSI defined terminal mode bits.
*
* Invoked in response to SM/RM.
*
* Unexpected and unimplemented values are silently ignored.
*/
hterm.VT.prototype.setANSIMode = function(code, state) {
if (code == 4) { // Insert Mode (IRM)
this.terminal.setInsertMode(state);
} else if (code == 20) { // Automatic Newline (LNM)
this.terminal.setAutoCarriageReturn(state);
} else if (this.warnUnimplemented) {
console.warn('Unimplemented ANSI Mode: ' + code);
}
};
/**
* Set or reset one of the DEC Private modes.
*
* Invoked in response to DECSET/DECRST.
*/
hterm.VT.prototype.setDECMode = function(code, state) {
switch (parseInt(code, 10)) {
case 1: // DECCKM
this.terminal.keyboard.applicationCursor = state;
break;
case 3: // DECCOLM
if (this.allowColumnWidthChanges_) {
this.terminal.setWidth(state ? 132 : 80);
this.terminal.clearHome();
this.terminal.setVTScrollRegion(null, null);
}
break;
case 5: // DECSCNM
this.terminal.setReverseVideo(state);
break;
case 6: // DECOM
this.terminal.setOriginMode(state);
break;
case 7: // DECAWM
this.terminal.setWraparound(state);
break;
case 12: // Start blinking cursor
if (this.enableDec12)
this.terminal.setCursorBlink(state);
break;
case 25: // DECTCEM
this.terminal.setCursorVisible(state);
break;
case 30: // Show scrollbar
this.terminal.setScrollbarVisible(state);
break;
case 40: // Allow 80 - 132 (DECCOLM) Mode
this.terminal.allowColumnWidthChanges_ = state;
break;
case 45: // Reverse-wraparound Mode
this.terminal.setReverseWraparound(state);
break;
case 67: // Backarrow key sends backspace (DECBKM)
this.terminal.keyboard.backspaceSendsBackspace = state;
break;
case 1000: // Report on mouse clicks only.
this.mouseReport = (
state ? this.MOUSE_REPORT_CLICK : this.MOUSE_REPORT_DISABLED);
this.terminal.syncMouseStyle();
break;
case 1002: // Report on mouse clicks and drags
this.mouseReport = (
state ? this.MOUSE_REPORT_DRAG : this.MOUSE_REPORT_DISABLED);
this.terminal.syncMouseStyle();
break;
case 1010: // Scroll to bottom on tty output
this.terminal.scrollOnOutput = state;
break;
case 1011: // Scroll to bottom on key press
this.terminal.scrollOnKeystroke = state;
break;
case 1036: // Send ESC when Meta modifies a key
this.terminal.keyboard.metaSendsEscape = state;
break;
case 1039: // Send ESC when Alt modifies a key
if (state) {
if (!this.terminal.keyboard.previousAltSendsWhat_) {
this.terminal.keyboard.previousAltSendsWhat_ =
this.terminal.keyboard.altSendsWhat;
this.terminal.keyboard.altSendsWhat = 'escape';
}
} else if (this.terminal.keyboard.previousAltSendsWhat_) {
this.terminal.keyboard.altSendsWhat =
this.terminal.keyboard.previousAltSendsWhat_;
this.terminal.keyboard.previousAltSendsWhat_ = null;
}
break;
case 47: // Use Alternate Screen Buffer
case 1047:
this.terminal.setAlternateMode(state);
break;
case 1048: // Save cursor as in DECSC.
this.savedState_.save();
case 1049: // 1047 + 1048 + clear.
if (state) {
this.savedState_.save();
this.terminal.setAlternateMode(state);
this.terminal.clear();
} else {
this.terminal.setAlternateMode(state);
this.savedState_.restore();
}
break;
case 2004: // Bracketed paste mode.
this.terminal.setBracketedPaste(state);
break;
default:
if (this.warnUnimplemented)
console.warn('Unimplemented DEC Private Mode: ' + code);
break;
}
};
/**
* Function shared by control characters and escape sequences that are
* ignored.
*/
hterm.VT.ignore = function() {};
/**
* Collection of control characters expressed in a single byte.
*
* This includes the characters from the C0 and C1 sets (see [CTRL]) that we
* care about. Two byte versions of the C1 codes are defined in the
* hterm.VT.ESC collection.
*
* The 'CC1' mnemonic here refers to the fact that these are one-byte Control
* Codes. It's only used in this source file and not defined in any of the
* referenced documents.
*/
hterm.VT.CC1 = {};
/**
* Collection of two-byte and three-byte sequences starting with ESC.
*/
hterm.VT.ESC = {};
/**
* Collection of CSI (Control Sequence Introducer) sequences.
*
* These sequences begin with 'ESC [', and may take zero or more arguments.
*/
hterm.VT.CSI = {};
/**
* Collection of OSC (Operating System Control) sequences.
*
* These sequences begin with 'ESC ]', followed by a function number and a
* string terminated by either ST or BEL.
*/
hterm.VT.OSC = {};
/**
* Collection of VT52 sequences.
*
* When in VT52 mode, other sequences are disabled.
*/
hterm.VT.VT52 = {};
/**
* Null (NUL).
*
* Silently ignored.
*/
hterm.VT.CC1['\x00'] = hterm.VT.ignore;
/**
* Enquiry (ENQ).
*
* Transmit answerback message.
*
* The default answerback message in xterm is an empty string, so we just
* ignore this.
*/
hterm.VT.CC1['\x05'] = hterm.VT.ignore;
/**
* Ring Bell (BEL).
*/
hterm.VT.CC1['\x07'] = function() {
this.terminal.ringBell();
};
/**
* Backspace (BS).
*
* Move the cursor to the left one character position, unless it is at the
* left margin, in which case no action occurs.
*/
hterm.VT.CC1['\x08'] = function() {
this.terminal.cursorLeft(1);
};
/**
* Horizontal Tab (HT).
*
* Move the cursor to the next tab stop, or to the right margin if no further
* tab stops are present on the line.
*/
hterm.VT.CC1['\x09'] = function() {
this.terminal.forwardTabStop();
};
/**
* Line Feed (LF).
*
* This code causes a line feed or a new line operation. See Automatic
* Newline (LNM).
*/
hterm.VT.CC1['\x0a'] = function() {
this.terminal.formFeed();
};
/**
* Vertical Tab (VT).
*
* Interpreted as LF.
*/
hterm.VT.CC1['\x0b'] = hterm.VT.CC1['\x0a'];
/**
* Form Feed (FF).
*
* Interpreted as LF.
*/
hterm.VT.CC1['\x0c'] = hterm.VT.CC1['\x0a'];
/**
* Carriage Return (CR).
*
* Move cursor to the left margin on the current line.
*/
hterm.VT.CC1['\x0d'] = function() {
this.terminal.setCursorColumn(0);
};
/**
* Shift Out (SO), aka Lock Shift 0 (LS1).
*
* Invoke G1 character set in GL.
*/
hterm.VT.CC1['\x0e'] = function() {
this.GL = 'G1';
};
/**
* Shift In (SI), aka Lock Shift 0 (LS0).
*
* Invoke G0 character set in GL.
*/
hterm.VT.CC1['\x0f'] = function() {
this.GL = 'G0';
};
/**
* Transmit On (XON).
*
* Not currently implemented.
*
* TODO(rginda): Implement?
*/
hterm.VT.CC1['\x11'] = hterm.VT.ignore;
/**
* Transmit Off (XOFF).
*
* Not currently implemented.
*
* TODO(rginda): Implement?
*/
hterm.VT.CC1['\x13'] = hterm.VT.ignore;
/**
* Cancel (CAN).
*
* If sent during a control sequence, the sequence is immediately terminated
* and not executed.
*
* It also causes the error character to be displayed.
*/
hterm.VT.CC1['\x18'] = function(parseState) {
// If we've shifted in the G1 character set, shift it back out to
// the default character set.
if (this.GL == 'G1') {
this.GL = 'G0';
}
parseState.resetParseFunction();
this.terminal.print('?');
};
/**
* Substitute (SUB).
*
* Interpreted as CAN.
*/
hterm.VT.CC1['\x1a'] = hterm.VT.CC1['\x18'];
/**
* Escape (ESC).
*/
hterm.VT.CC1['\x1b'] = function(parseState) {
function parseESC(parseState) {
var ch = parseState.consumeChar();
if (ch == '\x1b')
return;
this.dispatch('ESC', ch, parseState);
if (parseState.func == parseESC)
parseState.resetParseFunction();
};
parseState.func = parseESC;
};
/**
* Delete (DEL).
*/
hterm.VT.CC1['\x7f'] = hterm.VT.ignore;
// 8 bit control characters and their two byte equivalents, below...
/**
* Index (IND).
*
* Like newline, only keep the X position
*/
hterm.VT.CC1['\x84'] =
hterm.VT.ESC['D'] = function() {
this.terminal.lineFeed();
};
/**
* Next Line (NEL).
*
* Like newline, but doesn't add lines.
*/
hterm.VT.CC1['\x85'] =
hterm.VT.ESC['E'] = function() {
this.terminal.setCursorColumn(0);
this.terminal.cursorDown(1);
};
/**
* Horizontal Tabulation Set (HTS).
*/
hterm.VT.CC1['\x88'] =
hterm.VT.ESC['H'] = function() {
this.terminal.setTabStop(this.terminal.getCursorColumn());
};
/**
* Reverse Index (RI).
*
* Move up one line.
*/
hterm.VT.CC1['\x8d'] =
hterm.VT.ESC['M'] = function() {
this.terminal.reverseLineFeed();
};
/**
* Single Shift 2 (SS2).
*
* Select of G2 Character Set for the next character only.
*
* Not currently implemented.
*/
hterm.VT.CC1['\x8e'] =
hterm.VT.ESC['N'] = hterm.VT.ignore;
/**
* Single Shift 3 (SS3).
*
* Select of G3 Character Set for the next character only.
*
* Not currently implemented.
*/
hterm.VT.CC1['\x8f'] =
hterm.VT.ESC['O'] = hterm.VT.ignore;
/**
* Device Control String (DCS).
*
* Indicate a DCS sequence. See Device-Control functions in [XTERM].
* Not currently implemented.
*
* TODO(rginda): Consider implementing DECRQSS, the rest don't seem applicable.
*/
hterm.VT.CC1['\x90'] =
hterm.VT.ESC['P'] = function(parseState) {
parseState.resetArguments();
parseState.func = this.parseUntilStringTerminator_;
};
/**
* Start of Guarded Area (SPA).
*
* Will not implement.
*/
hterm.VT.CC1['\x96'] =
hterm.VT.ESC['V'] = hterm.VT.ignore;
/**
* End of Guarded Area (EPA).
*
* Will not implement.
*/
hterm.VT.CC1['\x97'] =
hterm.VT.ESC['W'] = hterm.VT.ignore;
/**
* Start of String (SOS).
*
* Will not implement.
*/
hterm.VT.CC1['\x98'] =
hterm.VT.ESC['X'] = hterm.VT.ignore;
/**
* Single Character Introducer (SCI, also DECID).
*
* Return Terminal ID. Obsolete form of 'ESC [ c' (DA).
*/
hterm.VT.CC1['\x9a'] =
hterm.VT.ESC['Z'] = function() {
this.terminal.io.sendString('\x1b[?1;2c');
};
/**
* Control Sequence Introducer (CSI).
*
* The lead into most escape sequences. See [CSI].
*/
hterm.VT.CC1['\x9b'] =
hterm.VT.ESC['['] = function(parseState) {
parseState.resetArguments();
this.leadingModifier_ = '';
this.trailingModifier_ = '';
parseState.func = this.parseCSI_;
};
/**
* String Terminator (ST).
*
* Used to terminate DCS/OSC/PM/APC commands which may take string arguments.
*
* We don't directly handle it here, as it's only used to terminate other
* sequences. See the 'parseUntilStringTerminator_' method.
*/
hterm.VT.CC1['\x9c'] =
hterm.VT.ESC['\\'] = hterm.VT.ignore;
/**
* Operating System Command (OSC).
*
* Commands relating to the operating system.
*/
hterm.VT.CC1['\x9d'] =
hterm.VT.ESC[']'] = function(parseState) {
parseState.resetArguments();
function parseOSC(parseState) {
if (!this.parseUntilStringTerminator_(parseState)) {
// The string sequence was too long.
return;
}
if (parseState.func == parseOSC) {
// We're not done parsing the string yet.
return;
}
// We're done.
var ary = parseState.args[0].match(/^(\d+);(.*)$/);
if (ary) {
parseState.args[0] = ary[2];
this.dispatch('OSC', ary[1], parseState);
} else {
console.warn('Invalid OSC: ' + JSON.stringify(parseState.args[0]));
}
};
parseState.func = parseOSC;
};
/**
* Privacy Message (PM).
*
* Will not implement.
*/
hterm.VT.CC1['\x9e'] =
hterm.VT.ESC['^'] = function(parseState) {
parseState.resetArguments();
parseState.func = this.parseUntilStringTerminator_;
};
/**
* Application Program Control (APC).
*
* Will not implement.
*/
hterm.VT.CC1['\x9f'] =
hterm.VT.ESC['_'] = function(parseState) {
parseState.resetArguments();
parseState.func = this.parseUntilStringTerminator_;
};
/**
* ESC \x20 - Unclear to me where these originated, possibly in xterm.
*
* Not currently implemented:
* ESC \x20 F - Select 7 bit escape codes in responses (S7C1T).
* ESC \x20 G - Select 8 bit escape codes in responses (S8C1T).
* NB: We currently assume S7C1T always.
*
* Will not implement:
* ESC \x20 L - Set ANSI conformance level 1.
* ESC \x20 M - Set ANSI conformance level 2.
* ESC \x20 N - Set ANSI conformance level 3.
*/
hterm.VT.ESC['\x20'] = function(parseState) {
parseState.func = function(parseState) {
var ch = parseState.consumeChar();
if (this.warnUnimplemented)
console.warn('Unimplemented sequence: ESC 0x20 ' + ch);
parseState.resetParseFunction();
};
};
/**
* DEC 'ESC #' sequences.
*/
hterm.VT.ESC['#'] = function(parseState) {
parseState.func = function(parseState) {
var ch = parseState.consumeChar();
if (ch == '8') // DEC Screen Alignment Test (DECALN)
this.terminal.fill('E');
parseState.resetParseFunction();
};
};
/**
* 'ESC %' sequences, character set control. Not currently implemented.
*
* To be implemented (currently ignored):
* ESC % @ - Set ISO 8859-1 character set.
* ESC % G - Set UTF-8 character set.
*
* All other ESC % sequences are echoed to the terminal.
*
* TODO(rginda): Implement.
*/
hterm.VT.ESC['%'] = function(parseState) {
parseState.func = function(parseState) {
var ch = parseState.consumeChar();
if (ch != '@' && ch != 'G' && this.warnUnimplemented)
console.warn('Unknown ESC % argument: ' + JSON.stringify(ch));
parseState.resetParseFunction();
};
};
/**
* Character Set Selection (SCS).
*
* ESC ( Ps - Set G0 character set (VT100).
* ESC ) Ps - Set G1 character set (VT220).
* ESC * Ps - Set G2 character set (VT220).
* ESC + Ps - Set G3 character set (VT220).
* ESC - Ps - Set G1 character set (VT300).
* ESC . Ps - Set G2 character set (VT300).
* ESC / Ps - Set G3 character set (VT300).
*
* All other sequences are echoed to the terminal.
*/
hterm.VT.ESC['('] =
hterm.VT.ESC[')'] =
hterm.VT.ESC['*'] =
hterm.VT.ESC['+'] =
hterm.VT.ESC['-'] =
hterm.VT.ESC['.'] =
hterm.VT.ESC['/'] = function(parseState, code) {
parseState.func = function(parseState) {
var ch = parseState.consumeChar();
if (ch == '\x1b') {
parseState.resetParseFunction();
parseState.func();
return;
}
if (ch in hterm.VT.CharacterMap.maps) {
if (code == '(') {
this.G0 = hterm.VT.CharacterMap.maps[ch];
} else if (code == ')' || code == '-') {
this.G1 = hterm.VT.CharacterMap.maps[ch];
} else if (code == '*' || code == '.') {
this.G2 = hterm.VT.CharacterMap.maps[ch];
} else if (code == '+' || code == '/') {
this.G3 = hterm.VT.CharacterMap.maps[ch];
}
} else if (this.warnUnimplemented) {
console.log('Invalid character set for "' + code + '": ' + ch);
}
parseState.resetParseFunction();
};
};
/**
* Back Index (DECBI).
*
* VT420 and up. Not currently implemented.
*/
hterm.VT.ESC['6'] = hterm.VT.ignore;
/**
* Save Cursor (DECSC).
*/
hterm.VT.ESC['7'] = function() {
this.savedState_.save();
};
/**
* Restore Cursor (DECRC).
*/
hterm.VT.ESC['8'] = function() {
this.savedState_.restore();
};
/**
* Forward Index (DECFI).
*
* VT210 and up. Not currently implemented.
*/
hterm.VT.ESC['9'] = hterm.VT.ignore;
/**
* Application keypad (DECKPAM).
*/
hterm.VT.ESC['='] = function() {
this.terminal.keyboard.applicationKeypad = true;
};
/**
* Normal keypad (DECKPNM).
*/
hterm.VT.ESC['>'] = function() {
this.terminal.keyboard.applicationKeypad = false;
};
/**
* Cursor to lower left corner of screen.
*
* Will not implement.
*
* This is only recognized by xterm when the hpLowerleftBugCompat resource is
* set.
*/
hterm.VT.ESC['F'] = hterm.VT.ignore;
/**
* Full Reset (RIS).
*/
hterm.VT.ESC['c'] = function() {
this.reset();
this.terminal.reset();
};
/**
* Memory lock/unlock.
*
* Will not implement.
*/
hterm.VT.ESC['l'] =
hterm.VT.ESC['m'] = hterm.VT.ignore;
/**
* Lock Shift 2 (LS2)
*
* Invoke the G2 Character Set as GL.
*/
hterm.VT.ESC['n'] = function() {
this.GL = 'G2';
};
/**
* Lock Shift 3 (LS3)
*
* Invoke the G3 Character Set as GL.
*/
hterm.VT.ESC['o'] = function() {
this.GL = 'G3';
};
/**
* Lock Shift 2, Right (LS3R)
*
* Invoke the G3 Character Set as GR.
*/
hterm.VT.ESC['|'] = function() {
this.GR = 'G3';
};
/**
* Lock Shift 2, Right (LS2R)
*
* Invoke the G2 Character Set as GR.
*/
hterm.VT.ESC['}'] = function() {
this.GR = 'G2';
};
/**
* Lock Shift 1, Right (LS1R)
*
* Invoke the G1 Character Set as GR.
*/
hterm.VT.ESC['~'] = function() {
this.GR = 'G1';
};
/**
* Change icon name and window title.
*
* We only change the window title.
*/
hterm.VT.OSC['0'] = function(parseState) {
this.terminal.setWindowTitle(parseState.args[0]);
};
/**
* Change window title.
*/
hterm.VT.OSC['2'] = hterm.VT.OSC['0'];
/**
* Set/read color palette.
*/
hterm.VT.OSC['4'] = function(parseState) {
// Args come in as a single 'index1;rgb1 ... ;indexN;rgbN' string.
// We split on the semicolon and iterate through the pairs.
var args = parseState.args[0].split(';');
var pairCount = parseInt(args.length / 2);
var colorPalette = this.terminal.getTextAttributes().colorPalette;
var responseArray = [];
for (var pairNumber = 0; pairNumber < pairCount; ++pairNumber) {
var colorIndex = parseInt(args[pairNumber * 2]);
var colorValue = args[pairNumber * 2 + 1];
if (colorIndex >= colorPalette.length)
continue;
if (colorValue == '?') {
// '?' means we should report back the current color value.
colorValue = lib.colors.rgbToX11(colorPalette[colorIndex]);
if (colorValue)
responseArray.push(colorIndex + ';' + colorValue);
continue;
}
colorValue = lib.colors.x11ToCSS(colorValue);
if (colorValue)
colorPalette[colorIndex] = colorValue;
}
if (responseArray.length)
this.terminal.io.sendString('\x1b]4;' + responseArray.join(';') + '\x07');
};
/**
* Change VT100 text foreground color.
*/
hterm.VT.OSC['10'] = function(parseState) {
// Args come in as a single string, but extra args will chain to the following
// OSC sequences.
var args = parseState.args[0].split(';');
if (!args)
return;
var colorArg;
var colorX11 = lib.colors.x11ToCSS(args.shift());
if (colorX11)
this.terminal.setForegroundColor(colorX11);
if (args.length > 0) {
parseState.args[0] = args.join(';');
hterm.VT.OSC['11'].apply(this, [parseState]);
}
};
/**
* Change VT100 text background color.
*/
hterm.VT.OSC['11'] = function(parseState) {
// Args come in as a single string, but extra args will chain to the following
// OSC sequences.
var args = parseState.args[0].split(';');
if (!args)
return;
var colorArg;
var colorX11 = lib.colors.x11ToCSS(args.shift());
if (colorX11)
this.terminal.setBackgroundColor(colorX11);
/* Note: If we support OSC 12+, we'd chain it here.
if (args.length > 0) {
parseState.args[0] = args.join(';');
hterm.VT.OSC['12'].apply(this, [parseState]);
}
*/
};
/**
* Set the cursor shape.
*
* Parameter is expected to be in the form "CursorShape=number", where number is
* one of:
*
* 0 - Block
* 1 - I-Beam
* 2 - Underline
*
* This is a bit of a de-facto standard supported by iTerm 2 and Konsole. See
* also: DECSCUSR.
*
* Invalid numbers will restore the cursor to the block shape.
*/
hterm.VT.OSC['50'] = function(parseState) {
var args = parseState.args[0].match(/CursorShape=(.)/i);
if (!args) {
console.warn('Could not parse OSC 50 args: ' + parseState.args[0]);
return;
}
switch (args[1]) {
case '1': // CursorShape=1: I-Beam.
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
break;
case '2': // CursorShape=2: Underline.
this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
break;
default: // CursorShape=0: Block.
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
}
};
/**
* Set/read system clipboard.
*
* Read is not implemented due to security considerations. A remote app
* that is able to both write and read to the clipboard could essentially
* take over your session.
*
* The clipboard data will be decoded according to the 'receive-encoding'
* preference.
*/
hterm.VT.OSC['52'] = function(parseState) {
// Args come in as a single 'clipboard;b64-data' string. The clipboard
// parameter is used to select which of the X clipboards to address. Since
// we're not integrating with X, we treat them all the same.
var args = parseState.args[0].match(/^[cps01234567]*;(.*)/);
if (!args)
return;
var data = window.atob(args[1]);
if (data)
this.terminal.copyStringToClipboard(this.decode(data));
};
/**
* Insert (blank) characters (ICH).
*/
hterm.VT.CSI['@'] = function(parseState) {
this.terminal.insertSpace(parseState.iarg(0, 1));
};
/**
* Cursor Up (CUU).
*/
hterm.VT.CSI['A'] = function(parseState) {
this.terminal.cursorUp(parseState.iarg(0, 1));
};
/**
* Cursor Down (CUD).
*/
hterm.VT.CSI['B'] = function(parseState) {
this.terminal.cursorDown(parseState.iarg(0, 1));
};
/**
* Cursor Forward (CUF).
*/
hterm.VT.CSI['C'] = function(parseState) {
this.terminal.cursorRight(parseState.iarg(0, 1));
};
/**
* Cursor Backward (CUB).
*/
hterm.VT.CSI['D'] = function(parseState) {
this.terminal.cursorLeft(parseState.iarg(0, 1));
};
/**
* Cursor Next Line (CNL).
*
* This is like Cursor Down, except the cursor moves to the beginning of the
* line as well.
*/
hterm.VT.CSI['E'] = function(parseState) {
this.terminal.cursorDown(parseState.iarg(0, 1));
this.terminal.setCursorColumn(0);
};
/**
* Cursor Preceding Line (CPL).
*
* This is like Cursor Up, except the cursor moves to the beginning of the
* line as well.
*/
hterm.VT.CSI['F'] = function(parseState) {
this.terminal.cursorUp(parseState.iarg(0, 1));
this.terminal.setCursorColumn(0);
};
/**
* Cursor Character Absolute (CHA).
*/
hterm.VT.CSI['G'] = function(parseState) {
this.terminal.setCursorColumn(parseState.iarg(0, 1) - 1);
};
/**
* Cursor Position (CUP).
*/
hterm.VT.CSI['H'] = function(parseState) {
this.terminal.setCursorPosition(parseState.iarg(0, 1) - 1,
parseState.iarg(1, 1) - 1);
};
/**
* Cursor Forward Tabulation (CHT).
*/
hterm.VT.CSI['I'] = function(parseState) {
var count = parseState.iarg(0, 1);
count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
for (var i = 0; i < count; i++) {
this.terminal.forwardTabStop();
}
};
/**
* Erase in Display (ED, DECSED).
*/
hterm.VT.CSI['J'] =
hterm.VT.CSI['?J'] = function(parseState, code) {
var arg = parseState.args[0];
if (!arg || arg == 0) {
this.terminal.eraseBelow();
} else if (arg == 1) {
this.terminal.eraseAbove();
} else if (arg == 2) {
this.terminal.clear();
} else if (arg == 3) {
// The xterm docs say this means "Erase saved lines", but we'll just clear
// the display since killing the scrollback seems rude.
this.terminal.clear();
}
};
/**
* Erase in line (EL, DECSEL).
*/
hterm.VT.CSI['K'] =
hterm.VT.CSI['?K'] = function(parseState, code) {
var arg = parseState.args[0];
if (!arg || arg == 0) {
this.terminal.eraseToRight();
} else if (arg == 1) {
this.terminal.eraseToLeft();
} else if (arg == 2) {
this.terminal.eraseLine();
}
};
/**
* Insert Lines (IL).
*/
hterm.VT.CSI['L'] = function(parseState) {
this.terminal.insertLines(parseState.iarg(0, 1));
};
/**
* Delete Lines (DL).
*/
hterm.VT.CSI['M'] = function(parseState) {
this.terminal.deleteLines(parseState.iarg(0, 1));
};
/**
* Delete Characters (DCH).
*
* This command shifts the line contents left, starting at the cursor position.
*/
hterm.VT.CSI['P'] = function(parseState) {
this.terminal.deleteChars(parseState.iarg(0, 1));
};
/**
* Scroll Up (SU).
*/
hterm.VT.CSI['S'] = function(parseState) {
this.terminal.vtScrollUp(parseState.iarg(0, 1));
};
/**
* Scroll Down (SD).
* Also 'Initiate highlight mouse tracking'. Will not implement this part.
*/
hterm.VT.CSI['T'] = function(parseState) {
if (parseState.args.length <= 1)
this.terminal.vtScrollDown(parseState.iarg(0, 1));
};
/**
* Reset one or more features of the title modes to the default value.
*
* ESC [ > Ps T
*
* Normally, "reset" disables the feature. It is possible to disable the
* ability to reset features by compiling a different default for the title
* modes into xterm.
*
* Ps values:
* 0 - Do not set window/icon labels using hexadecimal.
* 1 - Do not query window/icon labels using hexadecimal.
* 2 - Do not set window/icon labels using UTF-8.
* 3 - Do not query window/icon labels using UTF-8.
*
* Will not implement.
*/
hterm.VT.CSI['>T'] = hterm.VT.ignore;
/**
* Erase Characters (ECH).
*/
hterm.VT.CSI['X'] = function(parseState) {
this.terminal.eraseToRight(parseState.iarg(0, 1));
};
/**
* Cursor Backward Tabulation (CBT).
*/
hterm.VT.CSI['Z'] = function(parseState) {
var count = parseState.iarg(0, 1);
count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
for (var i = 0; i < count; i++) {
this.terminal.backwardTabStop();
}
};
/**
* Character Position Absolute (HPA).
*
* Same as Cursor Character Absolute (CHA).
*/
hterm.VT.CSI['`'] = hterm.VT.CSI['G'];
/**
* Character Position Relative (HPR).
*/
hterm.VT.CSI['a'] = function(parseState) {
this.terminal.setCursorColumn(this.terminal.getCursorColumn() +
parseState.iarg(0, 1));
};
/**
* Repeat the preceding graphic character.
*
* Not currently implemented.
*/
hterm.VT.CSI['b'] = hterm.VT.ignore;
/**
* Send Device Attributes (Primary DA).
*
* TODO(rginda): This is hardcoded to send back 'VT100 with Advanced Video
* Option', but it may be more correct to send a VT220 response once
* we fill out the 'Not currently implemented' parts.
*/
hterm.VT.CSI['c'] = function(parseState) {
if (!parseState.args[0] || parseState.args[0] == 0) {
this.terminal.io.sendString('\x1b[?1;2c');
}
};
/**
* Send Device Attributes (Secondary DA).
*
* TODO(rginda): This is hardcoded to send back 'VT100' but it may be more
* correct to send a VT220 response once we fill out more 'Not currently
* implemented' parts.
*/
hterm.VT.CSI['>c'] = function(parseState) {
this.terminal.io.sendString('\x1b[>0;256;0c');
};
/**
* Line Position Absolute (VPA).
*/
hterm.VT.CSI['d'] = function(parseState) {
this.terminal.setAbsoluteCursorRow(parseState.iarg(0, 1) - 1);
};
/**
* Horizontal and Vertical Position (HVP).
*
* Same as Cursor Position (CUP).
*/
hterm.VT.CSI['f'] = hterm.VT.CSI['H'];
/**
* Tab Clear (TBC).
*/
hterm.VT.CSI['g'] = function(parseState) {
if (!parseState.args[0] || parseState.args[0] == 0) {
// Clear tab stop at cursor.
this.terminal.clearTabStopAtCursor(false);
} else if (parseState.args[0] == 3) {
// Clear all tab stops.
this.terminal.clearAllTabStops();
}
};
/**
* Set Mode (SM).
*/
hterm.VT.CSI['h'] = function(parseState) {
for (var i = 0; i < parseState.args.length; i++) {
this.setANSIMode(parseState.args[i], true);
}
};
/**
* DEC Private Mode Set (DECSET).
*/
hterm.VT.CSI['?h'] = function(parseState) {
for (var i = 0; i < parseState.args.length; i++) {
this.setDECMode(parseState.args[i], true);
}
};
/**
* Media Copy (MC).
* Media Copy (MC, DEC Specific).
*
* These commands control the printer. Will not implement.
*/
hterm.VT.CSI['i'] =
hterm.VT.CSI['?i'] = hterm.VT.ignore;
/**
* Reset Mode (RM).
*/
hterm.VT.CSI['l'] = function(parseState) {
for (var i = 0; i < parseState.args.length; i++) {
this.setANSIMode(parseState.args[i], false);
}
};
/**
* DEC Private Mode Reset (DECRST).
*/
hterm.VT.CSI['?l'] = function(parseState) {
for (var i = 0; i < parseState.args.length; i++) {
this.setDECMode(parseState.args[i], false);
}
};
/**
* Character Attributes (SGR).
*
* Iterate through the list of arguments, applying the attribute changes based
* on the argument value...
*/
hterm.VT.CSI['m'] = function(parseState) {
function get256(i) {
if (parseState.args.length < i + 2 || parseState.args[i + 1] != 5)
return null;
return parseState.iarg(i + 2, 0);
}
function getTrueColor(i) {
if (parseState.args.length < i + 5 || parseState.args[i + 1] != 2)
return null;
var r = parseState.iarg(i + 2, 0);
var g = parseState.iarg(i + 3, 0);
var b = parseState.iarg(i + 4, 0);
return 'rgb(' + r + ' ,' + g + ' ,' + b + ')';
}
var attrs = this.terminal.getTextAttributes();
if (!parseState.args.length) {
attrs.reset();
return;
}
for (var i = 0; i < parseState.args.length; i++) {
var arg = parseState.iarg(i, 0);
if (arg < 30) {
if (arg == 0) { // Normal (default).
attrs.reset();
} else if (arg == 1) { // Bold.
attrs.bold = true;
} else if (arg == 2) { // Faint.
attrs.faint = true;
} else if (arg == 3) { // Italic.
attrs.italic = true;
} else if (arg == 4) { // Underline.
attrs.underline = true;
} else if (arg == 5) { // Blink.
attrs.blink = true;
} else if (arg == 7) { // Inverse.
attrs.inverse = true;
} else if (arg == 8) { // Invisible.
attrs.invisible = true;
} else if (arg == 9) { // Crossed out.
attrs.strikethrough = true;
} else if (arg == 22) { // Not bold & not faint.
attrs.bold = false;
attrs.faint = false;
} else if (arg == 23) { // Not italic.
attrs.italic = false;
} else if (arg == 24) { // Not underlined.
attrs.underline = false;
} else if (arg == 25) { // Not blink.
attrs.blink = false;
} else if (arg == 27) { // Steady.
attrs.inverse = false;
} else if (arg == 28) { // Visible.
attrs.invisible = false;
} else if (arg == 29) { // Not crossed out.
attrs.strikethrough = false;
}
} else if (arg < 50) {
// Select fore/background color from bottom half of 16 color palette
// or from the 256 color palette or alternative specify color in fully
// qualified rgb(r, g, b) form.
if (arg < 38) {
attrs.foregroundSource = arg - 30;
} else if (arg == 38) {
// First check for true color definition
var trueColor = getTrueColor(i);
if (trueColor != null) {
attrs.foregroundSource = attrs.SRC_RGB;
attrs.foreground = trueColor;
i += 5;
} else {
// Check for 256 color
var c = get256(i);
if (c == null)
break;
i += 2;
if (c >= attrs.colorPalette.length)
continue;
attrs.foregroundSource = c;
}
} else if (arg == 39) {
attrs.foregroundSource = attrs.SRC_DEFAULT;
} else if (arg < 48) {
attrs.backgroundSource = arg - 40;
} else if (arg == 48) {
// First check for true color definition
var trueColor = getTrueColor(i);
if (trueColor != null) {
attrs.backgroundSource = attrs.SRC_RGB;
attrs.background = trueColor;
i += 5;
} else {
// Check for 256 color
var c = get256(i);
if (c == null)
break;
i += 2;
if (c >= attrs.colorPalette.length)
continue;
attrs.backgroundSource = c;
}
} else {
attrs.backgroundSource = attrs.SRC_DEFAULT;
}
} else if (arg >= 90 && arg <= 97) {
attrs.foregroundSource = arg - 90 + 8;
} else if (arg >= 100 && arg <= 107) {
attrs.backgroundSource = arg - 100 + 8;
}
}
attrs.setDefaults(this.terminal.getForegroundColor(),
this.terminal.getBackgroundColor());
};
/**
* Set xterm-specific keyboard modes.
*
* Will not implement.
*/
hterm.VT.CSI['>m'] = hterm.VT.ignore;
/**
* Device Status Report (DSR, DEC Specific).
*
* 5 - Status Report. Result (OK) is CSI 0 n
* 6 - Report Cursor Position (CPR) [row;column]. Result is CSI r ; c R
*/
hterm.VT.CSI['n'] = function(parseState) {
if (parseState.args[0] == 5) {
this.terminal.io.sendString('\x1b0n');
} else if (parseState.args[0] == 6) {
var row = this.terminal.getCursorRow() + 1;
var col = this.terminal.getCursorColumn() + 1;
this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
}
};
/**
* Disable modifiers which may be enabled via CSI['>m'].
*
* Will not implement.
*/
hterm.VT.CSI['>n'] = hterm.VT.ignore;
/**
* Device Status Report (DSR, DEC Specific).
*
* 6 - Report Cursor Position (CPR) [row;column] as CSI ? r ; c R
* 15 - Report Printer status as CSI ? 1 0 n (ready) or
* CSI ? 1 1 n (not ready).
* 25 - Report UDK status as CSI ? 2 0 n (unlocked) or CSI ? 2 1 n (locked).
* 26 - Report Keyboard status as CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
* The last two parameters apply to VT400 & up, and denote keyboard ready
* and LK01 respectively.
* 53 - Report Locator status as CSI ? 5 3 n Locator available, if compiled-in,
* or CSI ? 5 0 n No Locator, if not.
*/
hterm.VT.CSI['?n'] = function(parseState) {
if (parseState.args[0] == 6) {
var row = this.terminal.getCursorRow() + 1;
var col = this.terminal.getCursorColumn() + 1;
this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
} else if (parseState.args[0] == 15) {
this.terminal.io.sendString('\x1b[?11n');
} else if (parseState.args[0] == 25) {
this.terminal.io.sendString('\x1b[?21n');
} else if (parseState.args[0] == 26) {
this.terminal.io.sendString('\x1b[?12;1;0;0n');
} else if (parseState.args[0] == 53) {
this.terminal.io.sendString('\x1b[?50n');
}
};
/**
* This is used by xterm to decide whether to hide the pointer cursor as the
* user types.
*
* Valid values for the parameter:
* 0 - Never hide the pointer.
* 1 - Hide if the mouse tracking mode is not enabled.
* 2 - Always hide the pointer.
*
* If no parameter is given, xterm uses the default, which is 1.
*
* Not currently implemented.
*/
hterm.VT.CSI['>p'] = hterm.VT.ignore;
/**
* Soft terminal reset (DECSTR).
*/
hterm.VT.CSI['!p'] = function() {
this.reset();
this.terminal.softReset();
};
/**
* Request ANSI Mode (DECRQM).
*
* Not currently implemented.
*/
hterm.VT.CSI['$p'] = hterm.VT.ignore;
hterm.VT.CSI['?$p'] = hterm.VT.ignore;
/**
* Set conformance level (DECSCL).
*
* Not currently implemented.
*/
hterm.VT.CSI['"p'] = hterm.VT.ignore;
/**
* Load LEDs (DECLL).
*
* Not currently implemented. Could be implemented as virtual LEDs overlaying
* the terminal if anyone cares.
*/
hterm.VT.CSI['q'] = hterm.VT.ignore;
/**
* Set cursor style (DECSCUSR, VT520).
*/
hterm.VT.CSI[' q'] = function(parseState) {
var arg = parseState.args[0];
if (arg == 0 || arg == 1) {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
this.terminal.setCursorBlink(true);
} else if (arg == 2) {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
this.terminal.setCursorBlink(false);
} else if (arg == 3) {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
this.terminal.setCursorBlink(true);
} else if (arg == 4) {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
this.terminal.setCursorBlink(false);
} else if (arg == 5) {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
this.terminal.setCursorBlink(true);
} else if (arg == 6) {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
this.terminal.setCursorBlink(false);
} else {
console.warn('Unknown cursor style: ' + arg);
}
};
/**
* Select character protection attribute (DECSCA).
*
* Will not implement.
*/
hterm.VT.CSI['"q'] = hterm.VT.ignore;
/**
* Set Scrolling Region (DECSTBM).
*/
hterm.VT.CSI['r'] = function(parseState) {
var args = parseState.args;
var scrollTop = args[0] ? parseInt(args[0], 10) -1 : null;
var scrollBottom = args[1] ? parseInt(args[1], 10) - 1 : null;
this.terminal.setVTScrollRegion(scrollTop, scrollBottom);
this.terminal.setCursorPosition(0, 0);
};
/**
* Restore DEC Private Mode Values.
*
* Will not implement.
*/
hterm.VT.CSI['?r'] = hterm.VT.ignore;
/**
* Change Attributes in Rectangular Area (DECCARA)
*
* Will not implement.
*/
hterm.VT.CSI['$r'] = hterm.VT.ignore;
/**
* Save cursor (ANSI.SYS)
*/
hterm.VT.CSI['s'] = function() {
this.savedState_.save();
};
/**
* Save DEC Private Mode Values.
*
* Will not implement.
*/
hterm.VT.CSI['?s'] = hterm.VT.ignore;
/**
* Window manipulation (from dtterm, as well as extensions).
*
* Will not implement.
*/
hterm.VT.CSI['t'] = hterm.VT.ignore;
/**
* Reverse Attributes in Rectangular Area (DECRARA).
*
* Will not implement.
*/
hterm.VT.CSI['$t'] = hterm.VT.ignore;
/**
* Set one or more features of the title modes.
*
* Will not implement.
*/
hterm.VT.CSI['>t'] = hterm.VT.ignore;
/**
* Set warning-bell volume (DECSWBV, VT520).
*
* Will not implement.
*/
hterm.VT.CSI[' t'] = hterm.VT.ignore;
/**
* Restore cursor (ANSI.SYS).
*/
hterm.VT.CSI['u'] = function() {
this.savedState_.restore();
};
/**
* Set margin-bell volume (DECSMBV, VT520).
*
* Will not implement.
*/
hterm.VT.CSI[' u'] = hterm.VT.ignore;
/**
* Copy Rectangular Area (DECCRA, VT400 and up).
*
* Will not implement.
*/
hterm.VT.CSI['$v'] = hterm.VT.ignore;
/**
* Enable Filter Rectangle (DECEFR).
*
* Will not implement.
*/
hterm.VT.CSI['\'w'] = hterm.VT.ignore;
/**
* Request Terminal Parameters (DECREQTPARM).
*
* Not currently implemented.
*/
hterm.VT.CSI['x'] = hterm.VT.ignore;
/**
* Select Attribute Change Extent (DECSACE).
*
* Will not implement.
*/
hterm.VT.CSI['*x'] = hterm.VT.ignore;
/**
* Fill Rectangular Area (DECFRA), VT420 and up.
*
* Will not implement.
*/
hterm.VT.CSI['$x'] = hterm.VT.ignore;
/**
* vt_tiledata (as used by NAOhack and UnNetHack)
* (see https://nethackwiki.com/wiki/Vt_tiledata for more info)
*
* Implemented as far as we care (start a glyph and end a glyph).
*/
hterm.VT.CSI['z'] = function(parseState) {
if (parseState.args.length < 1)
return;
var arg = parseState.args[0];
if (arg == 0) {
// Start a glyph (one parameter, the glyph number).
if (parseState.args.length < 2)
return;
this.terminal.getTextAttributes().tileData = parseState.args[1];
} else if (arg == 1) {
// End a glyph.
this.terminal.getTextAttributes().tileData = null;
}
};
/**
* Enable Locator Reporting (DECELR).
*
* Not currently implemented.
*/
hterm.VT.CSI['\'z'] = hterm.VT.ignore;
/**
* Erase Rectangular Area (DECERA), VT400 and up.
*
* Will not implement.
*/
hterm.VT.CSI['$z'] = hterm.VT.ignore;
/**
* Select Locator Events (DECSLE).
*
* Not currently implemented.
*/
hterm.VT.CSI['\'{'] = hterm.VT.ignore;
/**
* Request Locator Position (DECRQLP).
*
* Not currently implemented.
*/
hterm.VT.CSI['\'|'] = hterm.VT.ignore;
/**
* Insert Columns (DECIC), VT420 and up.
*
* Will not implement.
*/
hterm.VT.CSI['\'}'] = hterm.VT.ignore;
/**
* Delete P s Columns (DECDC), VT420 and up.
*
* Will not implement.
*/
hterm.VT.CSI['\'~'] = hterm.VT.ignore;