blob: ff5fcd0e930de351d819ce2f09bfa13027c295ef [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.
*
* Some sequences are marked "Will not implement", meaning that they aren't
* considered relevant to hterm and will probably never be implemented.
*
* Others are marked "Not currently implemented", meaning that they are lower
* priority items that may be useful to implement at some point.
*
* See also:
* [VT100] VT100 User Guide
* http://vt100.net/docs/vt100-ug/chapter3.html
* [VT510] VT510 Video Terminal Programmer Information
* http://vt100.net/docs/vt510-rm/contents
* [XTERM] Xterm Control Sequences
* http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
* [CTRL] Wikipedia: C0 and C1 Control Codes
* http://en.wikipedia.org/wiki/C0_and_C1_control_codes
* [CSI] Wikipedia: ANSI Escape Code
* http://en.wikipedia.org/wiki/Control_Sequence_Introducer
* man 5 terminfo, man infocmp, infocmp -L xterm-new
*
* @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;
// True if we should fake-out mouse "cell motion" reporting (DECSET 1002)
this.mouseCellMotionTrick_ = 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;
/**
* Report only mouse down events.
*
* This is an hterm specific mode that tricks vi's ':set mouse=a' mode into
* working more like emacs xterm-mouse-mode. Clicks will reposition the
* cursor, and the scroll wheel will scroll the buffer. Selection, however,
* will be browser-native, rather than the custom vi selection you usually get
* with ':set mouse=a'.
*
* When the 'mouse-cell-motion-trick' pref is enabled, we'll use this mode
* in place of MOUSE_REPORT_DRAG.
*
* It is distinct from the normal MOUSE_REPORT_CLICK so that we can switch it
* off if the user changes their 'mouse-cell-motion-trick' pref while this
* is enabled. (If it weren't distinct, we wouldn't be sure how we got into
* MOUSE_REPORT_CLICK mode.)
*/
hterm.VT.prototype.MOUSE_REPORT_CLICK_1002 = 2;
/**
* 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 retreive.
*/
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 postion.
*
* @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;
this.terminal.setSelectionEnabled(true);
};
hterm.VT.prototype.setMouseCellMotionTrick = function(state) {
this.mouseCellMotionTrick_ = state;
if ((state && this.mouseReport_ == this.MOUSE_REPORT_DRAG) ||
(!state && this.mouseReport_ == this.MOUSE_REPORT_CLICK_1002)) {
this.setDECMode('1002', true);
}
};
/**
* 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 'click':
case 'dblclick':
if (this.mouseReport_ == this.MOUSE_REPORT_CLICK ||
this.mouseReport_ == this.MOUSE_REPORT_CLICK_1002) {
// Buttons are encoded as button number plus 32.
var b = Math.min(e.which - 1, 2) + 32;
// And mix in the modifier keys.
b |= mod;
response = '\x1b[M' + String.fromCharCode(b) + x + y;
response += '\x1b[M\x23' + x + y;
}
break;
case 'mousewheel':
// Mouse wheel is treated as button 1 or 2 plus an additional 64.
b = ((e.wheelDeltaY > 0) ? 0 : 1) + 96;
b |= mod;
response = '\x1b[M' + String.fromCharCode(b) + x + y;
// Keep the terminal from scrolling.
e.preventDefault();
break;
case 'mousedown':
if (this.mouseReport_ == this.MOUSE_REPORT_DRAG && e.which) {
// Buttons are encoded as button number plus 32.
var b = Math.min(e.which - 1, 2) + 32;
// And mix in the modifier keys.
b |= mod;
response = '\x1b[M' + String.fromCharCode(b) + x + y;
}
break;
case 'mouseup':
if (this.mouseReport_ == this.MOUSE_REPORT_DRAG && e.which) {
// 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 && e.which) {
// Standard button bits.
b = 32 + Math.min(e.which - 1, 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;
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: http://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 delimeter.
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.
*
* Expected values for code:
* 2 - Keyboard Action Mode (AM). Will not implement.
* 4 - Insert Mode (IRM).
* 12 - Send/receive (SRM). Will not implement.
* 20 - Automatic Newline (LNM).
*
* Unexpected and unimplemented values are silently ignored.
*/
hterm.VT.prototype.setANSIMode = function(code, state) {
if (code == '4') {
this.terminal.setInsertMode(state);
} else if (code == '20') {
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.
*
* Expected values for code:
* 1 - Application Cursor Keys (DECCKM).
* 2 - [!] Designate USASCII for character sets G0-G3 (DECANM), and set
* VT100 mode.
* 3 - 132 Column Mode (DECCOLM).
* 4 - [x] Smooth (Slow) Scroll (DECSCLM).
* 5 - Reverse Video (DECSCNM).
* 6 - Origin Mode (DECOM).
* 7 - Wraparound Mode (DECAWM).
* 8 - [x] Auto-repeat Keys (DECARM).
* 9 - [!] Send Mouse X & Y on button press.
* 10 - [x] Show toolbar (rxvt).
* 12 - Start Blinking Cursor (att610).
* 18 - [!] Print form feed (DECPFF).
* 19 - [x] Set print extent to full screen (DECPEX).
* 25 - Show Cursor (DECTCEM).
* 30 - [!] Show scrollbar (rxvt).
* 35 - [x] Enable font-shifting functions (rxvt).
* 38 - [x] Enter Tektronix Mode (DECTEK).
* 40 - Allow 80 - 132 Mode.
* 41 - [!] more(1) fix (see curses resource).
* 42 - [!] Enable Nation Replacement Character sets (DECNRCM).
* 44 - [!] Turn On Margin Bell.
* 45 - Reverse-wraparound Mode.
* 46 - [x] Start Logging.
* 47 - [!] Use Alternate Screen Buffer.
* 66 - [!] Application keypad (DECNKM).
* 67 - Backarrow key sends backspace (DECBKM).
* 1000 - Send Mouse X & Y on button press and release. (MOUSE_REPORT_CLICK)
* 1001 - [!] Use Hilite Mouse Tracking.
* 1002 - Use Cell Motion Mouse Tracking. (MOUSE_REPORT_DRAG)
* 1003 - [!] Use All Motion Mouse Tracking.
* 1004 - [!] Send FocusIn/FocusOut events.
* 1005 - [!] Enable Extended Mouse Mode.
* 1010 - Scroll to bottom on tty output (rxvt).
* 1011 - Scroll to bottom on key press (rxvt).
* 1034 - [x] Interpret "meta" key, sets eighth bit.
* 1035 - [x] Enable special modifiers for Alt and NumLock keys.
* 1036 - Send ESC when Meta modifies a key.
* 1037 - [!] Send DEL from the editing-keypad Delete key.
* 1039 - Send ESC when Alt modifies a key.
* 1040 - [x] Keep selection even if not highlighted.
* 1041 - [x] Use the CLIPBOARD selection.
* 1042 - [!] Enable Urgency window manager hint when Control-G is received.
* 1043 - [!] Enable raising of the window when Control-G is received.
* 1047 - [!] Use Alternate Screen Buffer.
* 1048 - Save cursor as in DECSC.
* 1049 - Save cursor as in DECSC and use Alternate Screen Buffer, clearing
* it first. (This may be disabled by the titeInhibit resource). This
* combines the effects of the 1047 and 1048 modes. Use this with
* terminfo-based applications rather than the 47 mode.
* 1050 - [!] Set terminfo/termcap function-key mode.
* 1051 - [x] Set Sun function-key mode.
* 1052 - [x] Set HP function-key mode.
* 1053 - [x] Set SCO function-key mode.
* 1060 - [x] Set legacy keyboard emulation (X11R6).
* 1061 - [!] Set VT220 keyboard emulation.
* 2004 - [!] Set bracketed paste mode.
*
* [!] - Not currently implemented, may be in the future.
* [x] - Will not implement.
*/
hterm.VT.prototype.setDECMode = function(code, state) {
switch (code) {
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': // att610
if (this.enableDec12)
this.terminal.setCursorBlink(state);
break;
case '25': // DECTCEM
this.terminal.setCursorVisible(state);
break;
case '40': // no-spec
this.terminal.allowColumnWidthChanges_ = state;
break;
case '45': // no-spec
this.terminal.setReverseWraparound(state);
break;
case '67': // 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.setSelectionEnabled(true);
break;
case '1002': // Report on mouse clicks and drags
if (!state) {
this.mouseReport_ = this.MOUSE_REPORT_DISABLED;
this.terminal.setSelectionEnabled(true);
} else if (this.mouseCellMotionTrick_) {
this.mouseReport_ = this.MOUSE_REPORT_CLICK_1002;
this.terminal.setSelectionEnabled(true);
} else {
this.mouseReport_ = this.MOUSE_REPORT_DRAG;
this.terminal.setSelectionEnabled(false);
}
break;
case '1010': // rxvt
this.terminal.scrollOnOutput = state;
break;
case '1011': // rxvt
this.terminal.scrollOnKeystroke = state;
break;
case '1036': // no-spec
this.terminal.keyboard.metaSendsEscape = state;
break;
case '1039': // no-spec
this.terminal.keyboard.altSendsEscape = state;
break;
case '47':
case '1047': // no-spec
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;
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'] = function () {};
/**
* 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'] = function() {
this.terminal.formFeed();
};
/**
* 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) {
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 Protected Area (SPA).
*
* Will not implement.
*/
hterm.VT.CC1['\x96'] =
hterm.VT.ESC['V'] = hterm.VT.ignore;
/**
* End of Protected 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.
*
* Handled:
* ESC # 8 - DEC Screen Alignment Test (DECALN).
* Fills the terminal with 'E's. Used liberally by vttest.
*
* Ignored:
* ESC # 3 - DEC double-height line, top half (DECDHL).
* ESC # 4 - DEC double-height line, bottom half (DECDHL).
* ESC # 5 - DEC single-width line (DECSWL).
* ESC # 6 - DEC double-width line (DECDWL).
*
* All other ESC # sequences are echoed to the terminal.
*/
hterm.VT.ESC['#'] = function(parseState) {
parseState.func = function(parseState) {
var ch = parseState.consumeChar();
if (ch == '8') {
this.terminal.fill('E');
} else if ("3456".indexOf(ch) == -1) {
this.terminal.print('\x1b#' + ch);
}
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).
*
* Values for Ps are:
* 0 - DEC Special Character and Line Drawing Set.
* A - United Kingdom (UK).
* B - United States (USASCII).
* 4 - Dutch.
* C or 5 - Finnish.
* R - French.
* Q - French Canadian.
* K - German.
* Y - Italian.
* E or 6 - Norwegian/Danish.
* Z - Spanish.
* H or 7 - Swedish.
* = - Swiss.
*
* All other sequences are echoed to the terminal.
*
* TODO(rginda): Implement.
*/
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 (DECSC).
*/
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 (DECPAM).
*/
hterm.VT.ESC['='] = function() {
this.terminal.keyboard.applicationKeypad = true;
};
/**
* Normal keypad (DECPNM).
*/
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');
};
/**
* 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 = 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();
} else {
this.terminal.print('\x1b[' + code + args[0])
}
};
/**
* 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();
} else {
this.terminal.print('\x1b[' + arg + code)
}
};
/**
* 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).
*/
hterm.VT.CSI['`'] = function(parseState) {
this.terminal.setCursorColumn(parseState.iarg(0, 1) - 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 following attribute
* changes based on the argument value...
*
* 0 Normal (default).
* 1 Bold.
* 4 Underlined.
* 5 Blink (appears as Bold).
* 7 Inverse.
* 8 Invisible, i.e., hidden (VT300).
* 22 Normal (neither bold nor faint).
* 24 Not underlined.
* 25 Steady (not blinking).
* 27 Positive (not inverse).
* 28 Visible, i.e., not hidden (VT300).
* 30 Set foreground color to Black.
* 31 Set foreground color to Red.
* 32 Set foreground color to Green.
* 33 Set foreground color to Yellow.
* 34 Set foreground color to Blue.
* 35 Set foreground color to Magenta.
* 36 Set foreground color to Cyan.
* 37 Set foreground color to White.
* 39 Set foreground color to default (original).
* 40 Set background color to Black.
* 41 Set background color to Red.
* 42 Set background color to Green.
* 43 Set background color to Yellow.
* 44 Set background color to Blue.
* 45 Set background color to Magenta.
* 46 Set background color to Cyan.
* 47 Set background color to White.
* 49 Set background color to default (original)
*
* For 16-color support, the following apply.
*
* 90 Set foreground color to Bright Black.
* 91 Set foreground color to Bright Red.
* 92 Set foreground color to Bright Green.
* 93 Set foreground color to Bright Yellow.
* 94 Set foreground color to Bright Blue.
* 95 Set foreground color to Bright Magenta.
* 96 Set foreground color to Bright Cyan.
* 97 Set foreground color to Bright White.
* 100 Set background color to Bright Black.
* 101 Set background color to Bright Red.
* 102 Set background color to Bright Green.
* 103 Set background color to Bright Yellow.
* 104 Set background color to Bright Blue.
* 105 Set background color to Bright Magenta.
* 106 Set background color to Bright Cyan.
* 107 Set background color to Bright White.
*
* For 88- or 256-color support, the following apply.
* 38 ; 5 ; P Set foreground color to P.
* 48 ; 5 ; P Set background color to P.
*
* Note that most terminals consider "bold" to be "bold and bright". In
* some documents the bold state is even referred to as bright. We interpret
* bold as bold-bright here too, but only when the "bold" setting comes before
* the color selection.
*/
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);
}
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) {
attrs.reset();
} else if (arg == 1) {
attrs.bold = true;
} else if (arg == 4) {
attrs.underline = true;
} else if (arg == 5) {
attrs.blink = true;
} else if (arg == 7) { // Inverse.
attrs.inverse = true;
} else if (arg == 8) { // Invisible.
attrs.invisible = true;
} else if (arg == 22) {
attrs.bold = false;
} else if (arg == 24) {
attrs.underline = false;
} else if (arg == 25) {
attrs.blink = false;
} else if (arg == 27) {
attrs.inverse = false;
} else if (arg == 28) {
attrs.invisible = false;
}
} else if (arg < 50) {
// Select fore/background color from bottom half of 16 color palette
// or from the 256 color palette.
if (arg < 38) {
attrs.foregroundIndex = arg - 30;
} else if (arg == 38) {
var c = get256(i);
if (c == null)
break;
i += 2;
if (c >= attrs.colorPalette.length)
continue;
attrs.foregroundIndex = c;
} else if (arg == 39) {
attrs.foregroundIndex = null;
} else if (arg < 48) {
attrs.backgroundIndex = arg - 40;
} else if (arg == 48) {
var c = get256(i);
if (c == null)
break;
i += 2;
if (c >= attrs.colorPalette.length)
continue;
attrs.backgroundIndex = c;
} else {
attrs.backgroundIndex = null;
}
} else if (arg >= 90 && arg <= 97) {
attrs.foregroundIndex = arg - 90 + 8;
} else if (arg >= 100 && arg <= 107) {
attrs.backgroundIndex = 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).
*
* 0 - Blinking block.
* 1 - Blinking block (default).
* 2 - Steady block.
* 3 - Blinking underline.
* 4 - Steady underline.
* Not currently implemented.
*/
hterm.VT.CSI[' q'] = hterm.VT.ignore;
/**
* 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.terminal.restoreOptions();
};
/**
* 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;
/**
* 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;