neo-blessed/lib/tput.js

2260 lines
50 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Tput for node.js
* Copyright (c) 2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/blessed
*/
// Resources:
// $ man term
// $ man terminfo
// http://invisible-island.net/ncurses/man/term.5.html
// https://en.wikipedia.org/wiki/Terminfo
// Todo:
// - xterm's XT (set-title capability?) value should
// be true (at least tmux thinks it should).
// It's not parsed as true. Investigate.
// - Possibly switch to other method of finding the
// extended data string table: i += h.symOffsetCount * 2;
/**
* Modules
*/
var assert = require('assert')
, path = require('path')
, fs = require('fs');
/**
* Tput
*/
function Tput(options) {
if (!(this instanceof Tput)) {
return new Tput(options);
}
options = options || {};
if (typeof options === 'string') {
options = { term: options };
}
this.options = options;
this.terminal = options.term
|| options.terminal
|| process.env.TERM
|| 'xterm';
this.debug = options.debug;
this.padding = options.padding;
this.extended = options.extended;
this.printf = options.printf;
this.termcap = options.termcap;
this.terminfoPrefix = options.terminfoPrefix;
this.terminfoFile = options.terminfoFile;
this.termcapFile = options.termcapFile;
try {
if (this.termcap) {
this.compileTermcap();
} else {
this.compileTerminfo();
}
} catch (e) {
// If there was an error, fallback
// to an internally stored terminfo/cap.
if (this.debug) {
throw e;
}
this._useXtermI();
}
this.detectFeatures();
}
Tput.prototype.term = function(is) {
return this.terminal.indexOf(is) === 0;
};
/**
* Fallback
*/
Tput.prototype._useVt102C = function() {
this.terminal = 'vt102';
this.termcap = true;
this.compileTermcap();
};
Tput.prototype._useXtermC = function() {
this.terminal = 'xterm';
this.termcap = true;
this.termcapFile = __dirname + '/../usr/xterm.termcap';
this.compileTermcap();
};
Tput.prototype._useXtermI = function() {
this.terminal = 'xterm';
this.termcap = false;
this.terminfoFile = __dirname + '/../usr/xterm';
this.compileTerminfo();
};
/**
* Terminfo
*/
Tput.prototype.readTerminfo = function(data) {
if (!data) {
var file = this.terminfoFile
|| this._terminfoPrefix(this.terminfoPrefix)
|| this._terminfoPrefix(process.env.TERMINFO)
|| this._terminfoPrefix(process.env.TERMINFO_DIRS)
|| this._terminfoPrefix(process.env.HOME + '/.terminfo')
|| this._terminfoPrefix('/usr/share/terminfo')
|| this._terminfoPrefix('/usr/share/lib/terminfo')
|| this._terminfoPrefix('/usr/lib/terminfo')
|| this._terminfoPrefix('/usr/local/share/terminfo')
|| this._terminfoPrefix('/usr/local/share/lib/terminfo')
|| this._terminfoPrefix('/usr/local/lib/terminfo')
|| this._terminfoPrefix('/usr/local/ncurses/lib/terminfo');
data = fs.readFileSync(file);
}
var info = this.parseTerminfo(data);
if (this.debug) {
this._terminfo = info;
}
return info;
};
Tput.prototype._terminfoPrefix = function(prefix) {
if (!prefix) return;
var file = path.resolve(
prefix,
path.basename(this.terminal[0]),
path.basename(this.terminal)
);
try {
fs.statSync(file);
return file;
} catch (e) {
;
}
};
/**
* Terminfo Parser
* All shorts are little-endian
*/
Tput.prototype.parseTerminfo = function(data) {
var info = {}
, l = data.length
, i = 0
, v
, o;
var h = info.header = {
dataSize: data.length,
headerSize: 12,
magicNumber: (data[1] << 8) | data[0],
namesSize: (data[3] << 8) | data[2],
boolCount: (data[5] << 8) | data[4],
numCount: (data[7] << 8) | data[6],
strCount: (data[9] << 8) | data[8],
strTableSize: (data[11] << 8) | data[10]
};
h.total = h.headerSize
+ h.namesSize
+ h.boolCount
+ h.numCount * 2
+ h.strCount * 2
+ h.strTableSize;
i += h.headerSize;
// Names Section
var names = data.toString('ascii', i, i + h.namesSize - 1)
, parts = names.split('|')
, name = parts[0]
, desc = parts.pop();
info.name = name;
info.names = parts;
info.desc = desc;
i += h.namesSize - 1;
// Names is nul-terminated.
assert.equal(data[i], 0);
i++;
// Booleans Section
// One byte for each flag
// Same order as <term.h>
info.bools = {};
l = i + h.boolCount;
o = 0;
for (; i < l; i++) {
v = Tput.bools[o++];
info.bools[v] = data[i] === 1;
}
// Null byte in between to make sure numbers begin on an even byte.
if (i % 2) {
assert.equal(data[i], 0);
i++;
}
// Numbers Section
info.numbers = {};
l = i + h.numCount * 2;
o = 0;
for (; i < l; i += 2) {
v = Tput.numbers[o++];
if (data[i + 1] === 0377 && data[i] === 0377) {
info.numbers[v] = -1;
} else {
info.numbers[v] = (data[i + 1] << 8) | data[i];
}
}
// Strings Section
info.strings = {};
l = i + h.strCount * 2;
o = 0;
for (; i < l; i += 2) {
v = Tput.strings[o++];
if (data[i + 1] === 0377 && data[i] === 0377) {
info.strings[v] = -1;
} else {
info.strings[v] = (data[i + 1] << 8) | data[i];
}
}
// String Table
Object.keys(info.strings).forEach(function(key) {
if (info.strings[key] === -1) {
delete info.strings[key];
return;
}
// Workaround: fix an odd bug in the screen-256color terminfo where it tries
// to set -1, but it appears to have {0xfe, 0xff} instead of {0xff, 0xff}.
// TODO: Possibly handle errors gracefully below, as well as in the
// extended info. Also possibly do: `if (info.strings[key] >= data.length)`.
if (info.strings[key] === 65534) {
delete info.strings[key];
return;
}
var s = i + info.strings[key]
, j = s;
while (data[j]) j++;
assert(j < data.length);
info.strings[key] = data.toString('ascii', s, j);
});
// Extended Header
if (this.extended) {
i--;
i += h.strTableSize;
if (i % 2) {
assert.equal(data[i], 0);
i++;
}
l = data.length;
if (i < l - 1) {
var extended = this.parseExtended(data.slice(i));
info.header.extended = extended.header;
['bools', 'numbers', 'strings'].forEach(function(key) {
merge(info[key], extended[key]);
});
}
}
return info;
};
/**
* Extended Parsing
*/
// Some data to help understand:
// For xterm, non-extended header:
// { dataSize: 3270,
// headerSize: 12,
// magicNumber: 282,
// namesSize: 48,
// boolCount: 38,
// numCount: 15,
// strCount: 413,
// strTableSize: 1388,
// total: 2342 }
// For xterm, header:
// Offset: 2342
// { header:
// { dataSize: 928,
// headerSize: 10,
// boolCount: 2,
// numCount: 1,
// strCount: 57,
// strTableSize: 117,
// lastStrTableOffset: 680,
// total: 245 },
// For xterm, layout:
// { header: '0 - 10', // length: 10
// bools: '10 - 12', // length: 2
// numbers: '12 - 14', // length: 2
// strings: '14 - 128', // length: 114 (57 short)
// symoffsets: '128 - 248', // length: 120 (60 short)
// stringtable: '248 - 612', // length: 364
// sym: '612 - 928' } // length: 316
//
// How lastStrTableOffset works:
// data.length - h.lastStrTableOffset === 248
// (sym-offset end, string-table start)
// 364 + 316 === 680 (lastStrTableOffset)
// How strTableSize works:
// h.strCount + [symOffsetCount] === h.strTableSize
// 57 + 60 === 117 (strTableSize)
// symOffsetCount doesn't actually exist in the header. it's just implied.
// Getting the number of sym offsets:
// h.symOffsetCount = h.strTableSize - h.strCount;
// h.symOffsetSize = (h.strTableSize - h.strCount) * 2;
Tput.prototype.parseExtended = function(data) {
var info = {}
, l = data.length
, i = 0;
var h = info.header = {
dataSize: data.length,
headerSize: 10,
boolCount: (data[i + 1] << 8) | data[i + 0],
numCount: (data[i + 3] << 8) | data[i + 2],
strCount: (data[i + 5] << 8) | data[i + 4],
strTableSize: (data[i + 7] << 8) | data[i + 6],
lastStrTableOffset: (data[i + 9] << 8) | data[i + 8]
};
// h.symOffsetCount = h.strTableSize - h.strCount;
h.total = h.headerSize
+ h.boolCount
+ h.numCount * 2
+ h.strCount * 2
+ h.strTableSize;
i += h.headerSize;
// Booleans Section
// One byte for each flag
var _bools = [];
l = i + h.boolCount;
for (; i < l; i++) {
_bools.push(data[i] === 1);
}
// Null byte in between to make sure numbers begin on an even byte.
if (i % 2) {
assert.equal(data[i], 0);
i++;
}
// Numbers Section
var _numbers = [];
l = i + h.numCount * 2;
for (; i < l; i += 2) {
if (data[i + 1] === 0377 && data[i] === 0377) {
_numbers.push(-1);
} else {
_numbers.push((data[i + 1] << 8) | data[i]);
}
}
// Strings Section
var _strings = [];
l = i + h.strCount * 2;
for (; i < l; i += 2) {
if (data[i + 1] === 0377 && data[i] === 0377) {
_strings.push(-1);
} else {
_strings.push((data[i + 1] << 8) | data[i]);
}
}
// Pass over the sym offsets and get to the string table.
i = data.length - h.lastStrTableOffset;
// Might be better to do this instead if the file has trailing bytes:
// i += h.symOffsetCount * 2;
// String Table
var high = 0;
_strings.forEach(function(offset, k) {
if (offset === -1) {
_strings[k] = '';
return;
}
var s = i + offset
, j = s;
while (data[j]) j++;
assert(j < data.length);
// Find out where the string table ends by
// getting the highest string length.
if (high < j - i) {
high = j - i;
}
_strings[k] = data.toString('ascii', s, j);
});
// Symbol Table
// Add one to the highest string length because we didn't count \0.
i += high + 1;
l = data.length;
var sym = []
, j;
for (; i < l; i++) {
j = i;
while (data[j]) j++;
sym.push(data.toString('ascii', i, j));
i = j;
}
// Identify by name
j = 0;
info.bools = {};
_bools.forEach(function(bool) {
info.bools[sym[j++]] = bool;
});
info.numbers = {};
_numbers.forEach(function(number) {
info.numbers[sym[j++]] = number;
});
info.strings = {};
_strings.forEach(function(string) {
info.strings[sym[j++]] = string;
});
// Should be the very last bit of data.
assert.equal(i, data.length);
return info;
};
Tput.prototype.compileTerminfo = function(info, inject) {
if (arguments.length === 0) {
info = this.readTerminfo();
}
return this.compile(info, inject);
};
/**
* Compiler - terminfo cap->javascript
*/
Tput.prototype.compile = function(info, inject) {
var self = this;
if (!info) {
throw new Error('Terminal not found.');
}
if (this.debug) {
console.log(info);
}
info.all = {};
info.methods = {};
['bools', 'numbers', 'strings'].forEach(function(type) {
Object.keys(info[type]).forEach(function(key) {
info.all[key] = info[type][key];
if (self.debug) {
console.log('Compiling %s: %s', key, JSON.stringify(info.all[key]));
}
info.methods[key] = self._compile(info.all[key], key);
});
});
Tput.bools.forEach(function(key) {
if (info.methods[key] == null) info.methods[key] = false;
});
Tput.numbers.forEach(function(key) {
if (info.methods[key] == null) info.methods[key] = -1;
});
Tput.strings.forEach(function(key) {
if (!info.methods[key]) info.methods[key] = noop;
});
Object.keys(info.methods).forEach(function(key) {
var alias = Tput.alias[key];
if (!alias) return;
if (!Array.isArray(alias)) alias = [alias];
alias.forEach(function(alias) {
if (info.methods[alias] && alias !== key && alias !== 'ML') {
// We can ignore `lines` and `ML` here.
throw new Error('Alias collision: ' + key + ' -> ' + alias);
}
info.methods[alias] = info.methods[key];
});
});
if (inject !== false) {
this.inject(info);
}
return info;
};
Tput.prototype.inject = function(info) {
var self = this
, methods = info.methods || info;
Object.keys(methods).forEach(function(key) {
if (typeof methods[key] !== 'function') {
self[key] = methods[key];
return;
}
self[key] = function() {
var args = Array.prototype.slice.call(arguments);
return methods[key].call(self, args);
};
});
this.info = info;
this.all = info.all;
this.methods = info.methods;
this.bools = info.bools;
this.numbers = info.numbers;
this.strings = info.strings;
};
Tput.prototype._compile = function(val, key) {
var self = this
, _val = val
, _key = key;
switch (typeof val) {
case 'boolean':
return val;
case 'number':
return val;
case 'string':
break;
default:
return noop;
}
if (!val) {
return noop;
}
var code = 'var v, dyn = {}, stat = {}, stack = [], out = "";'
, buff = ''
, cap;
var ch
, op
, i
, v
var fi
, then
, els
, end;
function clear() {
if (buff) {
echo(JSON.stringify(buff).replace(/\\u001b/g, '\\x1b'));
buff = '';
}
}
function read(regex) {
cap = regex.exec(val);
if (!cap) return;
val = val.substring(cap[0].length);
ch = op = i = v = cap[1];
clear();
return cap;
}
function stmt(c) {
if (code[code.length-1] === ',') {
code = code.slice(0, -1);
}
code += c;
}
function expr(c) {
code += c + ',';
}
function echo(c) {
expr('out += (' + c + ')');
}
function print(c) {
echo('"' + c + '"');
}
function push(c) {
expr('(stack.push(v = ' + c + '), v)');
}
function pop(c) {
expr((c ? c + ' = ' : '') + 'stack.pop()');
}
while (val) {
// '\e' -> ^[
if (read(/^\\e/i)) {
print('\\x1b');
continue;
}
// '^A' -> ^A
if (read(/^\^(.)/i)) {
ch = ch.toUpperCase(); // ?
switch (ch) {
case '@':
ch = '\\x00';
break;
case '[':
ch = '\\x1b';
break;
case '\\':
ch = '\\x1c';
break;
case ']':
ch = '\\x1d';
break;
case '^':
ch = '\\x1e';
break;
case '_':
ch = '\\x1f';
break;
case '?':
ch = '\\x7f';
break;
default:
if (ch >= 'A' && ch <= 'Z') {
ch = ch.charCodeAt(0) - 64;
ch = ch.toString(16);
if (ch.length < 2) ch = '0' + ch;
ch = '\\x' + ch;
} else {
ch = cap[0];
}
break;
}
print(ch);
continue;
}
// '\n' -> \n
// '\r' -> \r
// '\0' -> \200 (special case)
if (read(/^\\([nlrtbfs\^\\,:0])/)) {
switch (ch) {
case 'n':
ch = '\\n';
break;
case 'l':
ch = '\\x85';
break;
case 'r':
ch = '\\r';
break;
case 't':
ch = '\\t';
break;
case 'b':
ch = '\\x08';
break;
case 'f':
ch = '\\x0c';
break;
case 's':
ch = ' ';
break;
case '^':
ch = '^';
break;
case '\\':
ch = '\\\\';
break;
case ',':
ch = ',';
break;
case ':':
ch = ':';
break;
case '0':
//ch = '\\0';
ch = '\\200';
break;
//case 'v':
// ch = '\\x0b';
// break;
//case 'a':
// ch = '\\x07':
// break;
default:
ch = '';
break;
}
print(ch);
continue;
}
// 3 octal digits -> character
if (read(/^\\(\d{3})/)) {
print('\\' + ch);
continue;
}
// $<5> -> padding
// e.g. flash_screen: '\u001b[?5h$<100/>\u001b[?5l',
if (read(/^\$<(\d+)([*\/]{0,2})>/)) {
if (this.padding) print(cap[0]);
continue;
}
// %% outputs `%'
if (read(/^%%/)) {
print('%');
continue;
}
// %[[:]flags][width[.precision]][doxXs]
// as in printf, flags are [-+#] and space. Use a `:' to allow the
// next character to be a `-' flag, avoiding interpreting "%-" as an
// operator.
// %c print pop() like %c in printf
// Example from screen terminfo:
// S0: "\u001b(%p1%c"
// %d print pop()
// "Print (e.g., "%d") is a special case."
// %s print pop() like %s in printf
if (read(/^%((?::-|[+# ]){1,4})?(\d+(?:\.\d+)?)?([doxXs])/)) {
if (this.printf) {
echo('sprintf("'+ cap[0].replace(':-', '-') + '", stack.pop())');
} else {
echo('stack.pop()');
}
continue;
}
// %c print pop() like %c in printf
// Example from screen terminfo:
// S0: "\u001b(%p1%c"
if (read(/^%c/)) {
if (this.printf) {
echo('sprintf("%c", stack.pop())');
} else {
echo('(v = stack.pop() || "",'
+ ' isFinite(v) ? String.fromCharCode(v) : v[0])');
}
continue;
}
// %d print pop()
// "Print (e.g., "%d") is a special case."
// if (read(/^%d/)) {
// echo('stack.pop()');
// continue;
// }
// %s print pop() like %s in printf
// if (read(/^%s/)) {
// echo('stack.pop()');
// continue;
// }
// %p[1-9]
// push i'th parameter
if (read(/^%p([1-9])/)) {
expr('(stack.push(v = params[' + (i - 1) + ']), v)');
continue;
}
// %P[a-z]
// set dynamic variable [a-z] to pop()
if (read(/^%P([a-z])/)) {
expr('dyn.' + v + ' = stack.pop()');
continue;
}
// %g[a-z]
// get dynamic variable [a-z] and push it
if (read(/^%g([a-z])/)) {
expr('(stack.push(dyn.' + v + '), dyn.' + v + ')');
continue;
}
// %P[A-Z]
// set static variable [a-z] to pop()
if (read(/^%P([A-Z])/)) {
expr('stat.' + v + ' = stack.pop()');
continue;
}
// %g[A-Z]
// get static variable [a-z] and push it
// The terms "static" and "dynamic" are misleading. Historically,
// these are simply two different sets of variables, whose values are
// not reset between calls to tparm. However, that fact is not
// documented in other implementations. Relying on it will adversely
// impact portability to other implementations.
if (read(/^%g([A-Z])/)) {
expr('(stack.push(v = stat.' + v + '), v)');
continue;
}
// %'c' char constant c
if (read(/^%'(\w)'/)) {
expr('(stack.push(v = "' + ch + '", v)');
continue;
}
// %{nn}
// integer constant nn
if (read(/^%\{(\d+)\}/)) {
expr('(stack.push(v = ' + ch + '), v)');
continue;
}
// %l push strlen(pop)
if (read(/^%l/)) {
expr('(stack.push(v = stack.pop().length), v)');
continue;
}
// %+ %- %* %/ %m
// arithmetic (%m is mod): push(pop() op pop())
// %& %| %^
// bit operations (AND, OR and exclusive-OR): push(pop() op pop())
// %= %> %<
// logical operations: push(pop() op pop())
if (read(/^%([+\-*\/m&|\^=><])/)) {
if (op === '=') op = '===';
else if (op === 'm') op = '%';
expr('(stack.push(v = (stack.pop() ' + op + ' stack.pop())), v)');
continue;
}
// %A, %O
// logical AND and OR operations (for conditionals)
if (read(/^%([AO])/)) {
stmt(op === ' A ' ? ' && ' : ' || ');
continue;
}
// %! %~
// unary operations (logical and bit complement): push(op pop())
if (read(/^%([!~])/)) {
expr('(stack.push(v = ' + op + 'stack.pop()), v)');
continue;
}
// %i add 1 to first two parameters (for ANSI terminals)
if (read(/^%i/)) {
expr('(params[0]++, params[1]++)');
continue;
}
// %? expr %t thenpart %e elsepart %;
// This forms an if-then-else. The %e elsepart is optional. Usually
// the %? expr part pushes a value onto the stack, and %t pops it from
// the stack, testing if it is nonzero (true). If it is zero (false),
// control passes to the %e (else) part.
// It is possible to form else-if's a la Algol 68:
// %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e %;
// where ci are conditions, bi are bodies.
// Use the -f option of tic or infocmp to see the structure of
// if-then-else's. Some strings, e.g., sgr can be very complicated when
// written on one line. The -f option splits the string into lines with
// the parts indented.
if (read(/^%\?/)) {
stmt(';if (');
continue;
}
if (read(/^%t/)) {
stmt(') {');
continue;
}
// if/then/else/end
// Terminfo does elseif's like
// this: %?[expr]%t...%e[expr]%t...%;
if (read(/^%e/)) {
fi = val.indexOf('%?');
then = val.indexOf('%t');
els = val.indexOf('%e');
end = val.indexOf('%;');
if (then !== -1 && then < end
&& (fi === -1 || then < fi)
&& (els === -1 || then < els)) {
stmt('} else if (');
} else {
stmt('} else {');
}
continue;
}
if (read(/^%;/)) {
stmt('}');
continue;
}
// Binary operations are in postfix form with the operands in the usual
// order. That is, to get x-5 one would use "%gx%{5}%-". %P and %g vari
// ables are persistent across escape-string evaluations.
// Consider the HP2645, which, to get to row 3 and column 12, needs to be
// sent \E&a12c03Y padded for 6 milliseconds. Note that the order of the
// rows and columns is inverted here, and that the row and column are
// printed as two digits. Thus its cup capability is
// “cup=6\E&%p2%2dc%p1%2dY”.
// The Microterm ACT-IV needs the current row and column sent
// preceded by a ^T, with the row and column simply encoded in
// binary, “cup=^T%p1%c%p2%c”. Terminals which use “%c” need to be able
// to backspace the cursor (cub1), and to move the cursor up one line
// on the screen (cuu1). This is necessary because it is not always safe
// to transmit \n ^D and \r, as the system may change or discard them.
// (The library routines dealing with terminfo set tty modes so that tabs
// are never expanded, so \t is safe to send. This turns out to be
// essential for the Ann Arbor 4080.)
// A final example is the LSI ADM-3a, which uses row and column offset
// by a blank character, thus “cup=\E=%p1%' '%+%c%p2%' '%+%c”. After
// sending `\E=', this pushes the first parameter, pushes the ASCII value
// for a space (32), adds them (pushing the sum on the stack in place of
// the two previous values) and outputs that value as a character.
// Then the same is done for the second parameter. More complex
// arithmetic is possible using the stack.
// $ man termcap
if (this.termcap) {
// %r Single parameter capability
if (read(/^%r/)) {
continue;
}
// %+ Add value of next character to this parameter and do binary output
// if (read(/^%\+/)) {
// echo('stack.pop()');
// continue;
// }
// %2 Do ASCII output of this parameter with a field with of 2
if (read(/^%2/)) {
echo('stack.pop()');
continue;
}
// %d Do ASCII output of this parameter with a field with of 3
// if (read(/^%d/)) {
// echo('stack.pop()');
// continue;
// }
}
buff += val[0];
val = val.substring(1);
}
clear();
stmt(';return out;');
// TODO: Make this less ridiculous.
// Optimize
// ... out += ("foo");return out.join("");
// To:
// return "foo";
v = code.split(/out = "";out \+= \(|\);return out/);
if (v.length === 3 && /^"[^"]+"$/.test(v[1])) {
code = 'return ' + v[1] + ';';
} else if (~code.indexOf('out = "";;return')) {
code = 'return "";';
}
if (this.debug) {
v = code
.replace(/\x1b/g, '\\x1b')
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n');
process.stdout.write(v + '\n');
}
// Possibly do:
// if (code.indexOf('return ') === 0) {
// return new Function('', code)();
// }
try {
return this.printf
? new Function('sprintf, params', code).bind(null, sprintf)
: new Function('params', code);
} catch (e) {
console.error('');
console.error('Error on `' + _key + '`:');
console.error(JSON.stringify(_val));
console.error('');
console.error(code.replace(/(,|;)/g, '$1\n'));
e.stack = e.stack.replace(/\x1b/g, '\\x1b');
throw e;
}
};
Tput.prototype._print =
Tput.prototype._parsePadding = function(code, print, done) {
var print = print || write
, done = done || noop;
if (!this.padding) {
print(code);
return done();
}
var parts = code.split(/(?=\$<\d+[*\/]{0,2}>)/)
, i = 0;
(function next() {
if (i === parts.length) {
return done();
}
var part = parts[i++]
, padding = /^\$<(\d+)([*\/]{0,2})>/.exec(part);
if (!padding) {
print(part);
return next();
}
part = part.substring(padding[0].length);
// A `*' indicates that the padding required is proportional to the number
// of lines affected by the operation, and the amount given is the
// per-affected-unit padding required. (In the case of insert character,
// the factor is still the number of lines affected.) Normally, padding is
// advisory if the device has the xon capability; it is used for cost
// computation but does not trigger delays.
if (~padding[2].indexOf('*')) {
;
}
// A `/' suffix indicates that the padding is mandatory and forces a
// delay of the given number of milliseconds even on devices for which xon
// is present to indicate flow control.
if (~padding[2].indexOf('/')) {
;
}
return setTimeout(function() {
print(part);
return next();
}, +padding[1]);
})();
};
/**
* Termcap
*/
Tput.prototype.readTermcap = function(data) {
var self = this
, TERMCAP = process.env.TERMCAP || ''
, TERMPATH = process.env.TERMPATH || ''
, HOME = process.env.HOME || '';
// TODO: Check to make sure termcap data matches
// the term name, otherwise keep searching.
var data = data
|| tryRead(this.termcapFile)
|| (TERMCAP[0] === '/' ? tryRead(TERMCAP) : TERMCAP)
|| (TERMPATH && tryRead(TERMPATH.split(/[: ]/)))
|| tryRead(HOME + '/.termcap')
|| tryRead('/usr/share/misc/termcap')
|| tryRead('/etc/termcap')
|| Tput.termcap;
var terms = this.parseTermcap(data)
, root = terms[this.terminal];
if (this.debug) {
this._termcap = terms;
}
(function tc(term) {
if (term && term.strings.tc) {
root.inherits = root.inherits || [];
root.inherits.push(term.strings.tc);
if (self.debug) {
var names = terms[term.strings.tc]
? terms[term.strings.tc].names
: [term.strings.tc];
console.log(term.names.join('/') + ' inherits from ' + names.join('/'));
}
var inherit = tc(terms[term.strings.tc]);
if (inherit) {
['bools', 'numbers', 'strings'].forEach(function(type) {
merge(term[type], inherit[type]);
});
}
}
return term;
})(root);
return root;
};
/**
* Termcap Parser
* http://en.wikipedia.org/wiki/Termcap
* http://www.gnu.org/software
* /termutils/manual/termcap-1.3/html_mono/termcap.html
* http://www.gnu.org/software
* /termutils/manual/termcap-1.3/html_mono/termcap.html#SEC17
* http://tldp.org/HOWTO/Text-Terminal-HOWTO.html#toc16
* man termcap
*/
// Example:
// vt102|dec vt102:\
// :do=^J:co#80:li#24:cl=50\E[;H\E[2J:\
// :le=^H:bs:cm=5\E[%i%d;%dH:nd=2\E[C:up=2\E[A:\
// :ce=3\E[K:cd=50\E[J:so=2\E[7m:se=2\E[m:us=2\E[4m:ue=2\E[m:\
// :md=2\E[1m:mr=2\E[7m:mb=2\E[5m:me=2\E[m:is=\E[1;24r\E[24;1H:\
// :rs=\E>\E[?3l\E[?4l\E[?5l\E[?7h\E[?8h:ks=\E[?1h\E=:ke=\E[?1l\E>:\
// :ku=\EOA:kd=\EOB:kr=\EOC:kl=\EOD:kb=^H:\
// :ho=\E[H:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:pt:sr=5\EM:vt#3:\
// :sc=\E7:rc=\E8:cs=\E[%i%d;%dr:vs=\E[?7l:ve=\E[?7h:\
// :mi:al=\E[L:dc=\E[P:dl=\E[M:ei=\E[4l:im=\E[4h:
Tput.prototype.parseTermcap = function(data) {
var terms = {}
, term
, entries
, fields
, field
, names
, i
, j
, k;
// remove escaped newlines
data = data.replace(/\\\n[ \t]*/g, '');
// remove comments
data = data.replace(/^#[^\n]+/gm, '');
// split entries
entries = data.trim().split(/\n+/);
for (i = 0; i < entries.length; i++) {
fields = entries[i].split(/:+/);
for (j = 0; j < fields.length; j++) {
field = fields[j].trim();
if (!field) continue;
if (j === 0) {
names = field.split('|');
term = {
name: names[0],
names: names,
desc: names.pop()
};
for (k = 0; k < names.length; k++) {
terms[names[k]] = term;
}
term.bools = {};
term.numbers = {};
term.strings = {};
continue;
}
if (~field.indexOf('=')) {
parts = field.split('=');
term.strings[parts[0]] = parts.slice(1).join('=');
} else if (~field.indexOf('#')) {
parts = field.split('#');
term.numbers[parts[0]] = +parts.slice(1).join('#');
} else {
term.bools[field] = true;
}
}
}
return terms;
};
/**
* Termcap Compiler
* man termcap
*/
Tput.prototype.translateTermcap = function(info) {
var self = this
, out = {};
if (!info) return;
if (this.debug) {
console.log(info);
}
['name', 'names', 'desc'].forEach(function(key) {
out[key] = info[key];
});
// Separate aliases for termcap
var talias = (function() {
var alias = Tput.alias
, out = {};
Object.keys(alias).forEach(function(key) {
var a = alias[key]
, n = a.slice();
if (!a._termcapRemoved) {
n.splice(1, 1);
}
out[a.termcap] = [key].concat(n);
});
return out;
})();
// Translate termcap cap names to terminfo cap names.
// e.g. `up` -> `cursor_up`
['bools', 'numbers', 'strings'].forEach(function(key) {
out[key] = {};
Object.keys(info[key]).forEach(function(cap) {
if (talias[cap]) {
out[key][talias[cap][0]] = info[key][cap];
} else {
// NOTE: Possibly include all termcap names
// in a separate alias.js file. Some are
// missing from the terminfo alias.js file
// which is why we have to do this:
// See: $ man termcap
out[key][cap] = info[key][cap];
}
});
});
return out;
};
Tput.prototype.compileTermcap = function(info, inject) {
if (arguments.length === 0) {
info = this.readTermcap();
}
// Translate termcap names to terminfo-style names.
info = this.translateTermcap(info);
return this.compile(info, inject);
};
/**
* Detect Features / Quirks
*/
Tput.prototype.detectFeatures = function() {
this.unicode = this.detectUnicode();
this.brokenACS = this.detectBrokenACS();
this.PCRomSet = this.detectPCRomSet();
this.magicCookie = this.detectMagicCookie();
this.padding = this.detectPadding();
this.setbuf = this.detectSetbuf();
this.parseACS();
};
Tput.prototype.detectUnicode = function() {
var LANG = process.env.LANG
+ ':' + process.env.LANGUAGE
+ ':' + process.env.LC_ALL
+ ':' + process.env.LC_CTYPE;
return /utf-?8/i.test(LANG);
};
// For some reason TERM=linux has smacs/rmacs, but it maps to `^[[11m`
// and it does not switch to the DEC SCLD character set. What the hell?
// xterm: \x1b(0, screen: \x0e, linux: \x1b[11m (doesn't work)
// `man console_codes` says:
// 11 select null mapping, set display control flag, reset tog
// gle meta flag (ECMA-48 says "first alternate font").
// See ncurses:
// ~/ncurses/ncurses/base/lib_set_term.c
// ~/ncurses/ncurses/tinfo/lib_acs.c
// ~/ncurses/ncurses/tinfo/tinfo_driver.c
// ~/ncurses/ncurses/tinfo/lib_setup.c
Tput.prototype.detectBrokenACS = function() {
// ncurses-compatible env variable.
if (process.env.NCURSES_NO_UTF8_ACS != null) {
return true;
}
// If the terminal supports unicode, we don't need ACS.
if (this.numbers['U8'] >= 0) {
return !!this.numbers['U8'];
}
// The linux console is just broken for some reason.
if (this.terminal === 'linux') {
return true;
}
// screen termcap is bugged?
if (this.terminal.indexOf('screen') == 0
&& process.env.TERMCAP
&& ~process.env.TERMCAP.indexOf('screen')
&& ~process.env.TERMCAP.indexOf('hhII00')) {
if (~this.strings.enter_alt_charset_mode.indexOf('\016')
|| ~this.strings.enter_alt_charset_mode.indexOf('\017')
|| ~this.strings.set_attributes.indexOf('\016')
|| ~this.strings.set_attributes.indexOf('\017')) {
return true;
}
}
return false;
};
// Apparently the Linux console does not support ACS,
// but it does support the PC ROM character set.
// See: ~/ncurses/ncurses/tinfo/lib_acs.c
Tput.prototype.detectPCRomSet = function() {
var str = this.strings;
if (str.enter_pc_charset_mode === str.enter_alt_charset_mode
&& str.exit_pc_charset_mode === str.exit_alt_charset_mode) {
return true;
}
return false;
};
Tput.prototype.detectMagicCookie = function() {
return process.env.NCURSES_NO_MAGIC_COOKIE == null;
};
Tput.prototype.detectPadding = function() {
return process.env.NCURSES_NO_PADDING == null;
};
Tput.prototype.detectSetbuf = function() {
return process.env.NCURSES_NO_SETBUF == null;
};
Tput.prototype.parseACS = function() {
var self = this;
this.acsc = {};
this.acscr = {};
if (this.PCRomSet) {
;
}
// See: ~/ncurses/ncurses/tinfo/lib_acs.c: L208
Object.keys(Tput.acsc).forEach(function(ch) {
var acs_chars = self.strings.acs_chars || ''
, i = acs_chars.indexOf(ch)
, next = acs_chars[i + 1];
if (!next || i === -1) {
return;
}
self.acsc[ch] = Tput.acsc[next];
self.acscr[Tput.acsc[next]] = ch;
});
};
/**
* Helpers
*/
function noop() {
return '';
}
noop.unsupported = true;
function merge(a, b) {
Object.keys(b).forEach(function(key) {
a[key] = b[key];
});
return a;
}
function write(data) {
return process.stdout.write(data);
}
function tryRead(file) {
if (Array.isArray(file)) {
for (var i = 0; i < file.length; i++) {
var data = tryRead(file[i]);
if (data) return data;
}
return '';
}
if (!file) return '';
file = path.resolve.apply(path, arguments);
try {
return fs.readFileSync(file, 'utf8');
} catch (e) {
return '';
}
}
/**
* sprintf
* http://www.cplusplus.com/reference/cstdio/printf/
*/
function sprintf(src) {
var params = Array.prototype.slice.call(arguments, 1)
, rule = /%([\-+# ]{1,4})?(\d+(?:\.\d+)?)?([doxXsc])/g
, i = 0;
return src.replace(rule, function(_, flag, width, type) {
var flags = (flag || '').split('')
, param = params[i++] || ''
, initial = param
, width = +width
, opt = {}
, pre = '';
switch (type) {
case 'd': // signed int
param = (+param).toString(10);
break;
case 'o': // unsigned octal
param = (+param).toString(8);
break;
case 'x': // unsigned hex int
param = (+param).toString(16);
break;
case 'X': // unsigned hex int uppercase
param = (+param).toString(16).toUppercase();
break;
case 's': // string
break;
case 'c': // char
// Should return here?
param = isFinite(param)
? String.fromCharCode(param)
: param[0];
//: '';
break;
}
flags.forEach(function(flag) {
switch (flag) {
// left-justify by width
case '-':
opt.left = true;
break;
// always precede numbers with their signs
case '+':
opt.signs = true;
break;
// used with o, x, X - value is preceded with 0, 0x, or 0X respectively.
// used with a, A, e, E, f, F, g, G - forces written output to contain
// a decimal point even if no more digits follow
case '#':
opt.hexpoint = true;
break;
// if no sign is going to be written, black space in front of the value
case ' ':
opt.space = true;
break;
}
});
if (opt.signs) {
if (+initial >= 0) {
pre += '+';
}
}
if (opt.space) {
if (!opt.signs && +initial >= 0) {
pre += ' ';
}
}
if (opt.hexpoint) {
switch (type) {
case 'o': // unsigned octal
pre += '0';
break;
case 'x': // unsigned hex int
pre += '0x';
break;
case 'X': // unsigned hex int uppercase
pre += '0X';
break;
}
}
if (opt.left) {
if (width > (pre.length + param.length)) {
width -= pre.length + param.length;
pre = Array(width + 1).join(' ') + pre;
}
}
return pre + param;
});
}
/**
* Aliases
*/
Tput.alias = require('./alias');
// Bools
Tput.alias.bools.no_esc_ctlc.push('beehive_glitch');
Tput.alias.bools.dest_tabs_magic_smso.push('teleray_glitch');
// Numbers
Tput.alias.numbers.micro_col_size.push('micro_char_size');
// Merge into one object
merge(Tput.alias, Tput.alias.bools);
delete Tput.alias.bools;
merge(Tput.alias, Tput.alias.numbers);
delete Tput.alias.numbers;
merge(Tput.alias, Tput.alias.strings);
delete Tput.alias.strings;
// Make sure there are no collisions between terminfo names
// and termcap names. Terminfo takes precedence.
Tput._vec = Object.keys(Tput.alias).map(function(key) {
return Array.isArray(Tput.alias[key])
? Tput.alias[key][0]
: Tput.alias[key];
});
Object.keys(Tput.alias).forEach(function(key) {
if (!Array.isArray(Tput.alias[key])) {
return;
}
var alias = Tput.alias[key]
, tcap = alias[1];
alias.name = key;
alias.terminfo = alias[0];
alias.termcap = alias[1];
if (!tcap) return;
if (~Tput._vec.indexOf(tcap)) {
alias._termcapRemoved = true;
alias.splice(1, 1);
}
});
assert.notEqual(Tput.alias.exit_delete_mode[1], 'ed');
assert.equal(Tput.alias.exit_delete_mode.termcap, 'ed');
// assert.notEqual(Tput.alias.lines[0], 'lines');
// assert.notEqual(Tput.alias.set_lr_margin[1], 'ML');
// Possibly just ignore certain aliases like so:
// Tput.alias.exit_delete_mode.ignore = 1;
// Tput.alias.lines.ignore = 0;
// Tput.alias.set_lr_margin.ignore = 1;
/**
* Feature Checking
*/
Tput.aliasMap = {};
Object.keys(Tput.alias).forEach(function(key) {
var aliases = Array.isArray(Tput.alias[key])
? Tput.alias[key]
: [Tput.alias[key]];
Tput.aliasMap[key] = key;
aliases.forEach(function(k) {
Tput.aliasMap[k] = key;
});
});
Tput.prototype.has = function(name) {
var name = Tput.aliasMap[name]
, val = this.all[name];
if (!name) return false;
if (typeof val === 'number') {
return val !== -1;
}
return !!val;
};
/**
* Fallback Termcap Entry
*/
Tput.termcap = ''
+ 'vt102|dec vt102:'
+ ':do=^J:co#80:li#24:cl=50\\E[;H\\E[2J:'
+ ':le=^H:bs:cm=5\\E[%i%d;%dH:nd=2\\E[C:up=2\\E[A:'
+ ':ce=3\\E[K:cd=50\\E[J:so=2\\E[7m:se=2\\E[m:us=2\\E[4m:ue=2\\E[m:'
+ ':md=2\\E[1m:mr=2\\E[7m:mb=2\\E[5m:me=2\\E[m:is=\\E[1;24r\\E[24;1H:'
+ ':rs=\\E>\\E[?3l\\E[?4l\\E[?5l\\E[?7h\\E[?8h:ks=\\E[?1h\\E=:ke=\\E[?1l\\E>:'
+ ':ku=\\EOA:kd=\\EOB:kr=\\EOC:kl=\\EOD:kb=^H:\\\n'
+ ':ho=\\E[H:k1=\\EOP:k2=\\EOQ:k3=\\EOR:k4=\\EOS:pt:sr=5\\EM:vt#3:'
+ ':sc=\\E7:rc=\\E8:cs=\\E[%i%d;%dr:vs=\\E[?7l:ve=\\E[?7h:'
+ ':mi:al=\\E[L:dc=\\E[P:dl=\\E[M:ei=\\E[4l:im=\\E[4h:';
/**
* Terminfo Data
*/
Tput.bools = [
'auto_left_margin',
'auto_right_margin',
'no_esc_ctlc',
'ceol_standout_glitch',
'eat_newline_glitch',
'erase_overstrike',
'generic_type',
'hard_copy',
'has_meta_key',
'has_status_line',
'insert_null_glitch',
'memory_above',
'memory_below',
'move_insert_mode',
'move_standout_mode',
'over_strike',
'status_line_esc_ok',
'dest_tabs_magic_smso',
'tilde_glitch',
'transparent_underline',
'xon_xoff',
'needs_xon_xoff',
'prtr_silent',
'hard_cursor',
'non_rev_rmcup',
'no_pad_char',
'non_dest_scroll_region',
'can_change',
'back_color_erase',
'hue_lightness_saturation',
'col_addr_glitch',
'cr_cancels_micro_mode',
'has_print_wheel',
'row_addr_glitch',
'semi_auto_right_margin',
'cpi_changes_res',
'lpi_changes_res',
// #ifdef __INTERNAL_CAPS_VISIBLE
'backspaces_with_bs',
'crt_no_scrolling',
'no_correctly_working_cr',
'gnu_has_meta_key',
'linefeed_is_newline',
'has_hardware_tabs',
'return_does_clr_eol'
];
Tput.numbers = [
'columns',
'init_tabs',
'lines',
'lines_of_memory',
'magic_cookie_glitch',
'padding_baud_rate',
'virtual_terminal',
'width_status_line',
'num_labels',
'label_height',
'label_width',
'max_attributes',
'maximum_windows',
'max_colors',
'max_pairs',
'no_color_video',
'buffer_capacity',
'dot_vert_spacing',
'dot_horz_spacing',
'max_micro_address',
'max_micro_jump',
'micro_col_size',
'micro_line_size',
'number_of_pins',
'output_res_char',
'output_res_line',
'output_res_horz_inch',
'output_res_vert_inch',
'print_rate',
'wide_char_size',
'buttons',
'bit_image_entwining',
'bit_image_type',
// #ifdef __INTERNAL_CAPS_VISIBLE
'magic_cookie_glitch_ul',
'carriage_return_delay',
'new_line_delay',
'backspace_delay',
'horizontal_tab_delay',
'number_of_function_keys'
];
Tput.strings = [
'back_tab',
'bell',
'carriage_return',
'change_scroll_region',
'clear_all_tabs',
'clear_screen',
'clr_eol',
'clr_eos',
'column_address',
'command_character',
'cursor_address',
'cursor_down',
'cursor_home',
'cursor_invisible',
'cursor_left',
'cursor_mem_address',
'cursor_normal',
'cursor_right',
'cursor_to_ll',
'cursor_up',
'cursor_visible',
'delete_character',
'delete_line',
'dis_status_line',
'down_half_line',
'enter_alt_charset_mode',
'enter_blink_mode',
'enter_bold_mode',
'enter_ca_mode',
'enter_delete_mode',
'enter_dim_mode',
'enter_insert_mode',
'enter_secure_mode',
'enter_protected_mode',
'enter_reverse_mode',
'enter_standout_mode',
'enter_underline_mode',
'erase_chars',
'exit_alt_charset_mode',
'exit_attribute_mode',
'exit_ca_mode',
'exit_delete_mode',
'exit_insert_mode',
'exit_standout_mode',
'exit_underline_mode',
'flash_screen',
'form_feed',
'from_status_line',
'init_1string',
'init_2string',
'init_3string',
'init_file',
'insert_character',
'insert_line',
'insert_padding',
'key_backspace',
'key_catab',
'key_clear',
'key_ctab',
'key_dc',
'key_dl',
'key_down',
'key_eic',
'key_eol',
'key_eos',
'key_f0',
'key_f1',
'key_f10',
'key_f2',
'key_f3',
'key_f4',
'key_f5',
'key_f6',
'key_f7',
'key_f8',
'key_f9',
'key_home',
'key_ic',
'key_il',
'key_left',
'key_ll',
'key_npage',
'key_ppage',
'key_right',
'key_sf',
'key_sr',
'key_stab',
'key_up',
'keypad_local',
'keypad_xmit',
'lab_f0',
'lab_f1',
'lab_f10',
'lab_f2',
'lab_f3',
'lab_f4',
'lab_f5',
'lab_f6',
'lab_f7',
'lab_f8',
'lab_f9',
'meta_off',
'meta_on',
'newline',
'pad_char',
'parm_dch',
'parm_delete_line',
'parm_down_cursor',
'parm_ich',
'parm_index',
'parm_insert_line',
'parm_left_cursor',
'parm_right_cursor',
'parm_rindex',
'parm_up_cursor',
'pkey_key',
'pkey_local',
'pkey_xmit',
'print_screen',
'prtr_off',
'prtr_on',
'repeat_char',
'reset_1string',
'reset_2string',
'reset_3string',
'reset_file',
'restore_cursor',
'row_address',
'save_cursor',
'scroll_forward',
'scroll_reverse',
'set_attributes',
'set_tab',
'set_window',
'tab',
'to_status_line',
'underline_char',
'up_half_line',
'init_prog',
'key_a1',
'key_a3',
'key_b2',
'key_c1',
'key_c3',
'prtr_non',
'char_padding',
'acs_chars',
'plab_norm',
'key_btab',
'enter_xon_mode',
'exit_xon_mode',
'enter_am_mode',
'exit_am_mode',
'xon_character',
'xoff_character',
'ena_acs',
'label_on',
'label_off',
'key_beg',
'key_cancel',
'key_close',
'key_command',
'key_copy',
'key_create',
'key_end',
'key_enter',
'key_exit',
'key_find',
'key_help',
'key_mark',
'key_message',
'key_move',
'key_next',
'key_open',
'key_options',
'key_previous',
'key_print',
'key_redo',
'key_reference',
'key_refresh',
'key_replace',
'key_restart',
'key_resume',
'key_save',
'key_suspend',
'key_undo',
'key_sbeg',
'key_scancel',
'key_scommand',
'key_scopy',
'key_screate',
'key_sdc',
'key_sdl',
'key_select',
'key_send',
'key_seol',
'key_sexit',
'key_sfind',
'key_shelp',
'key_shome',
'key_sic',
'key_sleft',
'key_smessage',
'key_smove',
'key_snext',
'key_soptions',
'key_sprevious',
'key_sprint',
'key_sredo',
'key_sreplace',
'key_sright',
'key_srsume',
'key_ssave',
'key_ssuspend',
'key_sundo',
'req_for_input',
'key_f11',
'key_f12',
'key_f13',
'key_f14',
'key_f15',
'key_f16',
'key_f17',
'key_f18',
'key_f19',
'key_f20',
'key_f21',
'key_f22',
'key_f23',
'key_f24',
'key_f25',
'key_f26',
'key_f27',
'key_f28',
'key_f29',
'key_f30',
'key_f31',
'key_f32',
'key_f33',
'key_f34',
'key_f35',
'key_f36',
'key_f37',
'key_f38',
'key_f39',
'key_f40',
'key_f41',
'key_f42',
'key_f43',
'key_f44',
'key_f45',
'key_f46',
'key_f47',
'key_f48',
'key_f49',
'key_f50',
'key_f51',
'key_f52',
'key_f53',
'key_f54',
'key_f55',
'key_f56',
'key_f57',
'key_f58',
'key_f59',
'key_f60',
'key_f61',
'key_f62',
'key_f63',
'clr_bol',
'clear_margins',
'set_left_margin',
'set_right_margin',
'label_format',
'set_clock',
'display_clock',
'remove_clock',
'create_window',
'goto_window',
'hangup',
'dial_phone',
'quick_dial',
'tone',
'pulse',
'flash_hook',
'fixed_pause',
'wait_tone',
'user0',
'user1',
'user2',
'user3',
'user4',
'user5',
'user6',
'user7',
'user8',
'user9',
'orig_pair',
'orig_colors',
'initialize_color',
'initialize_pair',
'set_color_pair',
'set_foreground',
'set_background',
'change_char_pitch',
'change_line_pitch',
'change_res_horz',
'change_res_vert',
'define_char',
'enter_doublewide_mode',
'enter_draft_quality',
'enter_italics_mode',
'enter_leftward_mode',
'enter_micro_mode',
'enter_near_letter_quality',
'enter_normal_quality',
'enter_shadow_mode',
'enter_subscript_mode',
'enter_superscript_mode',
'enter_upward_mode',
'exit_doublewide_mode',
'exit_italics_mode',
'exit_leftward_mode',
'exit_micro_mode',
'exit_shadow_mode',
'exit_subscript_mode',
'exit_superscript_mode',
'exit_upward_mode',
'micro_column_address',
'micro_down',
'micro_left',
'micro_right',
'micro_row_address',
'micro_up',
'order_of_pins',
'parm_down_micro',
'parm_left_micro',
'parm_right_micro',
'parm_up_micro',
'select_char_set',
'set_bottom_margin',
'set_bottom_margin_parm',
'set_left_margin_parm',
'set_right_margin_parm',
'set_top_margin',
'set_top_margin_parm',
'start_bit_image',
'start_char_set_def',
'stop_bit_image',
'stop_char_set_def',
'subscript_characters',
'superscript_characters',
'these_cause_cr',
'zero_motion',
'char_set_names',
'key_mouse',
'mouse_info',
'req_mouse_pos',
'get_mouse',
'set_a_foreground',
'set_a_background',
'pkey_plab',
'device_type',
'code_set_init',
'set0_des_seq',
'set1_des_seq',
'set2_des_seq',
'set3_des_seq',
'set_lr_margin',
'set_tb_margin',
'bit_image_repeat',
'bit_image_newline',
'bit_image_carriage_return',
'color_names',
'define_bit_image_region',
'end_bit_image_region',
'set_color_band',
'set_page_length',
'display_pc_char',
'enter_pc_charset_mode',
'exit_pc_charset_mode',
'enter_scancode_mode',
'exit_scancode_mode',
'pc_term_options',
'scancode_escape',
'alt_scancode_esc',
'enter_horizontal_hl_mode',
'enter_left_hl_mode',
'enter_low_hl_mode',
'enter_right_hl_mode',
'enter_top_hl_mode',
'enter_vertical_hl_mode',
'set_a_attributes',
'set_pglen_inch',
// #ifdef __INTERNAL_CAPS_VISIBLE
'termcap_init2',
'termcap_reset',
'linefeed_if_not_lf',
'backspace_if_not_bs',
'other_non_function_keys',
'arrow_key_map',
'acs_ulcorner',
'acs_llcorner',
'acs_urcorner',
'acs_lrcorner',
'acs_ltee',
'acs_rtee',
'acs_btee',
'acs_ttee',
'acs_hline',
'acs_vline',
'acs_plus',
'memory_lock',
'memory_unlock',
'box_chars_1'
];
// DEC Special Character and Line Drawing Set.
// Taken from tty.js.
Tput.acsc = { // (0
'`': '\u25c6', // '◆'
'a': '\u2592', // '▒'
'b': '\u0009', // '\t'
'c': '\u000c', // '\f'
'd': '\u000d', // '\r'
'e': '\u000a', // '\n'
'f': '\u00b0', // '°'
'g': '\u00b1', // '±'
'h': '\u2424', // '\u2424' (NL)
'i': '\u000b', // '\v'
'j': '\u2518', // '┘'
'k': '\u2510', // '┐'
'l': '\u250c', // '┌'
'm': '\u2514', // '└'
'n': '\u253c', // '┼'
'o': '\u23ba', // '⎺'
'p': '\u23bb', // '⎻'
'q': '\u2500', // '─'
'r': '\u23bc', // '⎼'
's': '\u23bd', // '⎽'
't': '\u251c', // '├'
'u': '\u2524', // '┤'
'v': '\u2534', // '┴'
'w': '\u252c', // '┬'
'x': '\u2502', // '│'
'y': '\u2264', // '≤'
'z': '\u2265', // '≥'
'{': '\u03c0', // 'π'
'|': '\u2260', // '≠'
'}': '\u00a3', // '£'
'~': '\u00b7' // '·'
};
// Convert ACS unicode characters to the
// most similar-looking ascii characters.
Tput.utoa = Tput.prototype.utoa = {
'\u25c6': '*', // '◆'
'\u2592': ' ', // '▒'
// '\u0009': '\t', // '\t'
// '\u000c': '\f', // '\f'
// '\u000d': '\r', // '\r'
// '\u000a': '\n', // '\n'
'\u00b0': '*', // '°'
'\u00b1': '+', // '±'
'\u2424': '\n', // '\u2424' (NL)
// '\u000b': '\v', // '\v'
'\u2518': '+', // '┘'
'\u2510': '+', // '┐'
'\u250c': '+', // '┌'
'\u2514': '+', // '└'
'\u253c': '+', // '┼'
'\u23ba': '-', // '⎺'
'\u23bb': '-', // '⎻'
'\u2500': '-', // '─'
'\u23bc': '-', // '⎼'
'\u23bd': '_', // '⎽'
'\u251c': '+', // '├'
'\u2524': '+', // '┤'
'\u2534': '+', // '┴'
'\u252c': '+', // '┬'
'\u2502': '|', // '│'
'\u2264': '<', // '≤'
'\u2265': '>', // '≥'
'\u03c0': '?', // 'π'
'\u2260': '=', // '≠'
'\u00a3': '?', // '£'
'\u00b7': '*' // '·'
};
/**
* Expose
*/
exports = Tput;
exports.sprintf = sprintf;
module.exports = exports;