PageRenderTime 135ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/tput.js

https://github.com/prodigeni/blessed
JavaScript | 2927 lines | 2155 code | 322 blank | 450 comment | 335 complexity | f497dafb416543d5a446cf1f62bc7ffb MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. /**
  2. * tput.js - parse and compile terminfo caps to javascript.
  3. * Copyright (c) 2013, Christopher Jeffrey (MIT License)
  4. * https://github.com/chjj/blessed
  5. */
  6. // Resources:
  7. // $ man term
  8. // $ man terminfo
  9. // http://invisible-island.net/ncurses/man/term.5.html
  10. // https://en.wikipedia.org/wiki/Terminfo
  11. // Todo:
  12. // - xterm's XT (set-title capability?) value should
  13. // be true (at least tmux thinks it should).
  14. // It's not parsed as true. Investigate.
  15. // - Possibly switch to other method of finding the
  16. // extended data string table: i += h.symOffsetCount * 2;
  17. /**
  18. * Modules
  19. */
  20. var assert = require('assert')
  21. , path = require('path')
  22. , fs = require('fs');
  23. /**
  24. * Tput
  25. */
  26. function Tput(options) {
  27. if (!(this instanceof Tput)) {
  28. return new Tput(options);
  29. }
  30. options = options || {};
  31. if (typeof options === 'string') {
  32. options = { term: options };
  33. }
  34. this.options = options;
  35. this.terminal = options.term
  36. || options.terminal
  37. || process.env.TERM
  38. || (process.platform === 'win32' ? 'windows-ansi' : 'xterm');
  39. this.debug = options.debug;
  40. this.padding = options.padding;
  41. this.extended = options.extended;
  42. this.printf = options.printf;
  43. this.termcap = options.termcap;
  44. this.terminfoPrefix = options.terminfoPrefix;
  45. this.terminfoFile = options.terminfoFile;
  46. this.termcapFile = options.termcapFile;
  47. if (options.term || options.terminal) {
  48. this.setup();
  49. }
  50. };
  51. Tput.prototype.setup = function() {
  52. try {
  53. if (this.termcap) {
  54. try {
  55. this.injectTermcap();
  56. } catch (e) {
  57. if (this.debug) throw e;
  58. this._useInternalCap(this.terminal);
  59. }
  60. } else {
  61. try {
  62. this.injectTerminfo();
  63. } catch (e) {
  64. if (this.debug) throw e;
  65. this._useInternalInfo(this.terminal);
  66. }
  67. }
  68. } catch (e) {
  69. // If there was an error, fallback
  70. // to an internally stored terminfo/cap.
  71. if (this.debug) throw e;
  72. this._useXtermInfo();
  73. }
  74. };
  75. Tput.prototype.term = function(is) {
  76. return this.terminal.indexOf(is) === 0;
  77. };
  78. Tput.prototype._debug = function() {
  79. if (!this.debug) return;
  80. return console.log.apply(console, arguments);
  81. };
  82. /**
  83. * Fallback
  84. */
  85. Tput.prototype._useVt102Cap = function() {
  86. return this.injectTermcap('vt102');
  87. };
  88. Tput.prototype._useXtermCap = function() {
  89. return this.injectTermcap(__dirname + '/../usr/xterm.termcap');
  90. };
  91. Tput.prototype._useXtermInfo = function() {
  92. return this.injectTerminfo(__dirname + '/../usr/xterm');
  93. };
  94. Tput.prototype._useInternalInfo = function(name) {
  95. name = path.basename(name);
  96. return this.injectTerminfo(__dirname + '/../usr/' + name);
  97. };
  98. Tput.prototype._useInternalCap = function(name) {
  99. name = path.basename(name);
  100. return this.injectTermcap(__dirname + '/../usr/' + name + '.termcap');
  101. };
  102. /**
  103. * Terminfo
  104. */
  105. Tput.ipaths = [
  106. process.env.TERMINFO || '',
  107. (process.env.TERMINFO_DIRS || '').split(':'),
  108. (process.env.HOME || '') + '/.terminfo',
  109. '/usr/share/terminfo',
  110. '/usr/share/lib/terminfo',
  111. '/usr/lib/terminfo',
  112. '/usr/local/share/terminfo',
  113. '/usr/local/share/lib/terminfo',
  114. '/usr/local/lib/terminfo',
  115. '/usr/local/ncurses/lib/terminfo'
  116. ];
  117. Tput.prototype.readTerminfo = function(term) {
  118. var term = term || this.terminal
  119. , data
  120. , file
  121. , info;
  122. file = path.normalize(this._prefix(term));
  123. data = fs.readFileSync(file);
  124. info = this.parseTerminfo(data, file);
  125. if (this.debug) {
  126. this._terminfo = info;
  127. }
  128. return info;
  129. };
  130. Tput._prefix =
  131. Tput.prototype._prefix = function(term) {
  132. // If we have a terminfoFile, or our
  133. // term looks like a filename, use it.
  134. if (term) {
  135. if (~term.indexOf(path.sep)) {
  136. return term;
  137. }
  138. if (this.terminfoFile) {
  139. return this.terminfoFile;
  140. }
  141. }
  142. var paths = Tput.ipaths.slice()
  143. , file;
  144. if (this.terminfoPrefix) {
  145. paths.unshift(this.terminfoPrefix);
  146. }
  147. // Try exact matches.
  148. file = this._tprefix(paths, term);
  149. if (file) return file;
  150. // Try similar matches.
  151. file = this._tprefix(paths, term, true);
  152. if (file) return file;
  153. // Not found.
  154. throw new Error('Terminfo directory not found.');
  155. };
  156. Tput._tprefix =
  157. Tput.prototype._tprefix = function(prefix, term, soft) {
  158. if (!prefix) return;
  159. var file
  160. , dir
  161. , ch
  162. , i
  163. , sdiff
  164. , sfile
  165. , list;
  166. if (Array.isArray(prefix)) {
  167. for (i = 0; i < prefix.length; i++) {
  168. file = this._tprefix(prefix[i], term, soft);
  169. if (file) return file;
  170. }
  171. return;
  172. }
  173. var find = function(word) {
  174. var file, ch;
  175. file = path.resolve(prefix, word[0]);
  176. try {
  177. fs.statSync(file);
  178. return file;
  179. } catch (e) {
  180. ;
  181. }
  182. ch = word[0].charCodeAt(0).toString(16);
  183. if (ch.length < 2) ch = '0' + ch;
  184. file = path.resolve(prefix, ch);
  185. try {
  186. fs.statSync(file);
  187. return file;
  188. } catch (e) {
  189. ;
  190. }
  191. };
  192. if (!term) {
  193. // Make sure the directory's sub-directories
  194. // are all one-letter, or hex digits.
  195. // return find('x') ? prefix : null;
  196. try {
  197. dir = fs.readdirSync(prefix).filter(function(file) {
  198. return file.length !== 1 && !/^[0-9a-fA-F]{2}$/.test(file);
  199. });
  200. if (!dir.length) {
  201. return prefix;
  202. }
  203. } catch (e) {
  204. ;
  205. }
  206. return;
  207. }
  208. term = path.basename(term);
  209. dir = find(term);
  210. if (!dir) return;
  211. if (soft) {
  212. try {
  213. list = fs.readdirSync(dir);
  214. } catch (e) {
  215. return;
  216. }
  217. list.forEach(function(file) {
  218. if (file.indexOf(term) === 0) {
  219. var diff = file.length - term.length;
  220. if (!sfile || diff < sdiff) {
  221. sdiff = diff;
  222. sfile = file;
  223. }
  224. }
  225. });
  226. return sfile && (soft || sdiff === 0)
  227. ? path.resolve(dir, sfile)
  228. : null;
  229. }
  230. file = path.resolve(dir, term);
  231. try {
  232. fs.statSync(file);
  233. return file;
  234. } catch (e) {
  235. ;
  236. }
  237. };
  238. /**
  239. * Terminfo Parser
  240. * All shorts are little-endian
  241. */
  242. Tput.prototype.parseTerminfo = function(data, file) {
  243. var info = {}
  244. , extended
  245. , l = data.length
  246. , i = 0
  247. , v
  248. , o;
  249. var h = info.header = {
  250. dataSize: data.length,
  251. headerSize: 12,
  252. magicNumber: (data[1] << 8) | data[0],
  253. namesSize: (data[3] << 8) | data[2],
  254. boolCount: (data[5] << 8) | data[4],
  255. numCount: (data[7] << 8) | data[6],
  256. strCount: (data[9] << 8) | data[8],
  257. strTableSize: (data[11] << 8) | data[10]
  258. };
  259. h.total = h.headerSize
  260. + h.namesSize
  261. + h.boolCount
  262. + h.numCount * 2
  263. + h.strCount * 2
  264. + h.strTableSize;
  265. i += h.headerSize;
  266. // Names Section
  267. var names = data.toString('ascii', i, i + h.namesSize - 1)
  268. , parts = names.split('|')
  269. , name = parts[0]
  270. , desc = parts.pop();
  271. info.name = name;
  272. info.names = parts;
  273. info.desc = desc;
  274. info.dir = path.resolve(file, '..', '..');
  275. info.file = file;
  276. i += h.namesSize - 1;
  277. // Names is nul-terminated.
  278. assert.equal(data[i], 0);
  279. i++;
  280. // Booleans Section
  281. // One byte for each flag
  282. // Same order as <term.h>
  283. info.bools = {};
  284. l = i + h.boolCount;
  285. o = 0;
  286. for (; i < l; i++) {
  287. v = Tput.bools[o++];
  288. info.bools[v] = data[i] === 1;
  289. }
  290. // Null byte in between to make sure numbers begin on an even byte.
  291. if (i % 2) {
  292. assert.equal(data[i], 0);
  293. i++;
  294. }
  295. // Numbers Section
  296. info.numbers = {};
  297. l = i + h.numCount * 2;
  298. o = 0;
  299. for (; i < l; i += 2) {
  300. v = Tput.numbers[o++];
  301. if (data[i + 1] === 0377 && data[i] === 0377) {
  302. info.numbers[v] = -1;
  303. } else {
  304. info.numbers[v] = (data[i + 1] << 8) | data[i];
  305. }
  306. }
  307. // Strings Section
  308. info.strings = {};
  309. l = i + h.strCount * 2;
  310. o = 0;
  311. for (; i < l; i += 2) {
  312. v = Tput.strings[o++];
  313. if (data[i + 1] === 0377 && data[i] === 0377) {
  314. info.strings[v] = -1;
  315. } else {
  316. info.strings[v] = (data[i + 1] << 8) | data[i];
  317. }
  318. }
  319. // String Table
  320. Object.keys(info.strings).forEach(function(key) {
  321. if (info.strings[key] === -1) {
  322. delete info.strings[key];
  323. return;
  324. }
  325. // Workaround: fix an odd bug in the screen-256color terminfo where it tries
  326. // to set -1, but it appears to have {0xfe, 0xff} instead of {0xff, 0xff}.
  327. // TODO: Possibly handle errors gracefully below, as well as in the
  328. // extended info. Also possibly do: `if (info.strings[key] >= data.length)`.
  329. if (info.strings[key] === 65534) {
  330. delete info.strings[key];
  331. return;
  332. }
  333. var s = i + info.strings[key]
  334. , j = s;
  335. while (data[j]) j++;
  336. assert(j < data.length);
  337. info.strings[key] = data.toString('ascii', s, j);
  338. });
  339. // Extended Header
  340. if (this.extended !== false) {
  341. i--;
  342. i += h.strTableSize;
  343. if (i % 2) {
  344. assert.equal(data[i], 0);
  345. i++;
  346. }
  347. l = data.length;
  348. if (i < l - 1) {
  349. try {
  350. extended = this.parseExtended(data.slice(i));
  351. } catch (e) {
  352. if (this.debug) {
  353. throw e;
  354. }
  355. return info;
  356. }
  357. info.header.extended = extended.header;
  358. ['bools', 'numbers', 'strings'].forEach(function(key) {
  359. merge(info[key], extended[key]);
  360. });
  361. }
  362. }
  363. return info;
  364. };
  365. /**
  366. * Extended Parsing
  367. */
  368. // Some data to help understand:
  369. // For xterm, non-extended header:
  370. // { dataSize: 3270,
  371. // headerSize: 12,
  372. // magicNumber: 282,
  373. // namesSize: 48,
  374. // boolCount: 38,
  375. // numCount: 15,
  376. // strCount: 413,
  377. // strTableSize: 1388,
  378. // total: 2342 }
  379. // For xterm, header:
  380. // Offset: 2342
  381. // { header:
  382. // { dataSize: 928,
  383. // headerSize: 10,
  384. // boolCount: 2,
  385. // numCount: 1,
  386. // strCount: 57,
  387. // strTableSize: 117,
  388. // lastStrTableOffset: 680,
  389. // total: 245 },
  390. // For xterm, layout:
  391. // { header: '0 - 10', // length: 10
  392. // bools: '10 - 12', // length: 2
  393. // numbers: '12 - 14', // length: 2
  394. // strings: '14 - 128', // length: 114 (57 short)
  395. // symoffsets: '128 - 248', // length: 120 (60 short)
  396. // stringtable: '248 - 612', // length: 364
  397. // sym: '612 - 928' } // length: 316
  398. //
  399. // How lastStrTableOffset works:
  400. // data.length - h.lastStrTableOffset === 248
  401. // (sym-offset end, string-table start)
  402. // 364 + 316 === 680 (lastStrTableOffset)
  403. // How strTableSize works:
  404. // h.strCount + [symOffsetCount] === h.strTableSize
  405. // 57 + 60 === 117 (strTableSize)
  406. // symOffsetCount doesn't actually exist in the header. it's just implied.
  407. // Getting the number of sym offsets:
  408. // h.symOffsetCount = h.strTableSize - h.strCount;
  409. // h.symOffsetSize = (h.strTableSize - h.strCount) * 2;
  410. Tput.prototype.parseExtended = function(data) {
  411. var info = {}
  412. , l = data.length
  413. , i = 0;
  414. var h = info.header = {
  415. dataSize: data.length,
  416. headerSize: 10,
  417. boolCount: (data[i + 1] << 8) | data[i + 0],
  418. numCount: (data[i + 3] << 8) | data[i + 2],
  419. strCount: (data[i + 5] << 8) | data[i + 4],
  420. strTableSize: (data[i + 7] << 8) | data[i + 6],
  421. lastStrTableOffset: (data[i + 9] << 8) | data[i + 8]
  422. };
  423. // h.symOffsetCount = h.strTableSize - h.strCount;
  424. h.total = h.headerSize
  425. + h.boolCount
  426. + h.numCount * 2
  427. + h.strCount * 2
  428. + h.strTableSize;
  429. i += h.headerSize;
  430. // Booleans Section
  431. // One byte for each flag
  432. var _bools = [];
  433. l = i + h.boolCount;
  434. for (; i < l; i++) {
  435. _bools.push(data[i] === 1);
  436. }
  437. // Null byte in between to make sure numbers begin on an even byte.
  438. if (i % 2) {
  439. assert.equal(data[i], 0);
  440. i++;
  441. }
  442. // Numbers Section
  443. var _numbers = [];
  444. l = i + h.numCount * 2;
  445. for (; i < l; i += 2) {
  446. if (data[i + 1] === 0377 && data[i] === 0377) {
  447. _numbers.push(-1);
  448. } else {
  449. _numbers.push((data[i + 1] << 8) | data[i]);
  450. }
  451. }
  452. // Strings Section
  453. var _strings = [];
  454. l = i + h.strCount * 2;
  455. for (; i < l; i += 2) {
  456. if (data[i + 1] === 0377 && data[i] === 0377) {
  457. _strings.push(-1);
  458. } else {
  459. _strings.push((data[i + 1] << 8) | data[i]);
  460. }
  461. }
  462. // Pass over the sym offsets and get to the string table.
  463. i = data.length - h.lastStrTableOffset;
  464. // Might be better to do this instead if the file has trailing bytes:
  465. // i += h.symOffsetCount * 2;
  466. // String Table
  467. var high = 0;
  468. _strings.forEach(function(offset, k) {
  469. if (offset === -1) {
  470. _strings[k] = '';
  471. return;
  472. }
  473. var s = i + offset
  474. , j = s;
  475. while (data[j]) j++;
  476. assert(j < data.length);
  477. // Find out where the string table ends by
  478. // getting the highest string length.
  479. if (high < j - i) {
  480. high = j - i;
  481. }
  482. _strings[k] = data.toString('ascii', s, j);
  483. });
  484. // Symbol Table
  485. // Add one to the highest string length because we didn't count \0.
  486. i += high + 1;
  487. l = data.length;
  488. var sym = []
  489. , j;
  490. for (; i < l; i++) {
  491. j = i;
  492. while (data[j]) j++;
  493. sym.push(data.toString('ascii', i, j));
  494. i = j;
  495. }
  496. // Identify by name
  497. j = 0;
  498. info.bools = {};
  499. _bools.forEach(function(bool) {
  500. info.bools[sym[j++]] = bool;
  501. });
  502. info.numbers = {};
  503. _numbers.forEach(function(number) {
  504. info.numbers[sym[j++]] = number;
  505. });
  506. info.strings = {};
  507. _strings.forEach(function(string) {
  508. info.strings[sym[j++]] = string;
  509. });
  510. // Should be the very last bit of data.
  511. assert.equal(i, data.length);
  512. return info;
  513. };
  514. Tput.prototype.compileTerminfo = function(term) {
  515. return this.compile(this.readTerminfo(term));
  516. };
  517. Tput.prototype.injectTerminfo = function(term) {
  518. return this.inject(this.compileTerminfo(term));
  519. };
  520. /**
  521. * Compiler - terminfo cap->javascript
  522. */
  523. Tput.prototype.compile = function(info) {
  524. var self = this;
  525. if (!info) {
  526. throw new Error('Terminal not found.');
  527. }
  528. this.detectFeatures(info);
  529. this._debug(info);
  530. info.all = {};
  531. info.methods = {};
  532. ['bools', 'numbers', 'strings'].forEach(function(type) {
  533. Object.keys(info[type]).forEach(function(key) {
  534. info.all[key] = info[type][key];
  535. info.methods[key] = self._compile(info, key, info.all[key]);
  536. });
  537. });
  538. Tput.bools.forEach(function(key) {
  539. if (info.methods[key] == null) info.methods[key] = false;
  540. });
  541. Tput.numbers.forEach(function(key) {
  542. if (info.methods[key] == null) info.methods[key] = -1;
  543. });
  544. Tput.strings.forEach(function(key) {
  545. if (!info.methods[key]) info.methods[key] = noop;
  546. });
  547. Object.keys(info.methods).forEach(function(key) {
  548. if (!Tput.alias[key]) return;
  549. Tput.alias[key].forEach(function(alias) {
  550. info.methods[alias] = info.methods[key];
  551. });
  552. // Could just use:
  553. // Object.keys(Tput.aliasMap).forEach(function(key) {
  554. // info.methods[key] = info.methods[Tput.aliasMap[key]];
  555. // });
  556. });
  557. return info;
  558. };
  559. Tput.prototype.inject = function(info) {
  560. var self = this
  561. , methods = info.methods || info;
  562. Object.keys(methods).forEach(function(key) {
  563. if (typeof methods[key] !== 'function') {
  564. self[key] = methods[key];
  565. return;
  566. }
  567. self[key] = function() {
  568. var args = Array.prototype.slice.call(arguments);
  569. return methods[key].call(self, args);
  570. };
  571. });
  572. this.info = info;
  573. this.all = info.all;
  574. this.methods = info.methods;
  575. this.bools = info.bools;
  576. this.numbers = info.numbers;
  577. this.strings = info.strings;
  578. if (!~info.names.indexOf(this.terminal)) {
  579. this.terminal = info.name;
  580. }
  581. this.features = info.features;
  582. Object.keys(info.features).forEach(function(key) {
  583. self[key] = info.features[key];
  584. });
  585. };
  586. // See:
  587. // ~/ncurses/ncurses/tinfo/lib_tparm.c
  588. // ~/ncurses/ncurses/tinfo/comp_scan.c
  589. Tput.prototype._compile = function(info, key, str) {
  590. var self = this;
  591. this._debug('Compiling %s: %s', key, JSON.stringify(str));
  592. switch (typeof str) {
  593. case 'boolean':
  594. return str;
  595. case 'number':
  596. return str;
  597. case 'string':
  598. break;
  599. default:
  600. return noop;
  601. }
  602. if (!str) {
  603. return noop;
  604. }
  605. // See:
  606. // ~/ncurses/progs/tput.c - tput() - L149
  607. // ~/ncurses/progs/tset.c - set_init() - L992
  608. if (key === 'init_file' || key === 'reset_file') {
  609. try {
  610. // NOTE: Should we consider this code and parse it instead?
  611. str = fs.readFileSync(str, 'utf8');
  612. return function() { return str; };
  613. } catch (e) {
  614. return noop;
  615. }
  616. }
  617. var tkey = info.name + '.' + key
  618. , header = 'var v, dyn = {}, stat = {}, stack = [], out = [];'
  619. , footer = ';return out.join("");'
  620. , code = header
  621. , val = str
  622. , buff = ''
  623. , cap
  624. , ch
  625. , fi
  626. , then
  627. , els
  628. , end;
  629. function read(regex, no) {
  630. cap = regex.exec(val);
  631. if (!cap) return;
  632. val = val.substring(cap[0].length);
  633. ch = cap[1];
  634. if (!no) clear();
  635. return cap;
  636. }
  637. function stmt(c) {
  638. if (code[code.length-1] === ',') {
  639. code = code.slice(0, -1);
  640. }
  641. code += c;
  642. }
  643. function expr(c) {
  644. code += c + ',';
  645. }
  646. function echo(c) {
  647. if (c === '""') return;
  648. expr('out.push(' + c + ')');
  649. }
  650. function print(c) {
  651. buff += c;
  652. }
  653. function clear() {
  654. if (buff) {
  655. echo(JSON.stringify(buff).replace(/\\u00([0-9a-fA-F]{2})/g, '\\x$1'));
  656. buff = '';
  657. }
  658. }
  659. while (val) {
  660. // Ignore newlines
  661. if (read(/^\n /, true)) {
  662. continue;
  663. }
  664. // '^A' -> ^A
  665. if (read(/^\^(.)/i, true)) {
  666. if (!(ch >= ' ' && ch <= '~')) {
  667. this._debug('%s: bad caret char.', tkey);
  668. // NOTE: ncurses appears to simply
  669. // continue in this situation, but
  670. // I could be wrong.
  671. print(cap[0]);
  672. continue;
  673. }
  674. if (ch === '?') {
  675. ch = '\x7f';
  676. } else {
  677. ch = ch.charCodeAt(0) & 31;
  678. if (ch === 0) ch = 128;
  679. ch = String.fromCharCode(ch);
  680. }
  681. print(ch);
  682. continue;
  683. }
  684. // 3 octal digits -> character
  685. if (read(/^\\([0-7]{3})/, true)) {
  686. print(String.fromCharCode(parseInt(ch, 8)));
  687. continue;
  688. }
  689. // '\e' -> ^[
  690. // '\n' -> \n
  691. // '\r' -> \r
  692. // '\0' -> \200 (special case)
  693. if (read(/^\\([eEnlrtbfs\^\\,:0]|.)/, true)) {
  694. switch (ch) {
  695. case 'e':
  696. case 'E':
  697. ch = '\x1b';
  698. break;
  699. case 'n':
  700. ch = '\n';
  701. break;
  702. case 'l':
  703. ch = '\x85';
  704. break;
  705. case 'r':
  706. ch = '\r';
  707. break;
  708. case 't':
  709. ch = '\t';
  710. break;
  711. case 'b':
  712. ch = '\x08';
  713. break;
  714. case 'f':
  715. ch = '\x0c';
  716. break;
  717. case 's':
  718. ch = ' ';
  719. break;
  720. case '^':
  721. ch = '^';
  722. break;
  723. case '\\':
  724. ch = '\\';
  725. break;
  726. case ',':
  727. ch = ',';
  728. break;
  729. case ':':
  730. ch = ':';
  731. break;
  732. case '0':
  733. ch = '\200';
  734. break;
  735. case 'a':
  736. ch = '\x07';
  737. break;
  738. default:
  739. this._debug('%s: bad backslash char.', tkey);
  740. ch = cap[0];
  741. break;
  742. }
  743. print(ch);
  744. continue;
  745. }
  746. // $<5> -> padding
  747. // e.g. flash_screen: '\u001b[?5h$<100/>\u001b[?5l',
  748. if (read(/^\$<(\d+)([*\/]{0,2})>/, true)) {
  749. if (this.padding) print(cap[0]);
  750. continue;
  751. }
  752. // %% outputs `%'
  753. if (read(/^%%/, true)) {
  754. print('%');
  755. continue;
  756. }
  757. // %[[:]flags][width[.precision]][doxXs]
  758. // as in printf, flags are [-+#] and space. Use a `:' to allow the
  759. // next character to be a `-' flag, avoiding interpreting "%-" as an
  760. // operator.
  761. // %c print pop() like %c in printf
  762. // Example from screen terminfo:
  763. // S0: "\u001b(%p1%c"
  764. // %d print pop()
  765. // "Print (e.g., "%d") is a special case."
  766. // %s print pop() like %s in printf
  767. if (read(/^%((?::-|[+# ]){1,4})?(\d+(?:\.\d+)?)?([doxXsc])/)) {
  768. if (this.printf || cap[1] || cap[2] || ~'oxX'.indexOf(cap[3])) {
  769. echo('sprintf("'+ cap[0].replace(':-', '-') + '", stack.pop())');
  770. } else if (cap[3] === 'c') {
  771. echo('(v = stack.pop(), isFinite(v) '
  772. + '? String.fromCharCode(v || 0200) : "")');
  773. } else {
  774. echo('stack.pop()');
  775. }
  776. continue;
  777. }
  778. // %p[1-9]
  779. // push i'th parameter
  780. if (read(/^%p([1-9])/)) {
  781. expr('(stack.push(v = params[' + (ch - 1) + ']), v)');
  782. continue;
  783. }
  784. // %P[a-z]
  785. // set dynamic variable [a-z] to pop()
  786. if (read(/^%P([a-z])/)) {
  787. expr('dyn.' + ch + ' = stack.pop()');
  788. continue;
  789. }
  790. // %g[a-z]
  791. // get dynamic variable [a-z] and push it
  792. if (read(/^%g([a-z])/)) {
  793. expr('(stack.push(dyn.' + ch + '), dyn.' + ch + ')');
  794. continue;
  795. }
  796. // %P[A-Z]
  797. // set static variable [a-z] to pop()
  798. if (read(/^%P([A-Z])/)) {
  799. expr('stat.' + ch + ' = stack.pop()');
  800. continue;
  801. }
  802. // %g[A-Z]
  803. // get static variable [a-z] and push it
  804. // The terms "static" and "dynamic" are misleading. Historically,
  805. // these are simply two different sets of variables, whose values are
  806. // not reset between calls to tparm. However, that fact is not
  807. // documented in other implementations. Relying on it will adversely
  808. // impact portability to other implementations.
  809. if (read(/^%g([A-Z])/)) {
  810. expr('(stack.push(v = stat.' + ch + '), v)');
  811. continue;
  812. }
  813. // %'c' char constant c
  814. // NOTE: These are stored as c chars, exemplified by:
  815. // cursor_address: "\u001b=%p1%' '%+%c%p2%' '%+%c"
  816. if (read(/^%'(.)'/)) {
  817. expr('(stack.push(v = ' + ch.charCodeAt(0) + '), v)');
  818. continue;
  819. }
  820. // %{nn}
  821. // integer constant nn
  822. if (read(/^%\{(\d+)\}/)) {
  823. expr('(stack.push(v = ' + ch + '), v)');
  824. continue;
  825. }
  826. // %l push strlen(pop)
  827. if (read(/^%l/)) {
  828. expr('(stack.push(v = (stack.pop() || "").length || 0), v)');
  829. continue;
  830. }
  831. // %+ %- %* %/ %m
  832. // arithmetic (%m is mod): push(pop() op pop())
  833. // %& %| %^
  834. // bit operations (AND, OR and exclusive-OR): push(pop() op pop())
  835. // %= %> %<
  836. // logical operations: push(pop() op pop())
  837. if (read(/^%([+\-*\/m&|\^=><])/)) {
  838. if (ch === '=') ch = '===';
  839. else if (ch === 'm') ch = '%';
  840. expr('(v = stack.pop(),'
  841. + ' stack.push(v = (stack.pop() ' + ch + ' v) || 0),'
  842. + ' v)');
  843. continue;
  844. }
  845. // %A, %O
  846. // logical AND and OR operations (for conditionals)
  847. if (read(/^%([AO])/)) {
  848. // Are we supposed to store the result on the stack?
  849. expr('(stack.push(v = (stack.pop() '
  850. + (ch === 'A' ? '&&' : '||')
  851. + ' stack.pop())), v)');
  852. continue;
  853. }
  854. // %! %~
  855. // unary operations (logical and bit complement): push(op pop())
  856. if (read(/^%([!~])/)) {
  857. expr('(stack.push(v = ' + ch + 'stack.pop()), v)');
  858. continue;
  859. }
  860. // %i add 1 to first two parameters (for ANSI terminals)
  861. if (read(/^%i/)) {
  862. // Are these supposed to go on the stack in certain situations?
  863. // ncurses doesn't seem to put them on the stack, but xterm.user6
  864. // seems to assume they're on the stack for some reason. Could
  865. // just be a bad terminfo string.
  866. // user6: "\u001b[%i%d;%dR" - possibly a termcap-style string.
  867. // expr('(params[0] |= 0, params[1] |= 0, params[0]++, params[1]++)');
  868. expr('(params[0]++, params[1]++)');
  869. continue;
  870. }
  871. // %? expr %t thenpart %e elsepart %;
  872. // This forms an if-then-else. The %e elsepart is optional. Usually
  873. // the %? expr part pushes a value onto the stack, and %t pops it from
  874. // the stack, testing if it is nonzero (true). If it is zero (false),
  875. // control passes to the %e (else) part.
  876. // It is possible to form else-if's a la Algol 68:
  877. // %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e %;
  878. // where ci are conditions, bi are bodies.
  879. if (read(/^%\?/)) {
  880. end = -1;
  881. stmt(';if (');
  882. continue;
  883. }
  884. if (read(/^%t/)) {
  885. end = -1;
  886. // Technically this is supposed to pop everything off the stack that was
  887. // pushed onto the stack after the if statement, see man terminfo.
  888. // Right now, we don't pop anything off. This could cause compat issues.
  889. // Perhaps implement a "pushed" counter from the time the if statement
  890. // is added, to the time the then statement is added, and pop off
  891. // the appropriate number of elements.
  892. // while (pushed--) expr('stack.pop()');
  893. stmt(') {');
  894. continue;
  895. }
  896. // Terminfo does elseif's like
  897. // this: %?[expr]%t...%e[expr]%t...%;
  898. if (read(/^%e/)) {
  899. fi = val.indexOf('%?');
  900. then = val.indexOf('%t');
  901. els = val.indexOf('%e');
  902. end = val.indexOf('%;');
  903. if (end === -1) end = Infinity;
  904. if (then !== -1 && then < end
  905. && (fi === -1 || then < fi)
  906. && (els === -1 || then < els)) {
  907. stmt('} else if (');
  908. } else {
  909. stmt('} else {');
  910. }
  911. continue;
  912. }
  913. if (read(/^%;/)) {
  914. end = null;
  915. stmt('}');
  916. continue;
  917. }
  918. buff += val[0];
  919. val = val.substring(1);
  920. }
  921. // Clear the buffer of any remaining text.
  922. clear();
  923. // Some terminfos (I'm looking at you, atari-color), don't end an if
  924. // statement. It's assumed terminfo will automatically end it for
  925. // them, because they are a bunch of lazy bastards.
  926. if (end != null) {
  927. stmt('}');
  928. }
  929. // Add the footer.
  930. stmt(footer);
  931. // Optimize and cleanup generated code.
  932. v = code.slice(header.length, -footer.length);
  933. if (!v.length) {
  934. code = 'return "";';
  935. } else if (v = /^out\.push\(("(?:[^"]|\\")+")\)$/.exec(v)) {
  936. code = 'return ' + v[1] + ';';
  937. } else {
  938. // Turn `(stack.push(v = params[0]), v),out.push(stack.pop())`
  939. // into `out.push(params[0])`.
  940. code = code.replace(
  941. /\(stack\.push\(v = params\[(\d+)\]\), v\),out\.push\(stack\.pop\(\)\)/g,
  942. 'out.push(params[$1])');
  943. // Remove unnecessary variable initializations.
  944. v = code.slice(header.length, -footer.length);
  945. if (!~v.indexOf('v = ')) code = code.replace('v, ', '');
  946. if (!~v.indexOf('dyn')) code = code.replace('dyn = {}, ', '');
  947. if (!~v.indexOf('stat')) code = code.replace('stat = {}, ', '');
  948. if (!~v.indexOf('stack')) code = code.replace('stack = [], ', '');
  949. // Turn `var out = [];out.push("foo"),` into `var out = ["foo"];`.
  950. code = code.replace(
  951. /out = \[\];out\.push\(("(?:[^"]|\\")+")\),/,
  952. 'out = [$1];');
  953. }
  954. // Terminfos `wyse350-vb`, and `wy350-w`
  955. // seem to have a few broken strings.
  956. if (str === '\u001b%?') {
  957. code = 'return "\\x1b";';
  958. }
  959. if (this.debug) {
  960. v = code
  961. .replace(/\x1b/g, '\\x1b')
  962. .replace(/\r/g, '\\r')
  963. .replace(/\n/g, '\\n');
  964. process.stdout.write(v + '\n');
  965. }
  966. try {
  967. if (this.options.stringify && code.indexOf('return ') === 0) {
  968. return new Function('', code)();
  969. }
  970. return this.printf || ~code.indexOf('sprintf(')
  971. ? new Function('sprintf, params', code).bind(null, sprintf)
  972. : new Function('params', code);
  973. } catch (e) {
  974. console.error('');
  975. console.error('Error on %s:', tkey);
  976. console.error(JSON.stringify(str));
  977. console.error('');
  978. console.error(code.replace(/(,|;)/g, '$1\n'));
  979. e.stack = e.stack.replace(/\x1b/g, '\\x1b');
  980. throw e;
  981. }
  982. };
  983. // See: ~/ncurses/ncurses/tinfo/lib_tputs.c
  984. Tput.prototype._print = function(code, print, done) {
  985. var print = print || write
  986. , done = done || noop
  987. , xon = !this.bools.needs_xon_xoff || this.bools.xon_xoff;
  988. if (!this.padding) {
  989. print(code);
  990. return done();
  991. }
  992. var parts = code.split(/(?=\$<[\d.]+[*\/]{0,2}>)/)
  993. , i = 0;
  994. (function next() {
  995. if (i === parts.length) {
  996. return done();
  997. }
  998. var part = parts[i++]
  999. , padding = /^\$<([\d.]+)([*\/]{0,2})>/.exec(part)
  1000. , amount
  1001. , suffix
  1002. , affect;
  1003. if (!padding) {
  1004. print(part);
  1005. return next();
  1006. }
  1007. part = part.substring(padding[0].length);
  1008. amount = +padding[1];
  1009. suffix = padding[2];
  1010. // A `/' suffix indicates that the padding is mandatory and forces a
  1011. // delay of the given number of milliseconds even on devices for which xon
  1012. // is present to indicate flow control.
  1013. if (xon && !~suffix.indexOf('/')) {
  1014. print(part);
  1015. return next();
  1016. }
  1017. // A `*' indicates that the padding required is proportional to the number
  1018. // of lines affected by the operation, and the amount given is the
  1019. // per-affected-unit padding required. (In the case of insert character,
  1020. // the factor is still the number of lines affected.) Normally, padding is
  1021. // advisory if the device has the xon capability; it is used for cost
  1022. // computation but does not trigger delays.
  1023. if (~suffix.indexOf('*')) {
  1024. if (affect = /\x1b\[(\d+)[LM]/.exec(part)) {
  1025. amount *= +affect[1];
  1026. }
  1027. // The above is a huge workaround. In reality, we need to compile
  1028. // `_print` into the string functions and check the cap name and
  1029. // params.
  1030. // if (cap === 'insert_line' || cap === 'delete_line') {
  1031. // amount *= params[0];
  1032. // }
  1033. // if (cap === 'clear_screen') {
  1034. // amount *= process.stdout.rows;
  1035. // }
  1036. }
  1037. return setTimeout(function() {
  1038. print(part);
  1039. return next();
  1040. }, amount);
  1041. })();
  1042. };
  1043. /**
  1044. * Termcap
  1045. */
  1046. Tput.cpaths = [
  1047. process.env.TERMCAP || '',
  1048. (process.env.TERMPATH || '').split(/[: ]/),
  1049. (process.env.HOME || '') + '/.termcap',
  1050. '/usr/share/misc/termcap',
  1051. '/etc/termcap'
  1052. ];
  1053. Tput.prototype.readTermcap = function(term) {
  1054. var self = this
  1055. , term = term || this.terminal
  1056. , terms
  1057. , term_
  1058. , root
  1059. , paths
  1060. , i;
  1061. // Termcap has a bunch of terminals usually stored in one file/string,
  1062. // so we need to find the one containing our desired terminal.
  1063. if (~term.indexOf(path.sep) && (terms = this._tryCap(path.resolve(term)))) {
  1064. term_ = path.basename(term).split('.')[0];
  1065. if (terms[process.env.TERM]) {
  1066. term = process.env.TERM;
  1067. } else if (terms[term_]) {
  1068. term = term_;
  1069. } else {
  1070. term = Object.keys(terms)[0];
  1071. }
  1072. } else {
  1073. paths = Tput.cpaths.slice();
  1074. if (this.termcapFile) {
  1075. paths.unshift(this.termcapFile);
  1076. }
  1077. paths.push(Tput.termcap);
  1078. terms = this._tryCap(paths, term);
  1079. }
  1080. if (!terms) {
  1081. throw new Error('Cannot find termcap for: ' + term);
  1082. }
  1083. root = terms[term];
  1084. if (this.debug) {
  1085. this._termcap = terms;
  1086. }
  1087. (function tc(term) {
  1088. if (term && term.strings.tc) {
  1089. root.inherits = root.inherits || [];
  1090. root.inherits.push(term.strings.tc);
  1091. var names = terms[term.strings.tc]
  1092. ? terms[term.strings.tc].names
  1093. : [term.strings.tc];
  1094. self._debug('%s inherits from %s.',
  1095. term.names.join('/'), names.join('/'));
  1096. var inherit = tc(terms[term.strings.tc]);
  1097. if (inherit) {
  1098. ['bools', 'numbers', 'strings'].forEach(function(type) {
  1099. merge(term[type], inherit[type]);
  1100. });
  1101. }
  1102. }
  1103. return term;
  1104. })(root);
  1105. // Translate termcap names to terminfo-style names.
  1106. root = this.translateTermcap(root);
  1107. return root;
  1108. };
  1109. Tput.prototype._tryCap = function(file, term) {
  1110. if (!file) return;
  1111. var terms
  1112. , data
  1113. , i;
  1114. if (Array.isArray(file)) {
  1115. for (i = 0; i < file.length; i++) {
  1116. data = this._tryCap(file[i], term);
  1117. if (data) return data;
  1118. }
  1119. return;
  1120. }
  1121. // If the termcap string starts with `/`,
  1122. // ncurses considers it a filename.
  1123. data = file[0] === '/'
  1124. ? tryRead(file)
  1125. : file;
  1126. if (!data) return;
  1127. terms = this.parseTermcap(data, file);
  1128. if (term && !terms[term]) {
  1129. return;
  1130. }
  1131. return terms;
  1132. };
  1133. /**
  1134. * Termcap Parser
  1135. * http://en.wikipedia.org/wiki/Termcap
  1136. * http://www.gnu.org/software
  1137. * /termutils/manual/termcap-1.3/html_mono/termcap.html
  1138. * http://www.gnu.org/software
  1139. * /termutils/manual/termcap-1.3/html_mono/termcap.html#SEC17
  1140. * http://tldp.org/HOWTO/Text-Terminal-HOWTO.html#toc16
  1141. * man termcap
  1142. */
  1143. // Example:
  1144. // vt102|dec vt102:\
  1145. // :do=^J:co#80:li#24:cl=50\E[;H\E[2J:\
  1146. // :le=^H:bs:cm=5\E[%i%d;%dH:nd=2\E[C:up=2\E[A:\
  1147. // :ce=3\E[K:cd=50\E[J:so=2\E[7m:se=2\E[m:us=2\E[4m:ue=2\E[m:\
  1148. // :md=2\E[1m:mr=2\E[7m:mb=2\E[5m:me=2\E[m:is=\E[1;24r\E[24;1H:\
  1149. // :rs=\E>\E[?3l\E[?4l\E[?5l\E[?7h\E[?8h:ks=\E[?1h\E=:ke=\E[?1l\E>:\
  1150. // :ku=\EOA:kd=\EOB:kr=\EOC:kl=\EOD:kb=^H:\
  1151. // :ho=\E[H:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:pt:sr=5\EM:vt#3:\
  1152. // :sc=\E7:rc=\E8:cs=\E[%i%d;%dr:vs=\E[?7l:ve=\E[?7h:\
  1153. // :mi:al=\E[L:dc=\E[P:dl=\E[M:ei=\E[4l:im=\E[4h:
  1154. Tput.prototype.parseTermcap = function(data, file) {
  1155. var terms = {}
  1156. , parts
  1157. , term
  1158. , entries
  1159. , fields
  1160. , field
  1161. , names
  1162. , i
  1163. , j
  1164. , k;
  1165. // remove escaped newlines
  1166. data = data.replace(/\\\n[ \t]*/g, '');
  1167. // remove comments
  1168. data = data.replace(/^#[^\n]+/gm, '');
  1169. // split entries
  1170. entries = data.trim().split(/\n+/);
  1171. for (i = 0; i < entries.length; i++) {
  1172. fields = entries[i].split(/:+/);
  1173. for (j = 0; j < fields.length; j++) {
  1174. field = fields[j].trim();
  1175. if (!field) continue;
  1176. if (j === 0) {
  1177. names = field.split('|');
  1178. term = {
  1179. name: names[0],
  1180. names: names,
  1181. desc: names.pop(),
  1182. file: ~file.indexOf(path.sep)
  1183. ? path.resolve(file)
  1184. : file,
  1185. termcap: true
  1186. };
  1187. for (k = 0; k < names.length; k++) {
  1188. terms[names[k]] = term;
  1189. }
  1190. term.bools = {};
  1191. term.numbers = {};
  1192. term.strings = {};
  1193. continue;
  1194. }
  1195. if (~field.indexOf('=')) {
  1196. parts = field.split('=');
  1197. term.strings[parts[0]] = parts.slice(1).join('=');
  1198. } else if (~field.indexOf('#')) {
  1199. parts = field.split('#');
  1200. term.numbers[parts[0]] = +parts.slice(1).join('#');
  1201. } else {
  1202. term.bools[field] = true;
  1203. }
  1204. }
  1205. }
  1206. return terms;
  1207. };
  1208. /**
  1209. * Termcap Compiler
  1210. * man termcap
  1211. */
  1212. Tput.prototype.translateTermcap = function(info) {
  1213. var self = this
  1214. , out = {};
  1215. if (!info) return;
  1216. this._debug(info);
  1217. ['name', 'names', 'desc', 'file', 'termcap'].forEach(function(key) {
  1218. out[key] = info[key];
  1219. });
  1220. // Separate aliases for termcap
  1221. var map = (function() {
  1222. var out = {};
  1223. Object.keys(Tput.alias).forEach(function(key) {
  1224. var aliases = Tput.alias[key];
  1225. out[aliases.termcap] = key;
  1226. });
  1227. return out;
  1228. })();
  1229. // Translate termcap cap names to terminfo cap names.
  1230. // e.g. `up` -> `cursor_up`
  1231. ['bools', 'numbers', 'strings'].forEach(function(key) {
  1232. out[key] = {};
  1233. Object.keys(info[key]).forEach(function(cap) {
  1234. if (key === 'strings') {
  1235. info.strings[cap] = self._captoinfo(cap, info.strings[cap], 1);
  1236. }
  1237. if (map[cap]) {
  1238. out[key][map[cap]] = info[key][cap];
  1239. } else {
  1240. // NOTE: Possibly include all termcap names
  1241. // in a separate alias.js file. Some are
  1242. // missing from the terminfo alias.js file
  1243. // which is why we have to do this:
  1244. // See: $ man termcap
  1245. out[key][cap] = info[key][cap];
  1246. }
  1247. });
  1248. });
  1249. return out;
  1250. };
  1251. Tput.prototype.compileTermcap = function(term) {
  1252. return this.compile(this.readTermcap(term));
  1253. };
  1254. Tput.prototype.injectTermcap = function(term) {
  1255. return this.inject(this.compileTermcap(term));
  1256. };
  1257. /**
  1258. * _nc_captoinfo - ported to javascript directly from ncurses.
  1259. * Copyright (c) 1998-2009,2010 Free Software Foundation, Inc.
  1260. * See: ~/ncurses/ncurses/tinfo/captoinfo.c
  1261. *
  1262. * Convert a termcap string to terminfo format.
  1263. * 'cap' is the relevant terminfo capability index.
  1264. * 's' is the string value of the capability.
  1265. * 'parameterized' tells what type of translations to do:
  1266. * % translations if 1
  1267. * pad translations if >=0
  1268. */
  1269. Tput.prototype._captoinfo = function(cap, s, parameterized) {
  1270. var self = this;
  1271. var capstart;
  1272. if (parameterized == null) {
  1273. parameterized = 0;
  1274. }
  1275. var MAX_PUSHED = 16
  1276. , stack = [];
  1277. var stackptr = 0
  1278. , onstack = 0
  1279. , seenm = 0
  1280. , seenn = 0
  1281. , seenr = 0
  1282. , param = 1
  1283. , i = 0
  1284. , out = '';
  1285. function warn() {
  1286. var args = Array.prototype.slice.call(arguments);
  1287. args[0] = 'captoinfo: ' + (args[0] || '');
  1288. return self._debug.apply(self, args);
  1289. }
  1290. function isdigit(ch) {
  1291. return ch >= '0' && ch <= '9';
  1292. }
  1293. function isgraph(ch) {
  1294. return ch > ' ' && ch <= '~';
  1295. }
  1296. // convert a character to a terminfo push
  1297. function cvtchar(sp) {
  1298. var c = '\0'
  1299. , len;
  1300. var j = i;
  1301. switch (sp[j]) {
  1302. case '\\':
  1303. switch (sp[++j]) {
  1304. case '\'':
  1305. case '$':
  1306. case '\\':
  1307. case '%':
  1308. c = sp[j];
  1309. len = 2;
  1310. break;
  1311. case '\0':
  1312. c = '\\';
  1313. len = 1;
  1314. break;
  1315. case '0':
  1316. case '1':
  1317. case '2':
  1318. case '3':
  1319. len = 1;
  1320. while (isdigit(sp[j])) {
  1321. c = String.fromCharCode(8 * c.charCodeAt(0)
  1322. + (sp[j++].charCodeAt(0) - '0'.charCodeAt(0)));
  1323. len++;
  1324. }
  1325. break;
  1326. default:
  1327. c = sp[j];
  1328. len = 2;
  1329. break;
  1330. }
  1331. break;
  1332. case '^':
  1333. c = String.fromCharCode(sp[++j].charCodeAt(0) & 0x1f);
  1334. len = 2;
  1335. break;
  1336. default:
  1337. c = sp[j];
  1338. len = 1;
  1339. }
  1340. if (isgraph(c) && c !== ',' && c !== '\'' && c !== '\\' && c !== ':') {
  1341. out += '%\'';
  1342. out += c;
  1343. out += '\'';
  1344. } else {
  1345. out += '%{';
  1346. if (c.charCodeAt(0) > 99) {
  1347. out += String.fromCharCode(
  1348. (c.charCodeAt(0) / 100 | 0) + '0'.charCodeAt(0));
  1349. }
  1350. if (c.charCodeAt(0) > 9) {
  1351. out += String.fromCharCode(
  1352. (c.charCodeAt(0) / 10 | 0) % 10 + '0'.charCodeAt(0));
  1353. }
  1354. out += String.fromCharCode(
  1355. c.charCodeAt(0) % 10 + '0'.charCodeAt(0));
  1356. out += '}';
  1357. }
  1358. return len;
  1359. }
  1360. // push n copies of param on the terminfo stack if not already there
  1361. function getparm(parm, n) {
  1362. if (seenr) {
  1363. if (parm === 1) {
  1364. parm = 2;
  1365. } else if (parm === 2) {
  1366. parm = 1;
  1367. }
  1368. }
  1369. if (onstack === parm) {
  1370. if (n > 1) {
  1371. warn('string may not be optimal');
  1372. out += '%Pa';
  1373. while (n--) {
  1374. out += '%ga';
  1375. }
  1376. }
  1377. return;
  1378. }
  1379. if (onstack !== 0) {
  1380. push();
  1381. }
  1382. onstack = parm;
  1383. while (n--) {
  1384. out += '%p';
  1385. out += String.fromCharCode('0'.charCodeAt(0) + parm);
  1386. }
  1387. if (seenn && parm < 3) {
  1388. out += '%{96}%^';
  1389. }
  1390. if (seenm && parm < 3) {
  1391. out += '%{127}%^';
  1392. }
  1393. }
  1394. // push onstack on to the stack
  1395. function push() {
  1396. if (stackptr >= MAX_PUSHED) {
  1397. warn('string too complex to convert');
  1398. } else {
  1399. stack[stackptr++] = onstack;
  1400. }
  1401. }
  1402. // pop the top of the stack into onstack
  1403. function pop() {
  1404. if (stackptr === 0) {
  1405. if (onstack === 0) {
  1406. warn('I\'m confused');
  1407. } else {
  1408. onstack = 0;
  1409. }
  1410. } else {
  1411. onstack = stack[--stackptr];
  1412. }
  1413. param++;
  1414. }
  1415. function see03() {
  1416. getparm(param, 1);
  1417. out += '%3d';
  1418. pop();
  1419. }
  1420. function invalid() {
  1421. out += '%';
  1422. i--;
  1423. warn('unknown %% code %s (%#x) in %s',
  1424. JSON.stringify(s[i]), s[i].charCodeAt(0), cap);
  1425. }
  1426. // skip the initial padding (if we haven't been told not to)
  1427. capstart = null;
  1428. if (s == null) s = '';
  1429. if (parameterized >= 0 && isdigit(s[i])) {
  1430. for (capstart = i;; i++) {
  1431. if (!(isdigit(s[i]) || s[i] === '*' || s[i] === '.')) {
  1432. break;
  1433. }
  1434. }
  1435. }
  1436. while (s[i]) {
  1437. switch (s[i]) {
  1438. case '%':
  1439. i++;
  1440. if (parameterized < 1) {
  1441. out += '%';
  1442. break;
  1443. }
  1444. switch (s[i++]) {
  1445. case '%':
  1446. out += '%';
  1447. break;
  1448. case 'r':
  1449. if (seenr++ === 1) {
  1450. warn('saw %%r twice in %s', cap);
  1451. }
  1452. break;
  1453. case 'm':
  1454. if (seenm++ === 1) {
  1455. warn('saw %%m twice in %s', cap);
  1456. }
  1457. break;
  1458. case 'n':
  1459. if (seenn++ === 1) {
  1460. warn('saw %%n twice in %s', cap);
  1461. }
  1462. break;
  1463. case 'i':
  1464. out += '%i';
  1465. break;
  1466. case '6':
  1467. case 'B':
  1468. getparm(param, 1);
  1469. out += '%{10}%/%{16}%*';
  1470. getparm(param, 1);
  1471. out += '%{10}%m%+';
  1472. break;
  1473. case '8':
  1474. case 'D':
  1475. getparm(param, 2);
  1476. out += '%{2}%*%-';
  1477. break;
  1478. case '>':
  1479. getparm(param, 2);
  1480. // %?%{x}%>%t%{y}%+%;
  1481. out += '%?';
  1482. i += cvtchar(s);
  1483. out += '%>%t';
  1484. i += cvtchar(s);
  1485. out += '%+%;';
  1486. break;
  1487. case 'a':
  1488. if ((s[i] === '=' || s[i] === '+' || s[i] === '-'
  1489. || s[i] === '*' || s[i] === '/')
  1490. && (s[i + 1] === 'p' || s[i + 1] === 'c')
  1491. && s[i + 2] !== '\0' && s[i + 2]) {
  1492. var l;
  1493. l = 2;
  1494. if (s[i] !== '=') {
  1495. getparm(param, 1);
  1496. }
  1497. if (s[i + 1] === 'p') {
  1498. getparm(param + s[i + 2].charCodeAt(0) - '@'.charCodeAt(0), 1);
  1499. if (param !== onstack) {
  1500. pop();
  1501. param--;
  1502. }
  1503. l++;
  1504. } else {
  1505. i += 2, l += cvtchar(s), i -= 2;
  1506. }
  1507. switch (s[i]) {
  1508. case '+':
  1509. out += '%+';
  1510. break;
  1511. case '-':
  1512. out += '%-';
  1513. break;
  1514. case '*':
  1515. out += '%*';
  1516. break;
  1517. case '/':
  1518. out += '%/';
  1519. break;
  1520. case '=':
  1521. if (seenr) {
  1522. if (param === 1) {
  1523. onstack = 2;
  1524. } else if (param === 2) {
  1525. onstack = 1;
  1526. } else {
  1527. onstack = param;
  1528. }
  1529. } else {
  1530. onstack = param;
  1531. }
  1532. break;
  1533. }
  1534. i += l;
  1535. break;
  1536. }
  1537. getparm(param, 1);
  1538. i += cvtchar(s);
  1539. out += '%+';
  1540. break;
  1541. case '+':
  1542. getparm(param, 1);
  1543. i += cvtchar(s);
  1544. out += '%+%c';
  1545. pop();
  1546. break;
  1547. case 's':
  1548. // #ifdef WATERLOO
  1549. // i += cvtchar(s);
  1550. // getparm(param, 1);
  1551. // out += '%-';
  1552. // #else
  1553. getparm(param, 1);
  1554. out += '%s';
  1555. pop();
  1556. // #endif /* WATERLOO */
  1557. break;
  1558. case '-':
  1559. i += cvtchar(s);
  1560. getparm(param, 1);
  1561. out += '%-%c';
  1562. pop();
  1563. break;
  1564. case '.':
  1565. getparm(param, 1);
  1566. out += '%c';
  1567. pop();
  1568. break;
  1569. case '0': // not clear any of the historical termcaps did this
  1570. if (s[i] === '3') {
  1571. see03(); // goto
  1572. break;
  1573. } else if (s[i] !== '2') {
  1574. invalid(); // goto
  1575. break;
  1576. }
  1577. // FALLTHRU
  1578. case '2':
  1579. getparm(param, 1);
  1580. out += '%2d';
  1581. pop();
  1582. break;
  1583. case '3':
  1584. see03();
  1585. break;
  1586. case 'd':
  1587. getparm(param, 1);
  1588. out += '%d';
  1589. pop();
  1590. break;
  1591. case 'f':
  1592. param++;
  1593. break;
  1594. case 'b':
  1595. param--;
  1596. break;
  1597. case '\\':
  1598. out += '%\\';
  1599. break;
  1600. default:
  1601. invalid();
  1602. break;
  1603. }
  1604. break;
  1605. // #ifdef REVISIBILIZE
  1606. // case '\\':
  1607. // out += s[i++];
  1608. // out += s[i++];
  1609. // break;
  1610. // case '\n':
  1611. // out += '\\n';
  1612. // i++;
  1613. // break;
  1614. // case '\t':
  1615. // out += '\\t';
  1616. // i++;
  1617. // break;
  1618. // case '\r':
  1619. // out += '\\r';
  1620. // i++;
  1621. // break;
  1622. // case '\200':
  1623. // out += '\\0';
  1624. // i++;
  1625. // break;
  1626. // case '\f':
  1627. // out += '\\f';
  1628. // i++;
  1629. // break;
  1630. // case '\b':
  1631. // out += '\\b';
  1632. // i++;
  1633. // break;
  1634. // case ' ':
  1635. // out += '\\s';
  1636. // i++;
  1637. // break;
  1638. // case '^':
  1639. // out += '\\^';
  1640. // i++;
  1641. // break;
  1642. // case ':':
  1643. // out += '\\:';
  1644. // i++;
  1645. // break;
  1646. // case ',':
  1647. // out += '\\,';
  1648. // i++;
  1649. // break;
  1650. // default:
  1651. // if (s[i] === '\033') {
  1652. // out += '\\E';
  1653. // i++;
  1654. // } else if (s[i].charCodeAt(0) > 0 && s[i].charCodeAt(0) < 32) {
  1655. // out += '^';
  1656. // out += String.fromCharCode(s[i].charCodeAt(0) + '@'.charCodeAt(0));
  1657. // i++;
  1658. // } else if (s[i].charCodeAt(0) <= 0 || s[i].charCodeAt(0) >= 127) {
  1659. // out += '\\';
  1660. // out += String.fromCharCode(
  1661. // ((s[i].charCodeAt(0) & 0300) >> 6) + '0'.charCodeAt(0));
  1662. // out += String.fromCharCode(
  1663. // ((s[i].charCodeAt(0) & 0070) >> 3) + '0'.charCodeAt(0));
  1664. // out += String.fromCharCode(
  1665. // (s[i].charCodeAt(0) & 0007) + '0'.charCodeAt(0));
  1666. // i++;
  1667. // } else {
  1668. // out += s[i++];
  1669. // }
  1670. // break;
  1671. // #else
  1672. default:
  1673. out += s[i++];
  1674. break;
  1675. // #endif
  1676. }
  1677. }
  1678. // Now, if we stripped off some leading padding, add it at the end
  1679. // of the string as mandatory padding.
  1680. if (capstart != null) {
  1681. out += '$<';
  1682. for (i = capstart;; i++) {
  1683. if (isdigit(s[i]) || s[i] === '*' || s[i] === '.') {
  1684. out += s[i];
  1685. } else {
  1686. break;
  1687. }
  1688. }
  1689. out += '/>';
  1690. }
  1691. if (s !== out) {
  1692. warn('Translating %s from %s to %s.',
  1693. cap, JSON.stringify(s), JSON.stringify(out));
  1694. }
  1695. return out;
  1696. };
  1697. /**
  1698. * Compile All Terminfo
  1699. */
  1700. Tput.prototype.getAll = function() {
  1701. var dir = this._prefix()
  1702. , list = asort(fs.readdirSync(dir))
  1703. , infos = [];
  1704. list.forEach(function(letter) {
  1705. var terms = asort(fs.readdirSync(path.resolve(dir, letter)));
  1706. infos.push.apply(infos, terms);
  1707. });
  1708. function asort(obj) {
  1709. return obj.sort(function(a, b) {
  1710. a = a.toLowerCase().charCodeAt(0);
  1711. b = b.toLowerCase().charCodeAt(0);
  1712. return a - b;
  1713. });
  1714. }
  1715. return infos;
  1716. };
  1717. Tput.prototype.compileAll = function(start) {
  1718. var self = this
  1719. , all = {};
  1720. this.getAll().forEach(function(name) {
  1721. if (start && name !== start) {
  1722. return;
  1723. } else {
  1724. start = null;
  1725. }
  1726. all[name] = self.compileTerminfo(name);
  1727. });
  1728. return all;
  1729. };
  1730. /**
  1731. * Detect Features / Quirks
  1732. */
  1733. Tput.prototype.detectFeatures = function(info) {
  1734. var data = this.parseACS(info);
  1735. info.features = {
  1736. unicode: this.detectUnicode(info),
  1737. brokenACS: this.detectBrokenACS(info),
  1738. PCRomSet: this.detectPCRomSet(info),
  1739. magicCookie: this.detectMagicCookie(info),
  1740. padding: this.detectPadding(info),
  1741. setbuf: this.detectSetbuf(info),
  1742. acsc: data.acsc,
  1743. acscr: data.acscr
  1744. };
  1745. return info.features;
  1746. };
  1747. Tput.prototype.detectUnicode = function() {
  1748. var LANG = process.env.LANG
  1749. + ':' + process.env.LANGUAGE
  1750. + ':' + process.env.LC_ALL
  1751. + ':' + process.env.LC_CTYPE;
  1752. return /utf-?8/i.test(LANG);
  1753. };
  1754. // For some reason TERM=linux has smacs/rmacs, but it maps to `^[[11m`
  1755. // and it does not switch to the DEC SCLD character set. What the hell?
  1756. // xterm: \x1b(0, screen: \x0e, linux: \x1b[11m (doesn't work)
  1757. // `man console_codes` says:
  1758. // 11 select null mapping, set display control flag, reset tog‐
  1759. // gle meta flag (ECMA-48 says "first alternate font").
  1760. // See ncurses:
  1761. // ~/ncurses/ncurses/base/lib_set_term.c
  1762. // ~/ncurses/ncurses/tinfo/lib_acs.c
  1763. // ~/ncurses/ncurses/tinfo/tinfo_driver.c
  1764. // ~/ncurses/ncurses/tinfo/lib_setup.c
  1765. Tput.prototype.detectBrokenACS = function(info) {
  1766. // ncurses-compatible env variable.
  1767. if (process.env.NCURSES_NO_UTF8_ACS != null) {
  1768. return true;
  1769. }
  1770. // If the terminal supports unicode, we don't need ACS.
  1771. if (info.numbers['U8'] >= 0) {
  1772. return !!info.numbers['U8'];
  1773. }
  1774. // The linux console is just broken for some reason.
  1775. // Apparently the Linux console does not support ACS,
  1776. // but it does support the PC ROM character set.
  1777. if (info.name === 'linux') {
  1778. return true;
  1779. }
  1780. // PC alternate charset
  1781. // if (acsc.indexOf('+\x10,\x11-\x18.\x190') === 0) {
  1782. if (this.detectPCRomSet(info)) {
  1783. return true;
  1784. }
  1785. // screen termcap is bugged?
  1786. if (info.name.indexOf('screen') == 0
  1787. && process.env.TERMCAP
  1788. && ~process.env.TERMCAP.indexOf('screen')
  1789. && ~process.env.TERMCAP.indexOf('hhII00')) {
  1790. if (~info.strings.enter_alt_charset_mode.indexOf('\016')
  1791. || ~info.strings.enter_alt_charset_mode.indexOf('\017')
  1792. || ~info.strings.set_attributes.indexOf('\016')
  1793. || ~info.strings.set_attributes.indexOf('\017')) {
  1794. return true;
  1795. }
  1796. }
  1797. return false;
  1798. };
  1799. // If enter_pc_charset is the same as enter_alt_charset,
  1800. // the terminal does not support SCLD as ACS.
  1801. // See: ~/ncurses/ncurses/tinfo/lib_acs.c
  1802. Tput.prototype.detectPCRomSet = function(info) {
  1803. var s = info.strings;
  1804. if (s.enter_pc_charset_mode && s.enter_alt_charset_mode
  1805. && s.enter_pc_charset_mode === s.enter_alt_charset_mode
  1806. && s.exit_pc_charset_mode === s.exit_alt_charset_mode) {
  1807. return true;
  1808. }
  1809. return false;
  1810. };
  1811. Tput.prototype.detectMagicCookie = function(info) {
  1812. return process.env.NCURSES_NO_MAGIC_COOKIE == null;
  1813. };
  1814. Tput.prototype.detectPadding = function(info) {
  1815. return process.env.NCURSES_NO_PADDING == null;
  1816. };
  1817. Tput.prototype.detectSetbuf = function(info) {
  1818. return process.env.NCURSES_NO_SETBUF == null;
  1819. };

Large files files are truncated, but you can click here to view the full file