@pres/program
Version:
Curses like basic functionality for pres
1,604 lines (1,348 loc) • 80.7 kB
JavaScript
import { SIGCONT, SIGTSTP } from '@geia/enum-signals';
import { ESC, LF, RN, BEL, DCS, ST, CSI, OSC, VT, FF, BS, TAB, SO, SI, IND, RI, NEL, RIS, HTS, DECSC, DECRC, DECKPNM } from '@pres/enum-control-chars';
import { DECSCUSR, CUU, CUD, CUF, CUB, CUP, ED, EL, SGR, DSR, ICH, CNL, CPL, CHA, IL, DL, DCH, ECH, HPA, HPR, DA, VPA, VPR, HVP, SM, RM, DECSTBM, SCOSC, SCORC, CHT, SU, SD, XTHIMOUSE, XTRMTITLE, CBT, REP, TBC, MC, XTMODKEYS, XTUNMODKEYS, XTSMPOINTER, DECRQM, DECSCL, DECLL, DECSCA, XTRESTORE, DECCARA, XTSAVE, XTWINOPS, DECRARA, XTSMTITLE, DECSWBV, DECSMBV, DECCRA, DECEFR, DECREQTPARM, DECSACE, DECFRA, DECELR, DECERA, DECSLE, DECSERA, DECIC, DECDC } from '@pres/enum-csi-codes';
import { DATA, WARNING, NEW_LISTENER, KEYPRESS, MOUSE, KEY, RESIZE, DESTROY, WHEELDOWN, WHEELUP, MOUSEUP, MOUSEDOWN, MOUSEMOVE, FOCUS, BLUR, BTNDOWN, BTNUP, MOVE, DRAG, MOUSEWHEEL, RESPONSE, ERROR } from '@pres/enum-events';
import { UNDEFINED, ENTER, LINEFEED, RETURN, MIDDLE, UNKNOWN, LEFT, RIGHT } from '@pres/enum-key-names';
import { GlobalProgram } from '@pres/global-program';
import { gpmClient } from '@pres/gpm-client';
import { toByte, degrade } from '@pres/util-byte-colors';
import { slice, Logger } from '@pres/util-helpers';
import { VO, SP, SC } from '@texting/enum-chars';
import { FUN, NUM, STR } from '@typen/enum-data-types';
import { StringDecoder } from 'string_decoder';
import { keypressEventsEmitter } from '@pres/events';
import { whichTerminal, TerminfoParser } from '@pres/terminfo-parser';
import cp from 'child_process';
import { EventEmitter } from 'events';
import fs from 'fs';
import util from 'util';
const nullish = x => x === null || x === void 0;
const last = ve => ve[ve.length - 1];
const ALL = 'all';
function stringify(data) {
return caret(data.replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/\t/g, '\\t')).replace(/[^ -~]/g, ch => {
if (ch.charCodeAt(0) > 0xff) return ch;
ch = ch.charCodeAt(0).toString(16);
if (ch.length > 2) {
if (ch.length < 4) ch = '0' + ch;
return `\\u${ch}`;
}
if (ch.length < 2) ch = '0' + ch;
return `\\x${ch}`;
});
}
function caret(data) {
return data.replace(/[\0\x80\x1b-\x1f\x7f\x01-\x1a]/g, ch => {
if (ch === '\0' || ch === '\x80') {
ch = '@';
} else if (ch === ESC) {
ch = '[';
} else if (ch === '\x1c') {
ch = '\\';
} else if (ch === '\x1d') {
ch = ']';
} else if (ch === '\x1e') {
ch = '^';
} else if (ch === '\x1f') {
ch = '_';
} else if (ch === '\x7f') {
ch = '?';
} else {
ch = ch.charCodeAt(0); // From ('A' - 64) to ('Z' - 64).
if (ch >= 1 && ch <= 26) {
ch = String.fromCharCode(ch + 64);
} else {
return String.fromCharCode(ch);
}
}
return `^${ch}`;
});
}
const nextTick = global.setImmediate || process.nextTick.bind(process);
class IO extends EventEmitter {
#logger = null;
#terminal = null;
constructor(options) {
super(options);
this.configIO(options);
}
get terminal() {
return this.#terminal;
}
set terminal(terminal) {
return this.setTerminal(terminal), this.terminal;
}
configIO(options) {
const self = this; // EventEmitter.call(this)
if (!options || options.__proto__ !== Object.prototype) {
const [input, output] = arguments;
options = {
input,
output
};
} // IO
this.options = options;
this.input = options.input || process.stdin; // IO
this.output = options.output || process.stdout; // IO
options.log = options.log || options.dump; // IO - logger
if (options.log) {
this.#logger = fs.createWriteStream(options.log);
if (options.dump) this.setupDump();
} // IO - logger
this.zero = options.zero !== false;
this.useBuffer = options.buffer; // IO
this.#terminal = whichTerminal(options); // IO
// OSX
this.isOSXTerm = process.env.TERM_PROGRAM === 'Apple_Terminal';
this.isiTerm2 = process.env.TERM_PROGRAM === 'iTerm.app' || !!process.env.ITERM_SESSION_ID; // VTE
// NOTE: lxterminal does not provide an env variable to check for.
// NOTE: gnome-terminal and sakura use a later version of VTE
// which provides VTE_VERSION as well as supports SGR events.
this.isXFCE = /xfce/i.test(process.env.COLORTERM);
this.isTerminator = !!process.env.TERMINATOR_UUID;
this.isLXDE = false;
this.isVTE = !!process.env.VTE_VERSION || this.isXFCE || this.isTerminator || this.isLXDE; // xterm and rxvt - not accurate
this.isRxvt = /rxvt/i.test(process.env.COLORTERM);
this.isXterm = false;
this.tmux = !!process.env.TMUX; // IO
this.tmuxVersion = function () {
if (!self.tmux) return 2;
try {
const version = cp.execFileSync('tmux', ['-V'], {
encoding: 'utf8'
});
return +/^tmux ([\d.]+)/i.exec(version.trim().split('\n')[0])[1];
} catch (e) {
return 2;
}
}(); // IO
this._buf = VO; // IO
this._flush = this.flush.bind(this); // IO
if (options.tput !== false) this.setupTput(); // IO
console.log(`>> [program.configIO] [terminal] ${this.#terminal} [tmux] ${this.tmux}`);
}
log() {
return this.#log('LOG', util.format.apply(util, arguments));
}
debug() {
return !this.options.debug ? void 0 : this.#log('DEBUG', util.format.apply(util, arguments));
}
#log(pre, msg) {
var _this$logger;
return (_this$logger = this.#logger) === null || _this$logger === void 0 ? void 0 : _this$logger.write(pre + ': ' + msg + '\n-\n');
}
setupDump() {
const self = this,
write = this.output.write,
decoder = new StringDecoder('utf8');
this.input.on(DATA, data => self.#log('IN', stringify(decoder.write(data))));
this.output.write = function (data) {
self.#log('OUT', stringify(data));
return write.apply(this, arguments);
};
}
setupTput() {
console.log('>> [io.setupTput]');
if (this._tputSetup) return;
this._tputSetup = true;
const self = this,
options = this.options,
write = this.writeOff.bind(this);
const tput = this.tput = new TerminfoParser({
terminal: this.terminal,
padding: options.padding,
extended: options.extended,
printf: options.printf,
termcap: options.termcap,
forceUnicode: options.forceUnicode
});
if (tput.error) nextTick(() => self.emit(WARNING, tput.error.message));
if (tput.padding) nextTick(() => self.emit(WARNING, 'Terminfo padding has been enabled.'));
this.put = function () {
const args = slice(arguments),
cap = args.shift();
if (tput[cap]) return this.writeOff(tput[cap].apply(tput, args));
};
Object.keys(tput).forEach(key => {
if (self[key] == null) self[key] = tput[key];
if (typeof tput[key] !== FUN) return void (self.put[key] = tput[key]);
self.put[key] = tput.padding ? function () {
return tput._print(tput[key].apply(tput, arguments), write);
} : function () {
return self.writeOff(tput[key].apply(tput, arguments));
};
});
}
setTerminal(terminal) {
this.#terminal = terminal.toLowerCase();
delete this._tputSetup;
this.setupTput();
}
has(name) {
var _this$tput;
return ((_this$tput = this.tput) === null || _this$tput === void 0 ? void 0 : _this$tput.has(name)) ?? false;
}
term(is) {
return this.terminal.indexOf(is) === 0;
}
listen() {
const self = this; // console.log(`>> [this.input.listenCount = ${this.input.listenCount}]`)
// Potentially reset window title on exit:
// if (!this.isRxvt) {
// if (!this.isVTE) this.setTitleModeFeature(3);
// this.manipulateWindow(21, function(err, data) {
// if (err) return;
// self._originalTitle = data.text;
// });
// }
// Listen for keys/mouse on input
if (!this.input.listenCount) {
this.input.listenCount = 1;
this.#listenInput();
} else {
this.input.listenCount++;
}
this.on(NEW_LISTENER, this._newHandler = function fn(type) {
if (type === KEYPRESS || type === MOUSE) {
self.removeListener(NEW_LISTENER, fn);
if (self.input.setRawMode && !self.input.isRaw) {
self.input.setRawMode(true);
self.input.resume();
}
}
});
this.on(NEW_LISTENER, function handler(type) {
if (type === MOUSE) {
self.removeListener(NEW_LISTENER, handler), self.bindMouse();
}
}); // Listen for resize on output
if (!this.output.listenCount) {
this.output.listenCount = 1, this.#listenOutput();
} else {
this.output.listenCount++;
}
console.log(`>> [program.listen] [ ${this.eventNames()} ]`);
}
#listenInput() {
const self = this;
setTimeout(() => {}, 3000); // Input
this.input.on(KEYPRESS, this.input._keypressHandler = (ch, key) => {
key = key || {
ch
}; // A mouse sequence. The `keys` module doesn't understand these.
if (key.name === UNDEFINED && (key.code === '[M' || key.code === '[I' || key.code === '[O')) return void 0; // Not sure what this is, but we should probably ignore it.
if (key.name === UNDEFINED) return void 0;
if (key.name === ENTER && key.sequence === LF) key.name = LINEFEED;
if (key.name === RETURN && key.sequence === RN) self.input.emit(KEYPRESS, ch, merge({}, key, {
name: ENTER
}));
const name = `${key.ctrl ? 'C-' : VO}${key.meta ? 'M-' : VO}${key.shift && key.name ? 'S-' : VO}${key.name || ch}`;
key.full = name;
GlobalProgram.instances.forEach(p => {
if (p.input !== self.input) return void 0;
p.emit(KEYPRESS, ch, key);
p.emit(KEY + SP + name, ch, key);
});
});
this.input.on(DATA, this.input._dataHandler = data => GlobalProgram.instances.forEach(p => p.input !== self.input ? void 0 : void p.emit(DATA, data)));
keypressEventsEmitter(this.input);
console.log(`>> [program.#listenInput] [ ${this.input.eventNames()} ]`);
}
#listenOutput() {
const self = this;
if (!this.output.isTTY) nextTick(() => self.emit(WARNING, 'Output is not a TTY')); // Output
function resize() {
GlobalProgram.instances.forEach(p => {
const {
output
} = p;
if (output !== self.output) return void 0;
p.cols = output.columns;
p.rows = output.rows;
p.emit(RESIZE);
});
}
this.output.on(RESIZE, this.output._resizeHandler = () => {
GlobalProgram.instances.forEach(p => {
if (p.output !== self.output) return;
const {
options: {
resizeTimeout
},
_resizeTimer
} = p;
if (!resizeTimeout) return resize();
if (_resizeTimer) clearTimeout(_resizeTimer), delete p._resizeTimer;
const time = typeof resizeTimeout === NUM ? resizeTimeout : 300;
p._resizeTimer = setTimeout(resize, time);
});
});
console.log(`>> [program.#listenOutput] [ ${this.output.eventNames()} ]`);
}
invoke(name, ...args) {
var _this$name;
this.ret = true;
const out = (_this$name = this[name]) === null || _this$name === void 0 ? void 0 : _this$name.apply(this, args);
this.ret = false;
return out;
}
writeOff(text) {
// wr, _write
return this.ret ? text : this.useBuffer ? this.writeBuffer(text) : this.writeOutput(text);
}
writeBuffer(text) {
// bf
if (this.exiting) return void (this.flush(), this.writeOutput(text));
if (this._buf) return void (this._buf += text);
this._buf = text;
nextTick(this._flush);
return true;
}
writeOutput(text) {
// ow, write
if (this.output.writable) this.output.write(text);
}
writeTmux(data) {
// tw
const self = this;
if (this.tmux) {
data = data.replace(/\x1b\\/g, BEL); // Replace all STs with BELs so they can be nested within the DCS code.
data = DCS + 'tmux;' + ESC + data + ST; // Wrap in tmux forward DCS:
// If we've never even flushed yet, it means we're still in the normal buffer. Wait for alt screen buffer.
let iter = 0;
if (this.output.bytesWritten === 0) {
const timer = setInterval(() => {
if (self.output.bytesWritten > 0 || ++iter === 50) {
clearInterval(timer);
self.flush();
self.writeOutput(data);
}
}, 100);
return true;
} // NOTE: Flushing the buffer is required in some cases. The DCS code must be at the start of the output.
this.flush(); // Write out raw now that the buffer is flushed.
return this.writeOutput(data);
}
return this.writeOff(data);
}
print(text, attr) {
return attr ? this.writeOff(this.text(text, attr)) : this.writeOff(text);
}
flush() {
if (!this._buf) return;
this.writeOutput(this._buf);
this._buf = VO;
}
}
function merge(target) {
slice.call(arguments, 1).forEach(source => Object.keys(source).forEach(key => target[key] = source[key]));
return target;
}
/**
* program.js - basic curses-like functionality for blessed.
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
* https://github.com/chjj/blessed
*/
/**
* Program
*/
class Program extends IO {
#boundResponse = false;
#boundMouse = false;
#entitled = '';
#savedCursors = {};
#currMouse = null;
#lastButton = null;
type = 'program'; // writeBuffer = this.writeBuffer //bf
echo = this.print;
unkey = this.removeKey;
recoords = this.auto;
cursorReset = this.resetCursor;
bel = this.bell;
ff = this.form;
kbs = this.backspace;
ht = this.tab; // write = this.writeOutput
// _write = this.writeOff // NOTE: dependencies cleared
// writeOutput = this.writeOutput // ow
// writeTmux = this.writeTmux // tw
cr = this.return;
nel = this.feed;
newline = this.feed; // XTerm mouse events
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
// To better understand these
// the xterm code is very helpful:
// Relevant files:
// button.c, charproc.c, misc.c
// Relevant functions in xterm/button.c:
// BtnCode, EmitButtonCode, EditorButton, SendMousePosition
// send a mouse event:
// regular/utf8: ^[[M Cb Cx Cy
// urxvt: ^[[ Cb ; Cx ; Cy M
// sgr: ^[[ Cb ; Cx ; Cy M/m
// vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r
// locator: CSI P e ; P b ; P r ; P c ; P p & w
// motion example of a left click:
// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
// mouseup, mousedown, mousewheel
// left click: ^[[M 3<^[[M#3<
/**
* Esc
*/
ind = this.index;
ri = this.reverseIndex;
reverse = this.reverseIndex;
saveCursor = this.sc;
restoreCursor = this.rc; // Save Cursor Locally
lsaveCursor = this.scL; // Restore Cursor Locally
lrestoreCursor = this.rcL;
enter_alt_charset_mode = this.smacs;
as = this.smacs;
exit_alt_charset_mode = this.rmacs;
ae = this.rmacs;
/**
* CSI
*/
cursorUp = this.cuu; // Cursor Up Ps Times (default = 1) (CUU).
up = this.cuu;
cursorDown = this.cud; // Cursor Down Ps Times (default = 1) (CUD).
down = this.cud;
cursorForward = this.cuf; // Cursor Forward Ps Times (default = 1) (CUF).
right = this.cuf;
forward = this.cuf; // Specific to iTerm2, but I think it's really cool.
// Example:
// if (!screen.copyToClipboard(text)) {
// execClipboardProgram(text);
cursorBackward = this.cub; // Cursor Backward Ps Times (default = 1) (CUB).
left = this.cub;
back = this.cub; // Cursor Position [row;column] (default = [1,1]) (CUP).
cursorPos = this.cup;
pos = this.cup;
eraseInDisplay = this.ed;
eraseInLine = this.el;
charAttr = this.sgr;
attr = this.sgr;
parseAttr = this.#sgr;
setForeground = this.fg;
setBackground = this.bg;
deviceStatus = this.dsr;
/**
* Additions
*/
insertChars = this.ich;
cursorNextLine = this.cnl;
cursorPrecedingLine = this.cpl;
cursorCharAbsolute = this.cha;
insertLines = this.il;
deleteLines = this.dl;
deleteChars = this.dch;
eraseChars = this.ech;
charPosAbsolute = this.hpa; // Character Position Absolute [column] (default = [row,1]) (HPA).
HPositionRelative = this.hpr; // Character Position Relative [columns] (default = [row,col+1]) (HPR).
sendDeviceAttributes = this.da;
linePosAbsolute = this.vpa;
VPositionRelative = this.vpr;
HVPosition = this.hvp;
setMode = this.sm;
setDecPrivMode = this.decset;
dectcem = this.showCursor;
cnorm = this.showCursor;
cvvis = this.showCursor;
alternate = this.alternateBuffer;
smcup = this.alternateBuffer;
resetMode = this.rm;
resetDecPrivMode = this.decrst;
dectcemh = this.hideCursor;
cursor_invisible = this.hideCursor;
vi = this.hideCursor;
civis = this.hideCursor;
rmcup = this.normalBuffer;
setScrollRegion = this.decstbm;
csr = this.decstbm;
scA = this.scosc;
saveCursorA = this.scosc;
rcA = this.scorc;
restoreCursorA = this.scorc;
cursorForwardTab = this.cht;
scrollUp = this.su;
scrollDown = this.sd;
initMouseTracking = this.xthimouse;
resetTitleModes = this.xtrmtitle;
cursorBackwardTab = this.cbt;
repeatPrecedingCharacter = this.rep;
tabClear = this.tbc;
mediaCopy = this.mc;
print_screen = this.ps;
mc0 = this.ps;
prtr_off = this.pf;
mc4 = this.pf;
prtr_on = this.po;
mc5 = this.po;
prtr_non = this.pO;
mc5p = this.pO;
setResources = this.xtmodkeys; // Set/reset key modifier options (XTMODKEYS), xterm.
disableModifiers = this.xtunmodkeys; // Disable key modifier options, xterm.
setPointerMode = this.xtsmpointer; // Set resource value pointerMode (XTSMPOINTER), xterm.
// XTerm mouse events
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
// To better understand these
// the xterm code is very helpful:
// Relevant files:
// button.c, charproc.c, misc.c
// Relevant functions in xterm/button.c:
// BtnCode, EmitButtonCode, EditorButton, SendMousePosition
// send a mouse event:
// regular/utf8: ^[[M Cb Cx Cy
// urxvt: ^[[ Cb ; Cx ; Cy M
// sgr: ^[[ Cb ; Cx ; Cy M/m
// vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r
// locator: CSI P e ; P b ; P r ; P c ; P p & w
// motion example of a left click:
// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
// mouseup, mousedown, mousewheel
decstr = this.softReset;
rs2 = this.softReset;
requestAnsiMode = this.decrqm;
requestPrivateMode = this.decrqmp;
setConformanceLevel = this.decscl;
loadLEDs = this.decll;
setCursorStyle = this.decscusr;
setCharProtectionAttr = this.decsca; // Select character protection attribute (DECSCA), VT220.
restorePrivateValues = this.xtrestore;
setAttrInRectangle = this.deccara; // Change Attributes in Rectangular Area (DECCARA), VT400 and up.
savePrivateValues = this.xtsave;
manipulateWindow = this.xtwinops;
reverseAttrInRectangle = this.decrara;
setTitleModeFeature = this.xtsmtitle;
setWarningBellVolume = this.decswbv;
setMarginBellVolume = this.decsmbv;
copyRectangle = this.deccra;
enableFilterRectangle = this.decefr;
requestParameters = this.decreqtparm; // TODO: pull request - changed x to *x
selectChangeExtent = this.decsace;
fillRectangle = this.decfra;
enableLocatorReporting = this.decelr;
eraseRectangle = this.decera;
setLocatorEvents = this.decsle;
selectiveEraseRectangle = this.decsera;
decrqlp = this.requestLocatorPosition;
req_mouse_pos = this.requestLocatorPosition;
reqmp = this.requestLocatorPosition; // TODO: pull request since modified from ' }' to '\'}'
insertColumns = this.decic; // Insert Ps Column(s) (default = 1) (DECIC), VT420 and up.
// TODO: pull request since modified from ' ~' to '\'~'
deleteColumns = this.decdc; // Delete Ps Column(s) (default = 1) (DECDC), VT420 and up.
constructor(options = {}) {
super(options);
GlobalProgram.initialize(this);
this.configGrid();
this.listen();
console.log(`>> [new program]`);
Logger.log('program', 'new program', '...');
}
get title() {
return this.#entitled;
}
set title(title) {
return this.setTitle(title), this.#entitled;
}
static build(options) {
return new Program(options);
}
configGrid() {
this.x = 0;
this.y = 0;
this.savedX = 0;
this.savedY = 0;
this.cols = this.output.columns || 1;
this.rows = this.output.rows || 1;
this.scrollTop = 0;
this.scrollBottom = this.rows - 1;
console.log(`>> [program.configGrid] (${this.rows},${this.cols}) [tput.colors] (${this.tput.colors})`);
}
destroy() {
Logger.log('program', 'destroy', '');
const index = GlobalProgram.instances.indexOf(this);
if (~index) {
this.flush();
this.exiting = true;
GlobalProgram.removeInstanceAt(index);
this.input.listenCount--;
this.output.listenCount--;
if (this.input.listenCount === 0) {
this.input.removeListener(KEYPRESS, this.input._keypressHandler);
this.input.removeListener(DATA, this.input._dataHandler);
delete this.input._keypressHandler;
delete this.input._dataHandler;
if (this.input.setRawMode) {
if (this.input.isRaw) {
this.input.setRawMode(false);
}
if (!this.input.destroyed) {
this.input.pause();
}
}
}
if (this.output.listenCount === 0) {
this.output.removeListener(RESIZE, this.output._resizeHandler);
delete this.output._resizeHandler;
}
this.removeListener(NEW_LISTENER, this._newHandler);
delete this._newHandler;
this.destroyed = true;
this.emit(DESTROY);
}
}
key(key, listener) {
if (typeof key === STR) key = key.split(/\s*,\s*/);
key.forEach(function (key) {
return this.on(KEY + SP + key, listener);
}, this);
}
onceKey(key, listener) {
if (typeof key === STR) key = key.split(/\s*,\s*/);
key.forEach(function (key) {
return this.once(KEY + SP + key, listener);
}, this);
}
removeKey(key, listener) {
if (typeof key === STR) key = key.split(/\s*,\s*/);
key.forEach(function (key) {
return this.removeListener(KEY + SP + key, listener);
}, this);
} // mousewheel up: ^[[M`3>
bindMouse() {
if (this.#boundMouse) return;
this.#boundMouse = true;
const decoder = new StringDecoder('utf8'),
self = this;
this.on(DATA, data => {
const text = decoder.write(data);
if (!text) return;
self.#bindMouse(text, data);
});
}
#bindMouse(s, buf) {
const self = this;
let key, parts, b, x, y, mod, params, down, page, button;
key = {
name: undefined,
ctrl: false,
meta: false,
shift: false
};
if (Buffer.isBuffer(s)) {
if (s[0] > 127 && s[1] === undefined) {
s[0] -= 128;
s = ESC + s.toString('utf-8');
} else {
s = s.toString('utf-8');
}
} // if (this.8bit) {
// s = s.replace(/\233/g, CSI);
// buf = new Buffer(s, 'utf8');
// }
// XTerm / X10 for buggy VTE
// VTE can only send unsigned chars and no unicode for coords. This limits
// them to 0xff. However, normally the x10 protocol does not allow a byte
// under 0x20, but since VTE can have the bytes overflow, we can consider
// bytes below 0x20 to be up to 0xff + 0x20. This gives a limit of 287. Since
// characters ranging from 223 to 248 confuse javascript's utf parser, we
// need to parse the raw binary. We can detect whether the terminal is using
// a bugged VTE version by examining the coordinates and seeing whether they
// are a value they would never otherwise be with a properly implemented x10
// protocol. This method of detecting VTE is only 99% reliable because we
// can't check if the coords are 0x00 (255) since that is a valid x10 coord
// technically.
const bx = s.charCodeAt(4);
const by = s.charCodeAt(5);
if (buf[0] === 0x1b && buf[1] === 0x5b && buf[2] === 0x4d && (this.isVTE || bx >= 65533 || by >= 65533 || bx > 0x00 && bx < 0x20 || by > 0x00 && by < 0x20 || buf[4] > 223 && buf[4] < 248 && buf.length === 6 || buf[5] > 223 && buf[5] < 248 && buf.length === 6)) {
b = buf[3];
x = buf[4];
y = buf[5]; // unsigned char overflow.
if (x < 0x20) x += 0xff;
if (y < 0x20) y += 0xff; // Convert the coordinates into a
// properly formatted x10 utf8 sequence.
s = CSI + `M${String.fromCharCode(b)}${String.fromCharCode(x)}${String.fromCharCode(y)}`;
} // XTerm / X10
if (parts = /^\x1b\[M([\x00\u0020-\uffff]{3})/.exec(s)) {
b = parts[1].charCodeAt(0);
x = parts[1].charCodeAt(1);
y = parts[1].charCodeAt(2);
key.name = MOUSE;
key.type = 'X10';
key.raw = [b, x, y, parts[0]];
key.buf = buf;
key.x = x - 32;
key.y = y - 32;
if (this.zero) key.x--, key.y--;
if (x === 0) key.x = 255;
if (y === 0) key.y = 255;
mod = b >> 2;
key.shift = !!(mod & 1);
key.meta = !!(mod >> 1 & 1);
key.ctrl = !!(mod >> 2 & 1);
b -= 32;
if (b >> 6 & 1) {
key.action = b & 1 ? WHEELDOWN : WHEELUP;
key.button = MIDDLE;
} else if (b === 3) {
// NOTE: x10 and urxvt have no way
// of telling which button mouseup used.
key.action = MOUSEUP;
key.button = this.#lastButton || UNKNOWN;
this.#lastButton = null;
} else {
key.action = MOUSEDOWN;
button = b & 3;
key.button = button === 0 ? LEFT : button === 1 ? MIDDLE : button === 2 ? RIGHT : UNKNOWN;
this.#lastButton = key.button;
} // Probably a movement.
// The *newer* VTE gets mouse movements comepletely wrong.
// This presents a problem: older versions of VTE that get it right might
// be confused by the second conditional in the if statement.
// NOTE: Possibly just switch back to the if statement below.
// none, shift, ctrl, alt
// gnome: 32, 36, 48, 40
// xterm: 35, _, 51, _
// urxvt: 35, _, _, _
// if (key.action === MOUSEDOWN && key.button === UNKNOWN) {
if (b === 35 || b === 39 || b === 51 || b === 43 || this.isVTE && (b === 32 || b === 36 || b === 48 || b === 40)) {
delete key.button;
key.action = MOUSEMOVE;
}
self.emit(MOUSE, key);
return;
} // URxvt
if (parts = /^\x1b\[(\d+;\d+;\d+)M/.exec(s)) {
params = parts[1].split(SC);
b = +params[0];
x = +params[1];
y = +params[2];
key.name = MOUSE;
key.type = 'urxvt';
key.raw = [b, x, y, parts[0]];
key.buf = buf;
key.x = x;
key.y = y;
if (this.zero) key.x--, key.y--;
mod = b >> 2;
key.shift = !!(mod & 1);
key.meta = !!(mod >> 1 & 1);
key.ctrl = !!(mod >> 2 & 1); // XXX Bug in urxvt after wheelup/down on mousemove
// NOTE: This may be different than 128/129 depending
// on mod keys.
if (b === 128 || b === 129) b = 67;
b -= 32;
if (b >> 6 & 1) {
key.action = b & 1 ? WHEELDOWN : WHEELUP;
key.button = MIDDLE;
} else if (b === 3) {
// NOTE: x10 and urxvt have no way
// of telling which button mouseup used.
key.action = MOUSEUP;
key.button = this.#lastButton || UNKNOWN;
this.#lastButton = null;
} else {
key.action = MOUSEDOWN;
button = b & 3;
key.button = button === 0 ? LEFT : button === 1 ? MIDDLE : button === 2 ? RIGHT : UNKNOWN; // NOTE: 0/32 = mousemove, 32/64 = mousemove with left down
// if ((b >> 1) === 32)
this.#lastButton = key.button;
} // Probably a movement.
// The *newer* VTE gets mouse movements comepletely wrong.
// This presents a problem: older versions of VTE that get it right might
// be confused by the second conditional in the if statement.
// NOTE: Possibly just switch back to the if statement below.
// none, shift, ctrl, alt
// urxvt: 35, _, _, _
// gnome: 32, 36, 48, 40
// if (key.action === MOUSEDOWN && key.button === UNKNOWN) {
if (b === 35 || b === 39 || b === 51 || b === 43 || this.isVTE && (b === 32 || b === 36 || b === 48 || b === 40)) {
delete key.button;
key.action = MOUSEMOVE;
}
self.emit(MOUSE, key);
return;
} // SGR
if (parts = /^\x1b\[<(\d+;\d+;\d+)([mM])/.exec(s)) {
down = parts[2] === 'M';
params = parts[1].split(SC);
b = +params[0];
x = +params[1];
y = +params[2];
key.name = MOUSE;
key.type = 'sgr';
key.raw = [b, x, y, parts[0]];
key.buf = buf;
key.x = x;
key.y = y;
if (this.zero) key.x--, key.y--;
mod = b >> 2;
key.shift = !!(mod & 1);
key.meta = !!(mod >> 1 & 1);
key.ctrl = !!(mod >> 2 & 1);
if (b >> 6 & 1) {
key.action = b & 1 ? WHEELDOWN : WHEELUP;
key.button = MIDDLE;
} else {
key.action = down ? MOUSEDOWN : MOUSEUP;
button = b & 3;
key.button = button === 0 ? LEFT : button === 1 ? MIDDLE : button === 2 ? RIGHT : UNKNOWN;
} // Probably a movement.
// The *newer* VTE gets mouse movements comepletely wrong.
// This presents a problem: older versions of VTE that get it right might
// be confused by the second conditional in the if statement.
// NOTE: Possibly just switch back to the if statement below.
// none, shift, ctrl, alt
// xterm: 35, _, 51, _
// gnome: 32, 36, 48, 40
// if (key.action === MOUSEDOWN && key.button === UNKNOWN) {
if (b === 35 || b === 39 || b === 51 || b === 43 || this.isVTE && (b === 32 || b === 36 || b === 48 || b === 40)) {
delete key.button;
key.action = MOUSEMOVE;
}
self.emit(MOUSE, key);
return;
} // DEC
// The xterm mouse documentation says there is a
// `<` prefix, the DECRQLP says there is no prefix.
if (parts = /^\x1b\[<(\d+;\d+;\d+;\d+)&w/.exec(s)) {
params = parts[1].split(SC);
b = +params[0];
x = +params[1];
y = +params[2];
page = +params[3];
key.name = MOUSE;
key.type = 'dec';
key.raw = [b, x, y, parts[0]];
key.buf = buf;
key.x = x;
key.y = y;
key.page = page;
if (this.zero) key.x--, key.y--;
key.action = b === 3 ? MOUSEUP : MOUSEDOWN;
key.button = b === 2 ? LEFT : b === 4 ? MIDDLE : b === 6 ? RIGHT : UNKNOWN;
self.emit(MOUSE, key);
return;
} // vt300
if (parts = /^\x1b\[24([0135])~\[(\d+),(\d+)\]\r/.exec(s)) {
b = +parts[1];
x = +parts[2];
y = +parts[3];
key.name = MOUSE;
key.type = 'vt300';
key.raw = [b, x, y, parts[0]];
key.buf = buf;
key.x = x;
key.y = y;
if (this.zero) key.x--, key.y--;
key.action = MOUSEDOWN;
key.button = b === 1 ? LEFT : b === 2 ? MIDDLE : b === 5 ? RIGHT : UNKNOWN;
self.emit(MOUSE, key);
return;
}
if (parts = /^\x1b\[(O|I)/.exec(s)) {
key.action = parts[1] === 'I' ? FOCUS : BLUR;
self.emit(MOUSE, key);
self.emit(key.action);
}
} // gpm support for linux vc
enableGpm() {
const self = this;
if (this.gpm) return;
const gpm = this.gpm = gpmClient();
this.gpm.on(BTNDOWN, (button, modifier, x, y) => {
x--, y--;
self.emit(MOUSE, gpm.createKey(MOUSEDOWN, button, modifier, x, y));
});
this.gpm.on(BTNUP, (button, modifier, x, y) => {
x--, y--;
self.emit(MOUSE, gpm.createKey(MOUSEUP, button, modifier, x, y));
});
this.gpm.on(MOVE, (button, modifier, x, y) => {
x--, y--;
self.emit(MOUSE, gpm.createKey(MOUSEMOVE, button, modifier, x, y));
});
this.gpm.on(DRAG, (button, modifier, x, y) => {
x--, y--;
self.emit(MOUSE, gpm.createKey(MOUSEMOVE, button, modifier, x, y));
});
this.gpm.on(MOUSEWHEEL, (button, modifier, x, y, dx, dy) => {
self.emit(MOUSE, gpm.createKey(dy > 0 ? WHEELUP : WHEELDOWN, button, modifier, x, y, dx, dy));
});
}
disableGpm() {
if (this.gpm) {
this.gpm.stop(), delete this.gpm;
}
} // All possible responses from the terminal
bindResponse() {
if (this.#boundResponse) return void 0;
this.#boundResponse = true;
const decoder = new StringDecoder('utf8'),
self = this;
this.on(DATA, data => {
if (data = decoder.write(data)) {
self.#bindResponse(data);
}
});
}
#bindResponse(s) {
const out = {};
let parts;
if (Buffer.isBuffer(s)) {
if (s[0] > 127 && nullish(s[1])) {
s[0] -= 128, s = ESC + s.toString('utf-8');
} else {
s = s.toString('utf-8');
}
} // CSI P s c
// Send Device Attributes (Primary DA).
// CSI > P s c
// Send Device Attributes (Secondary DA).
if (parts = /^\x1b\[(\?|>)(\d*(?:;\d*)*)c/.exec(s)) {
parts = parts[2].split(SC).map(ch => +ch || 0);
out.event = 'device-attributes';
out.code = 'DA';
if (parts[1] === '?') {
out.type = 'primary-attribute'; // VT100-style params:
if (parts[0] === 1 && parts[2] === 2) {
out.term = 'vt100', out.advancedVideo = true;
} else if (parts[0] === 1 && parts[2] === 0) {
out.term = 'vt101';
} else if (parts[0] === 6) {
out.term = 'vt102';
} else if (parts[0] === 60 && parts[1] === 1 && parts[2] === 2 && parts[3] === 6 && parts[4] === 8 && parts[5] === 9 && parts[6] === 15) {
out.term = 'vt220';
} else {
// VT200-style params:
parts.forEach(attr => attr === 1 ? out.cols132 = true : attr === 2 ? out.printer = true : attr === 6 ? out.selectiveErase = true : attr === 8 ? out.userDefinedKeys = true : attr === 9 ? out.nationalReplacementCharsets = true : attr === 15 ? out.technicalCharacters = true : attr === 18 ? out.userWindows = true : attr === 21 ? out.horizontalScrolling = true : attr === 22 ? out.ansiColor = true : attr === 29 ? out.ansiTextLocator = true : void 0);
}
} else {
out.type = 'secondary-attribute';
out.term = parts[0] === 0 ? 'vt100' : parts[0] === 1 ? 'vt220' : parts[0] === 2 ? 'vt240' : parts[0] === 18 ? 'vt330' : parts[0] === 19 ? 'vt340' : parts[0] === 24 ? 'vt320' : parts[0] === 41 ? 'vt420' : parts[0] === 61 ? 'vt510' : parts[0] === 64 ? 'vt520' : parts[0] === 65 ? 'vt525' : out.term;
out.firmwareVersion = parts[1];
out.romCartridgeRegistrationNumber = parts[2];
} // LEGACY
out.deviceAttributes = out;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return void 0;
} // CSI Ps n Device Status Report (DSR).
// Ps = 5 -> Status Report. Result (``OK'') is
// CSI 0 n
// CSI ? Ps n
// Device Status Report (DSR, DEC-specific).
// Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).
// or CSI ? 1 1 n (not ready).
// Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)
// or CSI ? 2 1 n (locked).
// Ps = 2 6 -> Report Keyboard status as
// CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
// The last two parameters apply to VT400 & up, and denote key-
// board ready and LK01 respectively.
// Ps = 5 3 -> Report Locator status as
// CSI ? 5 3 n Locator available, if compiled-in, or
// CSI ? 5 0 n No Locator, if not.
if (parts = /^\x1b\[(\?)?(\d+)(?:;(\d+);(\d+);(\d+))?n/.exec(s)) {
out.event = 'device-status';
out.code = 'DSR';
if (!parts[1] && parts[2] === '0' && !parts[3]) {
out.type = 'device-status';
out.status = 'OK'; // LEGACY
out.deviceStatus = out.status;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts[1] && (parts[2] === '10' || parts[2] === '11') && !parts[3]) {
out.type = 'printer-status';
out.status = parts[2] === '10' ? 'ready' : 'not ready'; // LEGACY
out.printerStatus = out.status;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts[1] && (parts[2] === '20' || parts[2] === '21') && !parts[3]) {
out.type = 'udk-status';
out.status = parts[2] === '20' ? 'unlocked' : 'locked'; // LEGACY
out.UDKStatus = out.status;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts[1] && parts[2] === '27' && parts[3] === '1' && parts[4] === '0' && parts[5] === '0') {
out.type = 'keyboard-status';
out.status = 'OK'; // LEGACY
out.keyboardStatus = out.status;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts[1] && (parts[2] === '53' || parts[2] === '50') && !parts[3]) {
out.type = 'locator-status';
out.status = parts[2] === '53' ? 'available' : 'unavailable'; // LEGACY
out.locator = out.status;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
out.type = 'error';
out.text = `Unhandled: ${JSON.stringify(parts)}`; // LEGACY
out.error = out.text;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
} // CSI Ps n Device Status Report (DSR).
// Ps = 6 -> Report Cursor Position (CPR) [row;column].
// Result is
// CSI r ; c R
// CSI ? Ps n
// Device Status Report (DSR, DEC-specific).
// Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI
// ? r ; c R (assumes page is zero).
if (parts = /^\x1b\[(\?)?(\d+);(\d+)R/.exec(s)) {
out.event = 'device-status';
out.code = 'DSR';
out.type = 'cursor-status';
out.status = {
x: +parts[3],
y: +parts[2],
page: !parts[1] ? undefined : 0
};
out.x = out.status.x;
out.y = out.status.y;
out.page = out.status.page; // LEGACY
out.cursor = out.status;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
} // CSI Ps ; Ps ; Ps t
// Window manipulation (from dtterm, as well as extensions).
// These controls may be disabled using the allowWindowOps
// resource. Valid values for the first (and any additional
// parameters) are:
// Ps = 1 1 -> Report xterm window state. If the xterm window
// is open (non-iconified), it returns CSI 1 t . If the xterm
// window is iconified, it returns CSI 2 t .
// Ps = 1 3 -> Report xterm window position. Result is CSI 3
// ; x ; y t
// Ps = 1 4 -> Report xterm window in pixels. Result is CSI
// 4 ; height ; width t
// Ps = 1 8 -> Report the size of the text area in characters.
// Result is CSI 8 ; height ; width t
// Ps = 1 9 -> Report the size of the screen in characters.
// Result is CSI 9 ; height ; width t
if (parts = /^\x1b\[(\d+)(?:;(\d+);(\d+))?t/.exec(s)) {
out.event = 'window-manipulation';
out.code = VO;
if ((parts[1] === '1' || parts[1] === '2') && !parts[2]) {
out.type = 'window-state';
out.state = parts[1] === '1' ? 'non-iconified' : 'iconified'; // LEGACY
out.windowState = out.state;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts[1] === '3' && parts[2]) {
out.type = 'window-position';
out.position = {
x: +parts[2],
y: +parts[3]
};
out.x = out.position.x;
out.y = out.position.y; // LEGACY
out.windowPosition = out.position;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts[1] === '4' && parts[2]) {
out.type = 'window-size-pixels';
out.size = {
height: +parts[2],
width: +parts[3]
};
out.height = out.size.height;
out.width = out.size.width; // LEGACY
out.windowSizePixels = out.size;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts[1] === '8' && parts[2]) {
out.type = 'textarea-size';
out.size = {
height: +parts[2],
width: +parts[3]
};
out.height = out.size.height;
out.width = out.size.width; // LEGACY
out.textAreaSizeCharacters = out.size;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts[1] === '9' && parts[2]) {
out.type = 'screen-size';
out.size = {
height: +parts[2],
width: +parts[3]
};
out.height = out.size.height;
out.width = out.size.width; // LEGACY
out.screenSizeCharacters = out.size;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
out.type = 'error';
out.text = `Unhandled: ${JSON.stringify(parts)}`; // LEGACY
out.error = out.text;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
} // rxvt-unicode does not support window manipulation
// Result Normal: OSC l/L 0xEF 0xBF 0xBD
// Result ASCII: OSC l/L 0x1c (file separator)
// Result UTF8->ASCII: OSC l/L 0xFD
// Test with:
// echo -ne '\ePtmux;\e\e[>3t\e\\'
// sleep 2 && echo -ne '\ePtmux;\e\e[21t\e\\' & cat -v
// -
// echo -ne '\e[>3t'
// sleep 2 && echo -ne '\e[21t' & cat -v
if (parts = /^\x1b\](l|L)([^\x07\x1b]*)$/.exec(s)) {
parts[2] = 'rxvt';
s = OSC + parts[1] + parts[2] + ST;
} // CSI Ps ; Ps ; Ps t
// Window manipulation (from dtterm, as well as extensions).
// These controls may be disabled using the allowWindowOps
// resource. Valid values for the first (and any additional
// parameters) are:
// Ps = 2 0 -> Report xterm window's icon label. Result is
// OSC L label ST
// Ps = 2 1 -> Report xterm window's title. Result is OSC l
// label ST
if (parts = /^\x1b\](l|L)([^\x07\x1b]*)(?:\x07|\x1b\\)/.exec(s)) {
out.event = 'window-manipulation';
out.code = VO;
if (parts[1] === 'L') {
out.type = 'window-icon-label';
out.text = parts[2]; // LEGACY
out.windowIconLabel = out.text;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts[1] === 'l') {
out.type = 'window-title';
out.text = parts[2]; // LEGACY
out.windowTitle = out.text;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
out.type = 'error';
out.text = `Unhandled: ${JSON.stringify(parts)}`; // LEGACY
out.error = out.text;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
}
if (parts = /^\x1b\[(\d+(?:;\d+){4})&w/.exec(s)) {
parts = parts[1].split(SC).map(ch => +ch);
out.event = 'locator-position';
out.code = 'DECRQLP';
out.status = parts[0] === 0 ? 'locator-unavailable' : parts[0] === 1 ? 'request' : parts[0] === 2 ? 'left-button-down' : parts[0] === 3 ? 'left-button-up' : parts[0] === 4 ? 'middle-button-down' : parts[0] === 5 ? 'middle-button-up' : parts[0] === 6 ? 'right-button-down' : parts[0] === 7 ? 'right-button-up' : parts[0] === 8 ? 'm4-button-down' : parts[0] === 9 ? 'm4-button-up' : parts[0] === 10 ? 'locator-outside' : out.status;
out.mask = parts[1];
out.row = parts[2];
out.col = parts[3];
out.page = parts[4]; // LEGACY
out.locatorPosition = out;
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
return;
} // OSC Ps ; Pt BEL
// OSC Ps ; Pt ST
// Set Text Parameters
if (parts = /^\x1b\](\d+);([^\x07\x1b]+)(?:\x07|\x1b\\)/.exec(s)) {
out.event = 'text-params';
out.code = 'Set Text Parameters';
out.ps = +s[1];
out.pt = s[2];
this.emit(RESPONSE, out);
this.emit(RESPONSE + SP + out.event, out);
}
}
response(name, text, callback, noBypass) {
const self = this;
if (arguments.length === 2) {
callback = text;
text = name;
name = null;
}
if (!callback) callback = () => {};
this.bindResponse();
name = name ? RESPONSE + SP + name : RESPONSE;
let responseHandler;
this.once(name, responseHandler = event => {
if (timeout) clearTimeout(timeout);
if (event.type === ERROR) {
return callback(new Error(`${event.event}: ${event.text}`));
}
return callback(null, event);
});
const timeout = setTimeout(() => {
self.removeListener(name, responseHandler);
return callback(new Error('Timeout.'));
}, 2000);
return noBypass ? this.writeOff(text) : this.writeTmux(text);
}
auto() {
this.x < 0 ? this.x = 0 : this.x >= this.cols ? this.x = this.cols - 1 : void 0;
this.y < 0 ? this.y = 0 : this.y >= this.rows ? this.y = this.rows - 1 : void 0;
}
setx(x) {
return this.cha(x);
}
sety(y) {
return this.vpa(y);
}
move(x, y) {
return this.cup(y, x);
}
omove(x, y) {
const {
zero
} = this;
x = !zero ? (x || 1) - 1 : x || 0;
y = !zero ? (y || 1) - 1 : y || 0;
if (y === this.y && x === this.x) {
return;
}
if (y === this.y) {
x > this.x ? this.cuf(x - this.x) : x < this.x ? this.cub(this.x - x) : void 0;
} else if (x === this.x) {
y > this.y ? this.cud(y - this.y) : y < this.y ? this.cuu(this.y - y) : void 0;
} else {
if (!zero) x++, y++;
this.cup(y, x);
}
}
rsetx(x) {
return !x ? void 0 : x > 0 ? this.forward(x) : this.back(-x);
}
rsety(y) {
return !y ? void 0 : y > 0 ? this.up(y) : this.down(-y);
} // return this.VPositionRelative(y);
rmove(x, y) {
this.rsetx(x), this.rsety(y);
}
simpleInsert(ch, i, attr) {
return this.writeOff(this.repeat(ch, i), attr);
}
repeat(ch, i) {
if (!i || i < 0) i = 0;
return Array(i + 1).join(ch);
} // }
copyToClipboard(text) {
return this.isiTerm2 ? (this.writeTmux(OSC + `50;CopyToCliboard=${text}` + BEL), true) : false;
} // Only XTerm and iTerm2. If you know of any others, post them.
cursorShape(shape, blink) {
if (this.isiTerm2) {
switch (shape) {
case 'block':
if (!blink) {
this.writeTmux(OSC + '50;CursorShape=0;BlinkingCursorEnabled=0' + BEL);
} else {
this.writeTmux(OSC + '50;CursorShape=0;BlinkingCursorEnabled=1' + BEL);
}
break;
case 'underline':
// !blink ? this.#writeTm('\x1b]50' + ';CursorShape=n;BlinkingCursorEnabled=0' + BEL) : this.#writeTm('\x1b]50' + ';CursorShape=n;BlinkingCursorEnabled=1' + BEL)
break;
case 'line':
!blink ? this.writeTmux(OSC + '50;CursorShape=1;BlinkingCursorEnabled=0' + BEL) : this.writeTmux(OSC + '50' + ';CursorShape=1;BlinkingCursorEnabled=1' + BEL);
break;
}
return true;
} else if (this.term('xterm') || this.term('screen')) {
switch (shape) {
case 'block':
!blink ? this.writeTmux(CSI + '0' + DECSCUSR) : this.writeTmux(CSI + '1' + DECSCUSR);
break;
case 'underline':
!blink ? this.writeTmux(CSI + '2' + DECSCUSR) : this.writeTmux(CSI + '3' + DECSCUSR);
break;
case 'line':
!blink ? this.writeTmux(CSI + '4' + DECSCUSR) : this.writeTmux(CSI + '5' + DECSCUSR);
break;
}
return true;
}
return false;
}
cursorColor(color) {
return this.term('xterm') || this.term('rxvt') || this.term('screen') ? (this.writeTmux(OSC + `12;${color}` + BEL), true) : false;
}
resetCursor() {
if (this.term('xterm') || this.term('rxvt') || this.term('screen')) {
// XXX
// return this.resetColors();
this.writeTmux(CSI + '0' + DECSCUSR);
this.writeTmux(OSC + '112' + BEL); // urxvt doesnt support OSC 112
this.writeTmux(OSC + '12;white' + BEL);
return true;
}
return false;
}
getTextParams(arg, callback) {
return this.response('text-params', OSC + arg + SC + '?' + BEL, (err, data) => err ? callback(err) : callback(null, data.pt));
}
getCursorColor(callback) {
return this.getTextParams(12, callback);
}
/**
* Normal
*/
nul() {
return this.writeOff('\x80');
}
bell() {
return this.has('bel') ? this.put.bel() : this.writeOff(BEL);
}
vtab() {
this.y++;
this.auto();
return this.writeOff(VT);
}
form() {
return this.has('ff') ? this.put.ff() : this.writeOff(FF);
}
backspace() {
this.x--;
this.auto();
return this.has('kbs') ? this.put.kbs() : this.writeOff(BS);
}
tab() {
this.x += 8;
this.auto();
return this.has('ht') ? this.put.ht() : this.writeOff(TAB);
}
shiftOut() {
return this.writeOff(SO);
}
shiftIn() {
return this.writeOff(SI);
}
return() {
this.x = 0;
if (this.has('cr')) return this.put.cr();
return this.writeOff(RN);
}
feed() {
if (this.tput && this.tput.booleans.eat_newline_glitch && this.x >= this.cols) return;
this.x = 0;
this.y++;
this.auto();
return this.has('nel') ? this.put.nel() : thi