PageRenderTime 60ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/program.js

https://github.com/prodigeni/blessed
JavaScript | 3581 lines | 2447 code | 375 blank | 759 comment | 459 complexity | 9cf7620da05e61e38f7a2e292cb14e27 MD5 | raw file

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

  1. /**
  2. * program.js - basic curses-like functionality for blessed.
  3. * Copyright (c) 2013, Christopher Jeffrey (MIT License).
  4. * https://github.com/chjj/blessed
  5. */
  6. /**
  7. * Modules
  8. */
  9. var EventEmitter = require('events').EventEmitter
  10. , StringDecoder = require('string_decoder').StringDecoder
  11. , util = require('util')
  12. , fs = require('fs');
  13. var Tput = require('./tput')
  14. , colors = require('./colors')
  15. , slice = Array.prototype.slice;
  16. /**
  17. * Program
  18. */
  19. function Program(options) {
  20. var self = this;
  21. if (!(this instanceof Program)) {
  22. return new Program(options);
  23. }
  24. EventEmitter.call(this);
  25. if (!options || options.__proto__ !== Object.prototype) {
  26. options = {
  27. input: arguments[0],
  28. output: arguments[1]
  29. };
  30. }
  31. this.options = options;
  32. this.input = options.input || process.stdin;
  33. this.output = options.output || process.stdout;
  34. options.log = options.log || options.dump;
  35. if (options.log) {
  36. this._logger = fs.createWriteStream(options.log);
  37. if (options.dump) this.setupDump();
  38. }
  39. this.zero = options.zero !== false;
  40. this.useBuffer = options.buffer;
  41. this.x = 0;
  42. this.y = 0;
  43. this.savedX = 0;
  44. this.savedY = 0;
  45. this.cols = this.output.columns || 1;
  46. this.rows = this.output.rows || 1;
  47. this.scrollTop = 0;
  48. this.scrollBottom = this.rows - 1;
  49. this.terminal = options.term
  50. || options.terminal
  51. || process.env.TERM
  52. || (process.platform === 'win32' ? 'windows-ansi' : 'xterm');
  53. this._buf = '';
  54. this._flush = this.flush.bind(this);
  55. unshiftEvent(process, 'exit', function() {
  56. // Ensure the buffer is flushed (it should
  57. // always be at this point, but who knows).
  58. self.flush();
  59. // Ensure _exiting is set (could technically
  60. // use process._exiting).
  61. self._exiting = true;
  62. });
  63. if (!Program.global) {
  64. Program.global = this;
  65. }
  66. if (options.tput !== false) {
  67. this.setupTput();
  68. }
  69. this.listen();
  70. }
  71. Program.prototype.__proto__ = EventEmitter.prototype;
  72. Program.prototype.log = function() {
  73. return this._log('LOG', util.format.apply(util, arguments));
  74. };
  75. Program.prototype.debug = function() {
  76. if (!this.options.debug) return;
  77. return this._log('DEBUG', util.format.apply(util, arguments));
  78. };
  79. Program.prototype._log = function(pre, msg) {
  80. if (!this._logger) return;
  81. return this._logger.write(pre + ': ' + msg + '\n-\n');
  82. };
  83. Program.prototype.setupDump = function() {
  84. var self = this
  85. , write = this.output.write
  86. , decoder = new StringDecoder('utf8');
  87. function stringify(data) {
  88. return caret(data
  89. .replace(/\r/g, '\\r')
  90. .replace(/\n/g, '\\n')
  91. .replace(/\t/g, '\\t'))
  92. .replace(/[^ -~]/g, function(ch) {
  93. if (ch.charCodeAt(0) > 0xff) return ch;
  94. ch = ch.charCodeAt(0).toString(16);
  95. if (ch.length > 2) {
  96. if (ch.length < 4) ch = '0' + ch;
  97. return '\\u' + ch;
  98. }
  99. if (ch.length < 2) ch = '0' + ch;
  100. return '\\x' + ch;
  101. });
  102. }
  103. function caret(data) {
  104. return data.replace(/[\0\x80\x1b-\x1f\x7f\x01-\x1a]/g, function(ch) {
  105. switch (ch) {
  106. case '\0':
  107. case '\200':
  108. ch = '@';
  109. break;
  110. case '\x1b':
  111. ch = '[';
  112. break;
  113. case '\x1c':
  114. ch = '\\';
  115. break;
  116. case '\x1d':
  117. ch = ']';
  118. break;
  119. case '\x1e':
  120. ch = '^';
  121. break;
  122. case '\x1f':
  123. ch = '_';
  124. break;
  125. case '\x7f':
  126. ch = '?';
  127. break;
  128. default:
  129. ch = ch.charCodeAt(0);
  130. // From ('A' - 64) to ('Z' - 64).
  131. if (ch >= 1 && ch <= 26) {
  132. ch = String.fromCharCode(ch + 64);
  133. } else {
  134. return String.fromCharCode(ch);
  135. }
  136. break;
  137. }
  138. return '^' + ch;
  139. });
  140. }
  141. this.input.on('data', function(data) {
  142. self._log('IN', stringify(decoder.write(data)));
  143. });
  144. this.output.write = function(data) {
  145. self._log('OUT', stringify(data));
  146. return write.apply(this, arguments);
  147. };
  148. };
  149. Program.prototype.setupTput = function() {
  150. if (this._tputSetup) return;
  151. this._tputSetup = true;
  152. var self = this
  153. , options = this.options
  154. , write = this.write.bind(this);
  155. var tput = this.tput = new Tput({
  156. term: this.terminal,
  157. padding: options.padding,
  158. extended: options.extended,
  159. printf: options.printf,
  160. termcap: options.termcap
  161. });
  162. this.put = function() {
  163. var args = slice.call(arguments)
  164. , cap = args.shift();
  165. if (tput[cap]) {
  166. return this._write(tput[cap].apply(tput, args));
  167. }
  168. };
  169. Object.keys(tput).forEach(function(key) {
  170. if (self[key] == null) {
  171. self[key] = tput[key];
  172. }
  173. if (typeof tput[key] !== 'function') {
  174. self.put[key] = tput[key];
  175. return;
  176. }
  177. if (options.padding) {
  178. self.put[key] = function() {
  179. return tput._print(tput[key].apply(tput, arguments), write);
  180. };
  181. } else {
  182. self.put[key] = function() {
  183. return self._write(tput[key].apply(tput, arguments));
  184. };
  185. }
  186. });
  187. };
  188. Program.prototype.has = function(name) {
  189. return this.tput
  190. ? this.tput.has(name)
  191. : false;
  192. };
  193. Program.prototype.term = function(is) {
  194. return this.terminal.indexOf(is) === 0;
  195. };
  196. Program.prototype.listen = function() {
  197. var readline = require('readline')
  198. , self = this;
  199. if (!this.input.isTTY || !this.output.isTTY) {
  200. throw new Error('Not a terminal.');
  201. }
  202. // Input
  203. this.input.on('keypress', function(ch, key) {
  204. key = key || { ch: ch };
  205. if (key.name === 'undefined'
  206. && (key.code === '[M' || key.code === '[I' || key.code === '[O')) {
  207. // A mouse sequence. The readline module doesn't understand these.
  208. return;
  209. }
  210. if (key.name === 'undefined') {
  211. // Not sure what this is, but we should probably ignore it.
  212. return;
  213. }
  214. if (key.name === 'enter' && key.sequence === '\n') {
  215. key.name = 'linefeed';
  216. }
  217. if (key.name === 'return' && key.sequence === '\r') {
  218. self.input.emit('keypress', ch, merge({}, key, { name: 'enter' }));
  219. }
  220. var name = (key.ctrl ? 'C-' : '')
  221. + (key.meta ? 'M-' : '')
  222. + (key.shift && key.name ? 'S-' : '')
  223. + (key.name || ch);
  224. key.full = name;
  225. self.emit('keypress', ch, key);
  226. self.emit('key ' + name, ch, key);
  227. });
  228. this.input.on('data', function(data) {
  229. self.emit('data', data);
  230. });
  231. readline.emitKeypressEvents(this.input);
  232. this.on('newListener', function fn(type) {
  233. if (type === 'keypress' || type === 'mouse') {
  234. self.removeListener('newListener', fn);
  235. if (!self.input.isRaw) {
  236. self.input.setRawMode(true);
  237. self.input.resume();
  238. }
  239. }
  240. });
  241. this.on('newListener', function fn(type) {
  242. if (type === 'mouse') {
  243. self.removeListener('newListener', fn);
  244. self.bindMouse();
  245. }
  246. });
  247. // Output
  248. this.output.on('resize', function() {
  249. self.cols = self.output.columns;
  250. self.rows = self.output.rows;
  251. self.emit('resize');
  252. });
  253. };
  254. Program.prototype.key = function(key, listener) {
  255. if (typeof key === 'string') key = key.split(/\s*,\s*/);
  256. key.forEach(function(key) {
  257. return this.on('key ' + key, listener);
  258. }, this);
  259. };
  260. Program.prototype.onceKey = function(key, listener) {
  261. if (typeof key === 'string') key = key.split(/\s*,\s*/);
  262. key.forEach(function(key) {
  263. return this.once('key ' + key, listener);
  264. }, this);
  265. };
  266. Program.prototype.unkey =
  267. Program.prototype.removeKey = function(key, listener) {
  268. if (typeof key === 'string') key = key.split(/\s*,\s*/);
  269. key.forEach(function(key) {
  270. return this.removeListener('key ' + key, listener);
  271. }, this);
  272. };
  273. // XTerm mouse events
  274. // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
  275. // To better understand these
  276. // the xterm code is very helpful:
  277. // Relevant files:
  278. // button.c, charproc.c, misc.c
  279. // Relevant functions in xterm/button.c:
  280. // BtnCode, EmitButtonCode, EditorButton, SendMousePosition
  281. // send a mouse event:
  282. // regular/utf8: ^[[M Cb Cx Cy
  283. // urxvt: ^[[ Cb ; Cx ; Cy M
  284. // sgr: ^[[ Cb ; Cx ; Cy M/m
  285. // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r
  286. // locator: CSI P e ; P b ; P r ; P c ; P p & w
  287. // motion example of a left click:
  288. // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
  289. // mouseup, mousedown, mousewheel
  290. // left click: ^[[M 3<^[[M#3<
  291. // mousewheel up: ^[[M`3>
  292. Program.prototype.bindMouse = function() {
  293. if (this._boundMouse) return;
  294. this._boundMouse = true;
  295. var decoder = new StringDecoder('utf8')
  296. , self = this;
  297. this.on('data', function(data) {
  298. data = decoder.write(data);
  299. if (!data) return;
  300. self._bindMouse(data);
  301. });
  302. };
  303. Program.prototype._bindMouse = function(s) {
  304. var self = this
  305. , key
  306. , parts;
  307. key = {
  308. name: undefined,
  309. ctrl: false,
  310. meta: false,
  311. shift: false
  312. };
  313. if (Buffer.isBuffer(s)) {
  314. if (s[0] > 127 && s[1] === undefined) {
  315. s[0] -= 128;
  316. s = '\x1b' + s.toString('utf-8');
  317. } else {
  318. s = s.toString('utf-8');
  319. }
  320. }
  321. // XTerm / X10
  322. if (parts = /^\x1b\[M([\x00\u0020-\uffff]{3})/.exec(s)) {
  323. var b = parts[1].charCodeAt(0)
  324. , x = parts[1].charCodeAt(1)
  325. , y = parts[1].charCodeAt(2)
  326. , mod;
  327. key.name = 'mouse';
  328. key.type = 'X10';
  329. key.raw = [b, x, y, parts[0]];
  330. key.x = x - 32;
  331. key.y = y - 32;
  332. if (key.x === -32) key.x = 255;
  333. if (key.y === -32) key.y = 255;
  334. if (this.zero) key.x--, key.y--;
  335. mod = b >> 2;
  336. key.shift = !!(mod & 1);
  337. key.meta = !!((mod >> 1) & 1);
  338. key.ctrl = !!((mod >> 2) & 1);
  339. b -= 32;
  340. if ((b >> 6) & 1) {
  341. key.action = b & 1 ? 'wheeldown' : 'wheelup';
  342. key.button = 'middle';
  343. } else if (b === 3) {
  344. // Could also be a movement.
  345. key.action = 'mouseup';
  346. key.button = 'unknown';
  347. } else {
  348. key.action = 'mousedown';
  349. key.button =
  350. b === 0 ? 'left'
  351. : b === 1 ? 'middle'
  352. : b === 2 ? 'right'
  353. : 'unknown';
  354. }
  355. // It's a movement
  356. // Wrong
  357. //if (b > 32 && b < 64) {
  358. // delete key.button;
  359. // key.action = 'mousemove';
  360. //}
  361. // Probably a movement.
  362. if (key.action === 'mousedown' && key.button === 'unknown') {
  363. delete key.button;
  364. key.action = 'mousemove';
  365. }
  366. self.emit('keypress', null, key);
  367. self.emit('mouse', key);
  368. return;
  369. }
  370. // URxvt
  371. if (parts = /^\x1b\[(\d+;\d+;\d+)M/.exec(s)) {
  372. var parts = parts[1].split(';')
  373. , b = +parts[0]
  374. , x = +parts[1]
  375. , y = +parts[2];
  376. key.name = 'mouse';
  377. key.type = 'urxvt';
  378. key.x = x;
  379. key.y = y;
  380. if (this.zero) key.x--, key.y--;
  381. // NOTE: Duplicate of the above.
  382. mod = b >> 3;
  383. key.shift = mod & 4;
  384. key.meta = mod & 8;
  385. key.ctrl = mod & 16;
  386. b -= 32;
  387. if (b === 64) {
  388. key.action = 'wheelup';
  389. key.button = 'middle';
  390. } else if (b === 65) {
  391. key.action = 'wheeldown';
  392. key.button = 'middle';
  393. } else if (b === 3) {
  394. // Could also be a movement.
  395. key.action = 'mouseup';
  396. key.button = 'unknown';
  397. } else {
  398. key.action = 'mousedown';
  399. key.button =
  400. b === 0 ? 'left'
  401. : b === 1 ? 'middle'
  402. : b === 2 ? 'right'
  403. : 'unknown';
  404. }
  405. // It's a movement
  406. // Wrong
  407. //if (b > 32 && b < 64) {
  408. // delete key.button;
  409. // key.action = 'movement';
  410. //}
  411. // Probably a movement.
  412. if (key.action === 'mousedown' && key.button === 'unknown') {
  413. delete key.button;
  414. key.action = 'mousemove';
  415. }
  416. self.emit('keypress', null, key);
  417. self.emit('mouse', key);
  418. return;
  419. }
  420. // SGR
  421. if (parts = /^\x1b\[<(\d+;\d+;\d+)([mM])/.exec(s)) {
  422. var down = parts[2] === 'm'
  423. , parts = parts[1].split(';')
  424. , b = +parts[0]
  425. , x = +parts[1]
  426. , y = +parts[2];
  427. key.name = 'mouse';
  428. key.type = 'sgr';
  429. key.x = x;
  430. key.y = y;
  431. if (this.zero) key.x--, key.y--;
  432. b &= 3;
  433. // NOTE: Get mod. And wheel.
  434. key.action = down
  435. ? 'mousedown'
  436. : 'mouseup';
  437. key.button =
  438. b === 0 ? 'left'
  439. : b === 1 ? 'middle'
  440. : b === 2 ? 'right'
  441. : 'unknown';
  442. self.emit('keypress', null, key);
  443. self.emit('mouse', key);
  444. return;
  445. }
  446. // DEC
  447. // The xterm mouse documentation says there is a
  448. // `<` prefix, the DECRQLP says there is no prefix.
  449. if (parts = /^\x1b\[<(\d+;\d+;\d+;\d+)&w/.exec(s)) {
  450. var parts = parts[1].split(';')
  451. , b = +parts[0]
  452. , x = +parts[1]
  453. , y = +parts[2]
  454. , page = +parts[3];
  455. key.name = 'mouse';
  456. key.type = 'dec';
  457. key.button = b;
  458. key.x = x;
  459. key.y = y;
  460. if (this.zero) key.x--, key.y--;
  461. key.action = b === 3
  462. ? 'mouseup'
  463. : 'mousedown';
  464. key.button =
  465. b === 2 ? 'left'
  466. : b === 4 ? 'middle'
  467. : b === 6 ? 'right'
  468. : 'unknown';
  469. self.emit('keypress', null, key);
  470. self.emit('mouse', key);
  471. return;
  472. }
  473. // vt300
  474. if (parts = /^\x1b\[24([0135])~\[(\d+),(\d+)\]\r/.exec(s)) {
  475. var b = +parts[1]
  476. , x = +parts[2]
  477. , y = +parts[3];
  478. key.name = 'mouse';
  479. key.type = 'vt300';
  480. key.x = x;
  481. key.y = y;
  482. if (this.zero) key.x--, key.y--;
  483. key.action = 'mousedown';
  484. key.button =
  485. b === 1 ? 'left'
  486. : b === 2 ? 'middle'
  487. : b === 5 ? 'right'
  488. : 'unknown';
  489. self.emit('keypress', null, key);
  490. self.emit('mouse', key);
  491. return;
  492. }
  493. if (parts = /^\x1b\[(O|I)/.exec(s)) {
  494. key.action = parts[1] === 'I'
  495. ? 'focus'
  496. : 'blur';
  497. self.emit('mouse', key);
  498. self.emit(key.action);
  499. return;
  500. }
  501. };
  502. // All possible responses from the terminal
  503. Program.prototype.bindResponse = function() {
  504. if (this._boundResponse) return;
  505. this._boundResponse = true;
  506. var decoder = new StringDecoder('utf8')
  507. , self = this;
  508. this.on('data', function(data) {
  509. data = decoder.write(data);
  510. if (!data) return;
  511. self._bindResponse(data);
  512. });
  513. };
  514. Program.prototype._bindResponse = function(s) {
  515. var self = this
  516. , out = {}
  517. , parts;
  518. if (Buffer.isBuffer(s)) {
  519. if (s[0] > 127 && s[1] === undefined) {
  520. s[0] -= 128;
  521. s = '\x1b' + s.toString('utf-8');
  522. } else {
  523. s = s.toString('utf-8');
  524. }
  525. }
  526. // CSI P s c
  527. // Send Device Attributes (Primary DA).
  528. // CSI > P s c
  529. // Send Device Attributes (Secondary DA).
  530. if (parts = /^\x1b\[(\?|>)(\d*(?:;\d*)*)c/.exec(s)) {
  531. parts = parts[2].split(';').map(function(ch) {
  532. return +ch || 0;
  533. });
  534. out.event = 'device-attributes';
  535. out.code = 'DA';
  536. if (parts[1] === '?') {
  537. out.type = 'primary-attribute';
  538. // VT100-style params:
  539. if (parts[0] === 1 && parts[2] === 2) {
  540. out.term = 'vt100';
  541. out.advancedVideo = true;
  542. } else if (parts[0] === 1 && parts[2] === 0) {
  543. out.term = 'vt101';
  544. } else if (parts[0] === 6) {
  545. out.term = 'vt102';
  546. } else if (parts[0] === 60
  547. && parts[1] === 1 && parts[2] === 2
  548. && parts[3] === 6 && parts[4] === 8
  549. && parts[5] === 9 && parts[6] === 15) {
  550. out.term = 'vt220';
  551. } else {
  552. // VT200-style params:
  553. parts.forEach(function(attr) {
  554. switch (attr) {
  555. case 1:
  556. out.cols132 = true;
  557. break;
  558. case 2:
  559. out.printer = true;
  560. break;
  561. case 6:
  562. out.selectiveErase = true;
  563. break;
  564. case 8:
  565. out.userDefinedKeys = true;
  566. break;
  567. case 9:
  568. out.nationalReplacementCharsets = true;
  569. break;
  570. case 15:
  571. out.technicalCharacters = true;
  572. break;
  573. case 18:
  574. out.userWindows = true;
  575. break;
  576. case 21:
  577. out.horizontalScrolling = true;
  578. break;
  579. case 22:
  580. out.ansiColor = true;
  581. break;
  582. case 29:
  583. out.ansiTextLocator = true;
  584. break;
  585. }
  586. });
  587. }
  588. } else {
  589. out.type = 'secondary-attribute';
  590. switch (parts[0]) {
  591. case 0:
  592. out.term = 'vt100';
  593. break;
  594. case 1:
  595. out.term = 'vt220';
  596. break;
  597. case 2:
  598. out.term = 'vt240';
  599. break;
  600. case 18:
  601. out.term = 'vt330';
  602. break;
  603. case 19:
  604. out.term = 'vt340';
  605. break;
  606. case 24:
  607. out.term = 'vt320';
  608. break;
  609. case 41:
  610. out.term = 'vt420';
  611. break;
  612. case 61:
  613. out.term = 'vt510';
  614. break;
  615. case 64:
  616. out.term = 'vt520';
  617. break;
  618. case 65:
  619. out.term = 'vt525';
  620. break;
  621. }
  622. out.firmwareVersion = parts[1];
  623. out.romCartridgeRegistrationNumber = parts[2];
  624. }
  625. // LEGACY
  626. out.deviceAttributes = out;
  627. this.emit('response', out);
  628. this.emit('response ' + out.event, out);
  629. return;
  630. }
  631. // CSI Ps n Device Status Report (DSR).
  632. // Ps = 5 -> Status Report. Result (``OK'') is
  633. // CSI 0 n
  634. // CSI ? Ps n
  635. // Device Status Report (DSR, DEC-specific).
  636. // Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).
  637. // or CSI ? 1 1 n (not ready).
  638. // Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)
  639. // or CSI ? 2 1 n (locked).
  640. // Ps = 2 6 -> Report Keyboard status as
  641. // CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
  642. // The last two parameters apply to VT400 & up, and denote key-
  643. // board ready and LK01 respectively.
  644. // Ps = 5 3 -> Report Locator status as
  645. // CSI ? 5 3 n Locator available, if compiled-in, or
  646. // CSI ? 5 0 n No Locator, if not.
  647. if (parts = /^\x1b\[(\?)?(\d+)(?:;(\d+);(\d+);(\d+))?n/.exec(s)) {
  648. out.event = 'device-status';
  649. out.code = 'DSR';
  650. if (!parts[1] && parts[2] === '0' && !parts[3]) {
  651. out.type = 'device-status';
  652. out.status = 'OK';
  653. // LEGACY
  654. out.deviceStatus = out.status;
  655. this.emit('response', out);
  656. this.emit('response ' + out.event, out);
  657. return;
  658. }
  659. if (parts[1] && (parts[2] === '10' || parts[2] === '11') && !parts[3]) {
  660. out.type = 'printer-status';
  661. out.status = parts[2] === '10'
  662. ? 'ready'
  663. : 'not ready';
  664. // LEGACY
  665. out.printerStatus = out.status;
  666. this.emit('response', out);
  667. this.emit('response ' + out.event, out);
  668. return;
  669. }
  670. if (parts[1] && (parts[2] === '20' || parts[2] === '21') && !parts[3]) {
  671. out.type = 'udk-status';
  672. out.status = parts[2] === '20'
  673. ? 'unlocked'
  674. : 'locked';
  675. // LEGACY
  676. out.UDKStatus = out.status;
  677. this.emit('response', out);
  678. this.emit('response ' + out.event, out);
  679. return;
  680. }
  681. if (parts[1]
  682. && parts[2] === '27'
  683. && parts[3] === '1'
  684. && parts[4] === '0'
  685. && parts[5] === '0') {
  686. out.type = 'keyboard-status';
  687. out.status = 'OK';
  688. // LEGACY
  689. out.keyboardStatus = out.status;
  690. this.emit('response', out);
  691. this.emit('response ' + out.event, out);
  692. return;
  693. }
  694. if (parts[1] && (parts[2] === '53' || parts[2] === '50') && !parts[3]) {
  695. out.type = 'locator-status';
  696. out.status = parts[2] === '53'
  697. ? 'available'
  698. : 'unavailable';
  699. // LEGACY
  700. out.locator = out.status;
  701. this.emit('response', out);
  702. this.emit('response ' + out.event, out);
  703. return;
  704. }
  705. out.type = 'error';
  706. out.text = 'Unhandled: ' + JSON.stringify(parts);
  707. // LEGACY
  708. out.error = out.text;
  709. this.emit('response', out);
  710. this.emit('response ' + out.event, out);
  711. return;
  712. }
  713. // CSI Ps n Device Status Report (DSR).
  714. // Ps = 6 -> Report Cursor Position (CPR) [row;column].
  715. // Result is
  716. // CSI r ; c R
  717. // CSI ? Ps n
  718. // Device Status Report (DSR, DEC-specific).
  719. // Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI
  720. // ? r ; c R (assumes page is zero).
  721. if (parts = /^\x1b\[(\?)?(\d+);(\d+)R/.exec(s)) {
  722. out.event = 'device-status-report';
  723. out.code = 'DSR';
  724. out.type = 'cursor-status';
  725. out.status = {
  726. x: +parts[3],
  727. y: +parts[2],
  728. page: !parts[1] ? undefined : 0
  729. };
  730. out.x = out.status.x;
  731. out.y = out.status.y;
  732. out.page = out.status.page;
  733. // LEGACY
  734. out.cursor = out.status;
  735. this.emit('response', out);
  736. this.emit('response ' + out.event, out);
  737. return;
  738. }
  739. // CSI Ps ; Ps ; Ps t
  740. // Window manipulation (from dtterm, as well as extensions).
  741. // These controls may be disabled using the allowWindowOps
  742. // resource. Valid values for the first (and any additional
  743. // parameters) are:
  744. // Ps = 1 1 -> Report xterm window state. If the xterm window
  745. // is open (non-iconified), it returns CSI 1 t . If the xterm
  746. // window is iconified, it returns CSI 2 t .
  747. // Ps = 1 3 -> Report xterm window position. Result is CSI 3
  748. // ; x ; y t
  749. // Ps = 1 4 -> Report xterm window in pixels. Result is CSI
  750. // 4 ; height ; width t
  751. // Ps = 1 8 -> Report the size of the text area in characters.
  752. // Result is CSI 8 ; height ; width t
  753. // Ps = 1 9 -> Report the size of the screen in characters.
  754. // Result is CSI 9 ; height ; width t
  755. if (parts = /^\x1b\[(\d+)(?:;(\d+);(\d+))?t/.exec(s)) {
  756. out.event = 'window-manipulation';
  757. out.code = '';
  758. if ((parts[1] === '1' || parts[1] === '2') && !parts[2]) {
  759. out.type = 'window-state';
  760. out.state = parts[1] === '1'
  761. ? 'non-iconified'
  762. : 'iconified'
  763. // LEGACY
  764. out.windowState = out.state;
  765. this.emit('response', out);
  766. this.emit('response ' + out.event, out);
  767. return;
  768. }
  769. if (parts[1] === '3' && parts[2]) {
  770. out.type = 'window-position';
  771. out.position = {
  772. x: +parts[2],
  773. y: +parts[3]
  774. };
  775. out.x = out.position.x;
  776. out.y = out.position.y;
  777. // LEGACY
  778. out.windowPosition = out.position;
  779. this.emit('response', out);
  780. this.emit('response ' + out.event, out);
  781. return;
  782. }
  783. if (parts[1] === '4' && parts[2]) {
  784. out.type = 'window-size-pixels';
  785. out.size = {
  786. height: +parts[2],
  787. width: +parts[3]
  788. };
  789. out.height = out.size.height;
  790. out.width = out.size.width;
  791. // LEGACY
  792. out.windowSizePixels = out.size;
  793. this.emit('response', out);
  794. this.emit('response ' + out.event, out);
  795. return;
  796. }
  797. if (parts[1] === '8' && parts[2]) {
  798. out.type = 'textarea-size';
  799. out.size = {
  800. height: +parts[2],
  801. width: +parts[3]
  802. };
  803. out.height = out.size.height;
  804. out.width = out.size.width;
  805. // LEGACY
  806. out.textAreaSizeCharacters = out.size;
  807. this.emit('response', out);
  808. this.emit('response ' + out.event, out);
  809. return;
  810. }
  811. if (parts[1] === '9' && parts[2]) {
  812. out.type = 'screen-size';
  813. out.size = {
  814. height: +parts[2],
  815. width: +parts[3]
  816. };
  817. out.height = out.size.height;
  818. out.width = out.size.width;
  819. // LEGACY
  820. out.screenSizeCharacters = out.size;
  821. this.emit('response', out);
  822. this.emit('response ' + out.event, out);
  823. return;
  824. }
  825. out.type = 'error';
  826. out.text = 'Unhandled: ' + JSON.stringify(parts);
  827. // LEGACY
  828. out.error = out.text;
  829. this.emit('response', out);
  830. this.emit('response ' + out.event, out);
  831. return;
  832. }
  833. // CSI Ps ; Ps ; Ps t
  834. // Window manipulation (from dtterm, as well as extensions).
  835. // These controls may be disabled using the allowWindowOps
  836. // resource. Valid values for the first (and any additional
  837. // parameters) are:
  838. // Ps = 2 0 -> Report xterm window's icon label. Result is
  839. // OSC L label ST
  840. // Ps = 2 1 -> Report xterm window's title. Result is OSC l
  841. // label ST
  842. if (parts = /^\x1b\](l|L)([^\x07\x1b]*)(?:\x07|\x1b\\)/.exec(s)) {
  843. out.type = 'window-manipulation';
  844. out.code = '';
  845. if (parts[1] === 'L') {
  846. out.type = 'window-icon-label';
  847. out.text = parts[2];
  848. // LEGACY
  849. out.windowIconLabel = out.text;
  850. this.emit('response', out);
  851. this.emit('response ' + out.event, out);
  852. return;
  853. }
  854. if (parts[1] === 'l') {
  855. out.type = 'window-title';
  856. out.text = parts[2];
  857. // LEGACY
  858. out.windowTitle = out.text;
  859. this.emit('response', out);
  860. this.emit('response ' + out.event, out);
  861. return;
  862. }
  863. out.type = 'error';
  864. out.text = 'Unhandled: ' + JSON.stringify(parts);
  865. // LEGACY
  866. out.error = out.text;
  867. this.emit('response', out);
  868. this.emit('response ' + out.event, out);
  869. return;
  870. }
  871. // CSI Ps ' |
  872. // Request Locator Position (DECRQLP).
  873. // -> CSI Pe ; Pb ; Pr ; Pc ; Pp & w
  874. // Parameters are [event;button;row;column;page].
  875. // Valid values for the event:
  876. // Pe = 0 -> locator unavailable - no other parameters sent.
  877. // Pe = 1 -> request - xterm received a DECRQLP.
  878. // Pe = 2 -> left button down.
  879. // Pe = 3 -> left button up.
  880. // Pe = 4 -> middle button down.
  881. // Pe = 5 -> middle button up.
  882. // Pe = 6 -> right button down.
  883. // Pe = 7 -> right button up.
  884. // Pe = 8 -> M4 button down.
  885. // Pe = 9 -> M4 button up.
  886. // Pe = 1 0 -> locator outside filter rectangle.
  887. // ``button'' parameter is a bitmask indicating which buttons are
  888. // pressed:
  889. // Pb = 0 <- no buttons down.
  890. // Pb & 1 <- right button down.
  891. // Pb & 2 <- middle button down.
  892. // Pb & 4 <- left button down.
  893. // Pb & 8 <- M4 button down.
  894. // ``row'' and ``column'' parameters are the coordinates of the
  895. // locator position in the xterm window, encoded as ASCII deci-
  896. // mal.
  897. // The ``page'' parameter is not used by xterm, and will be omit-
  898. // ted.
  899. // NOTE:
  900. // This is already implemented in the _bindMouse
  901. // method, but it might make more sense here.
  902. // The xterm mouse documentation says there is a
  903. // `<` prefix, the DECRQLP says there is no prefix.
  904. if (parts = /^\x1b\[(\d+(?:;\d+){4})&w/.exec(s)) {
  905. parts = parts[1].split(';').map(function(ch) {
  906. return +ch;
  907. });
  908. out.event = 'locator-position';
  909. out.code = 'DECRQLP';
  910. switch (parts[0]) {
  911. case 0:
  912. out.status = 'locator-unavailable';
  913. break;
  914. case 1:
  915. out.status = 'request';
  916. break;
  917. case 2:
  918. out.status = 'left-button-down';
  919. break;
  920. case 3:
  921. out.status = 'left-button-up';
  922. break;
  923. case 4:
  924. out.status = 'middle-button-down';
  925. break;
  926. case 5:
  927. out.status = 'middle-button-up';
  928. break;
  929. case 6:
  930. out.status = 'right-button-down';
  931. break;
  932. case 7:
  933. out.status = 'right-button-up';
  934. break;
  935. case 8:
  936. out.status = 'm4-button-down';
  937. break;
  938. case 9:
  939. out.status = 'm4-button-up';
  940. break;
  941. case 10:
  942. out.status = 'locator-outside';
  943. break;
  944. }
  945. out.mask = parts[1];
  946. out.row = parts[2];
  947. out.col = parts[3];
  948. out.page = parts[4];
  949. // LEGACY
  950. out.locatorPosition = out;
  951. this.emit('response', out);
  952. this.emit('response ' + out.event, out);
  953. return;
  954. }
  955. };
  956. Program.prototype.receive = function(text, callback) {
  957. var listeners = (this._events && this._events['keypress']) || []
  958. , bak = listeners.slice()
  959. , self = this;
  960. if (!this.input.isRaw) {
  961. throw new Error('Input must be raw.');
  962. }
  963. listeners.length = 0;
  964. if (!callback) {
  965. callback = text;
  966. text = null;
  967. }
  968. this.input.once('data', function(data) {
  969. listeners.push.apply(listeners, bak);
  970. if (typeof data !== 'string') {
  971. data = data.toString('utf8');
  972. }
  973. return callback(null, data);
  974. });
  975. if (text != null) {
  976. return this._write(text);
  977. }
  978. };
  979. Program.prototype.response = function(name, text, callback) {
  980. if (arguments.length === 2) {
  981. callback = text;
  982. text = name;
  983. name = null;
  984. }
  985. if (!callback) {
  986. callback = function() {};
  987. }
  988. this.bindResponse();
  989. name = name
  990. ? 'response ' + name
  991. : 'response';
  992. this.once(name, function(event) {
  993. if (event.type === 'error') {
  994. return callback(new Error(event.event + ': ' + event.text));
  995. }
  996. return callback(null, event);
  997. });
  998. return this._write(text);
  999. };
  1000. Program.prototype._wrapCursor = function(nl) {
  1001. if (this.tput) {
  1002. //if (this.tput.bools.eat_newline_glitch) {
  1003. // return;
  1004. //}
  1005. if (this.tput.bools.auto_right_margin) {
  1006. this.x = 0;
  1007. this.y++;
  1008. return;
  1009. }
  1010. this.x--;
  1011. return;
  1012. }
  1013. this.x = 0;
  1014. this.y++;
  1015. };
  1016. // TODO: Make a private write for all of the methods here
  1017. // which does not call _parseChar. Make public write call
  1018. // _parseChar.
  1019. Program.prototype._parseChar = function(text, attr) {
  1020. for (var i = 0; i < text.length; i++) {
  1021. var cs = false;
  1022. while (text[i] === '\x1b') {
  1023. i++;
  1024. if (text[i] === '[' || text === ']') {
  1025. cs = true;
  1026. continue;
  1027. }
  1028. if (cs && (text[i] === ';' || (text[i] >= '0' && text[i] <= '9'))) {
  1029. continue;
  1030. }
  1031. if (text[i] === '\\' && text[i + 1] === '\x1b') i++;
  1032. i++;
  1033. cs = false;
  1034. break;
  1035. }
  1036. if (text[i] === '\n') {
  1037. if (this.tput
  1038. && this.tput.bools.eat_newline_glitch
  1039. && this.x >= this.cols) {
  1040. ;
  1041. } else {
  1042. this.x = 0;
  1043. this.y++;
  1044. }
  1045. } else if (text[i] === '\r') {
  1046. this.x = 0;
  1047. } else {
  1048. this.x++;
  1049. if (this.x >= this.cols) {
  1050. this._wrapCursor();
  1051. }
  1052. }
  1053. }
  1054. };
  1055. Program.prototype._buffer = function(text) {
  1056. if (this._exiting) {
  1057. this.flush();
  1058. this.output.write(text);
  1059. return;
  1060. }
  1061. if (this._buf) {
  1062. this._buf += text;
  1063. return;
  1064. }
  1065. this._buf = text;
  1066. process.nextTick(this._flush);
  1067. };
  1068. Program.prototype.flush = function(text) {
  1069. if (!this._buf) return;
  1070. this.output.write(this._buf);
  1071. this._buf = '';
  1072. };
  1073. Program.prototype._write = function(text) {
  1074. if (this.ret) return text;
  1075. if (this.useBuffer) {
  1076. return this._buffer(text);
  1077. }
  1078. return this.output.write(text);
  1079. };
  1080. Program.prototype.echo =
  1081. Program.prototype.write = function(text, attr) {
  1082. return attr
  1083. ? this._write(this.text(text, attr))
  1084. : this._write(text);
  1085. };
  1086. Program.prototype._ncoords = function() {
  1087. if (this.x < 0) this.x = 0;
  1088. else if (this.x >= this.cols) this.x = this.cols - 1;
  1089. if (this.y < 0) this.y = 0;
  1090. else if (this.y >= this.rows) this.y = this.rows - 1;
  1091. };
  1092. Program.prototype.setx = function(x) {
  1093. return this.cursorCharAbsolute(x);
  1094. // return this.charPosAbsolute(x);
  1095. };
  1096. Program.prototype.sety = function(y) {
  1097. return this.linePosAbsolute(y);
  1098. };
  1099. Program.prototype.move = function(x, y) {
  1100. return this.cursorPos(y, x);
  1101. };
  1102. // TODO: Fix cud and cuu calls.
  1103. Program.prototype.omove = function(x, y) {
  1104. if (!this.zero) {
  1105. x = (x || 1) - 1;
  1106. y = (y || 1) - 1;
  1107. } else {
  1108. x = x || 0;
  1109. y = y || 0;
  1110. }
  1111. if (y === this.y && x === this.x) {
  1112. return;
  1113. }
  1114. if (y === this.y) {
  1115. if (x > this.x) {
  1116. this.cuf(x - this.x);
  1117. } else if (x < this.x) {
  1118. this.cub(this.x - x);
  1119. }
  1120. } else if (x === this.x) {
  1121. if (y > this.y) {
  1122. this.cud(y - this.y);
  1123. } else if (y < this.y) {
  1124. this.cuu(this.y - y);
  1125. }
  1126. } else {
  1127. if (!this.zero) x++, y++;
  1128. this.cup(y, x);
  1129. }
  1130. };
  1131. Program.prototype.rsetx = function(x) {
  1132. // return this.HPositionRelative(x);
  1133. if (!x) return;
  1134. return x > 0
  1135. ? this.forward(x)
  1136. : this.back(-x);
  1137. };
  1138. Program.prototype.rsety = function(y) {
  1139. // return this.VPositionRelative(y);
  1140. if (!y) return;
  1141. return y > 0
  1142. ? this.up(y)
  1143. : this.down(-y);
  1144. };
  1145. Program.prototype.rmove = function(x, y) {
  1146. this.rsetx(x);
  1147. this.rsety(y);
  1148. };
  1149. Program.prototype.simpleInsert = function(ch, i, attr) {
  1150. return this._write(this.repeat(ch, i), attr);
  1151. };
  1152. Program.prototype.repeat = function(ch, i) {
  1153. if (!(i >= 0)) i = 0;
  1154. return Array(i + 1).join(ch);
  1155. };
  1156. /**
  1157. * Normal
  1158. */
  1159. //Program.prototype.pad =
  1160. Program.prototype.nul = function() {
  1161. //if (this.has('pad')) return this.put.pad();
  1162. return this._write('\200');
  1163. };
  1164. Program.prototype.bel =
  1165. Program.prototype.bell = function() {
  1166. if (this.has('bel')) return this.put.bel();
  1167. return this._write('\x07');
  1168. };
  1169. Program.prototype.vtab = function() {
  1170. this.y++;
  1171. this._ncoords();
  1172. return this._write('\x0b');
  1173. };
  1174. Program.prototype.ff =
  1175. Program.prototype.form = function() {
  1176. if (this.has('ff')) return this.put.ff();
  1177. return this._write('\x0c');
  1178. };
  1179. Program.prototype.kbs =
  1180. Program.prototype.backspace = function() {
  1181. this.x--;
  1182. this._ncoords();
  1183. if (this.has('kbs')) return this.put.kbs();
  1184. return this._write('\x08');
  1185. };
  1186. Program.prototype.ht =
  1187. Program.prototype.tab = function() {
  1188. this.x += 8;
  1189. this._ncoords();
  1190. if (this.has('ht')) return this.put.ht();
  1191. return this._write('\t');
  1192. };
  1193. Program.prototype.shiftOut = function() {
  1194. // if (this.has('S2')) return this.put.S2();
  1195. return this._write('\x0e');
  1196. };
  1197. Program.prototype.shiftIn = function() {
  1198. // if (this.has('S3')) return this.put.S3();
  1199. return this._write('\x0f');
  1200. };
  1201. Program.prototype.cr =
  1202. Program.prototype.return = function() {
  1203. this.x = 0;
  1204. if (this.has('cr')) return this.put.cr();
  1205. return this._write('\r');
  1206. };
  1207. Program.prototype.nel =
  1208. Program.prototype.newline =
  1209. Program.prototype.feed = function() {
  1210. if (this.tput && this.tput.bools.eat_newline_glitch && this.x >= this.cols) {
  1211. return;
  1212. }
  1213. this.x = 0;
  1214. this.y++;
  1215. this._ncoords();
  1216. if (this.has('nel')) return this.put.nel();
  1217. return this._write('\n');
  1218. };
  1219. /**
  1220. * Esc
  1221. */
  1222. // ESC D Index (IND is 0x84).
  1223. Program.prototype.ind =
  1224. Program.prototype.index = function() {
  1225. this.y++;
  1226. this._ncoords();
  1227. if (this.tput) return this.put.ind();
  1228. return this._write('\x1bD');
  1229. };
  1230. // ESC M Reverse Index (RI is 0x8d).
  1231. Program.prototype.ri =
  1232. Program.prototype.reverse =
  1233. Program.prototype.reverseIndex = function() {
  1234. this.y--;
  1235. this._ncoords();
  1236. if (this.tput) return this.put.ri();
  1237. return this._write('\x1bM');
  1238. };
  1239. // ESC E Next Line (NEL is 0x85).
  1240. Program.prototype.nextLine = function() {
  1241. this.y++;
  1242. this.x = 0;
  1243. this._ncoords();
  1244. if (this.has('nel')) return this.put.nel();
  1245. return this._write('\x1bE');
  1246. };
  1247. // ESC c Full Reset (RIS).
  1248. Program.prototype.reset = function() {
  1249. this.x = this.y = 0;
  1250. if (this.has('rs1') || this.has('ris')) {
  1251. return this.has('rs1')
  1252. ? this.put.rs1()
  1253. : this.put.ris();
  1254. }
  1255. return this._write('\x1bc');
  1256. };
  1257. // ESC H Tab Set (HTS is 0x88).
  1258. Program.prototype.tabSet = function() {
  1259. if (this.tput) return this.put.hts();
  1260. return this._write('\x1bH');
  1261. };
  1262. // ESC 7 Save Cursor (DECSC).
  1263. Program.prototype.sc =
  1264. Program.prototype.saveCursor = function(key) {
  1265. if (key) return this.lsaveCursor(key);
  1266. this.savedX = this.x || 0;
  1267. this.savedY = this.y || 0;
  1268. if (this.tput) return this.put.sc();
  1269. return this._write('\x1b7');
  1270. };
  1271. // ESC 8 Restore Cursor (DECRC).
  1272. Program.prototype.rc =
  1273. Program.prototype.restoreCursor = function(key, hide) {
  1274. if (key) return this.lrestoreCursor(key, hide);
  1275. this.x = this.savedX || 0;
  1276. this.y = this.savedY || 0;
  1277. if (this.tput) return this.put.rc();
  1278. return this._write('\x1b8');
  1279. };
  1280. // Save Cursor Locally
  1281. Program.prototype.lsaveCursor = function(key) {
  1282. var key = key || 'local';
  1283. this._saved = this._saved || {};
  1284. this._saved[key] = this._saved[key] || {};
  1285. this._saved[key].x = this.x;
  1286. this._saved[key].y = this.y;
  1287. this._saved[key].hidden = this.cursorHidden;
  1288. };
  1289. // Restore Cursor Locally
  1290. Program.prototype.lrestoreCursor = function(key, hide) {
  1291. var key = key || 'local', pos;
  1292. if (!this._saved || !this._saved[key]) return;
  1293. pos = this._saved[key];
  1294. //delete this._saved[key];
  1295. this.cup(pos.y, pos.x);
  1296. if (hide && pos.hidden !== this.cursorHidden) {
  1297. if (pos.hidden) {
  1298. this.hideCursor();
  1299. } else {
  1300. this.showCursor();
  1301. }
  1302. }
  1303. };
  1304. // ESC # 3 DEC line height/width
  1305. Program.prototype.lineHeight = function() {
  1306. return this._write('\x1b#');
  1307. };
  1308. // ESC (,),*,+,-,. Designate G0-G2 Character Set.
  1309. Program.prototype.charset = function(val, level) {
  1310. level = level || 0;
  1311. // See also:
  1312. // acs_chars / acsc / ac
  1313. // enter_alt_charset_mode / smacs / as
  1314. // exit_alt_charset_mode / rmacs / ae
  1315. // enter_pc_charset_mode / smpch / S2
  1316. // exit_pc_charset_mode / rmpch / S3
  1317. switch (level) {
  1318. case 0:
  1319. level = '(';
  1320. break;
  1321. case 1:
  1322. level = ')';
  1323. break;
  1324. case 2:
  1325. level = '*';
  1326. break;
  1327. case 3:
  1328. level = '+';
  1329. break;
  1330. }
  1331. var name = typeof val === 'string'
  1332. ? val.toLowerCase()
  1333. : val;
  1334. switch (name) {
  1335. case 'acs':
  1336. case 'scld': // DEC Special Character and Line Drawing Set.
  1337. if (this.tput) return this.put.smacs();
  1338. val = '0';
  1339. break;
  1340. case 'uk': // UK
  1341. val = 'A';
  1342. break;
  1343. case 'us': // United States (USASCII).
  1344. case 'usascii':
  1345. case 'ascii':
  1346. if (this.tput) return this.put.rmacs();
  1347. val = 'B';
  1348. break;
  1349. case 'dutch': // Dutch
  1350. val = '4';
  1351. break;
  1352. case 'finnish': // Finnish
  1353. val = 'C';
  1354. val = '5';
  1355. break;
  1356. case 'french': // French
  1357. val = 'R';
  1358. break;
  1359. case 'frenchcanadian': // FrenchCanadian
  1360. val = 'Q';
  1361. break;
  1362. case 'german': // German
  1363. val = 'K';
  1364. break;
  1365. case 'italian': // Italian
  1366. val = 'Y';
  1367. break;
  1368. case 'norwegiandanish': // NorwegianDanish
  1369. val = 'E';
  1370. val = '6';
  1371. break;
  1372. case 'spanish': // Spanish
  1373. val = 'Z';
  1374. break;
  1375. case 'swedish': // Swedish
  1376. val = 'H';
  1377. val = '7';
  1378. break;
  1379. case 'swiss': // Swiss
  1380. val = '=';
  1381. break;
  1382. case 'isolatin': // ISOLatin (actually /A)
  1383. val = '/A';
  1384. break;
  1385. default: // Default
  1386. if (this.tput) return this.put.rmacs();
  1387. val = 'B';
  1388. break;
  1389. }
  1390. return this._write('\x1b(' + val);
  1391. };
  1392. Program.prototype.enter_alt_charset_mode =
  1393. Program.prototype.as =
  1394. Program.prototype.smacs = function() {
  1395. return this.charset('acs');
  1396. };
  1397. Program.prototype.exit_alt_charset_mode =
  1398. Program.prototype.ae =
  1399. Program.prototype.rmacs = function() {
  1400. return this.charset('ascii');
  1401. };
  1402. // ESC N
  1403. // Single Shift Select of G2 Character Set
  1404. // ( SS2 is 0x8e). This affects next character only.
  1405. // ESC O
  1406. // Single Shift Select of G3 Character Set
  1407. // ( SS3 is 0x8f). This affects next character only.
  1408. // ESC n
  1409. // Invoke the G2 Character Set as GL (LS2).
  1410. // ESC o
  1411. // Invoke the G3 Character Set as GL (LS3).
  1412. // ESC |
  1413. // Invoke the G3 Character Set as GR (LS3R).
  1414. // ESC }
  1415. // Invoke the G2 Character Set as GR (LS2R).
  1416. // ESC ~
  1417. // Invoke the G1 Character Set as GR (LS1R).
  1418. Program.prototype.setG = function(val) {
  1419. // if (this.tput) return this.put.S2();
  1420. // if (this.tput) return this.put.S3();
  1421. switch (val) {
  1422. case 1:
  1423. val = '~'; // GR
  1424. break;
  1425. case 2:
  1426. val = 'n'; // GL
  1427. val = '}'; // GR
  1428. val = 'N'; // Next Char Only
  1429. break;
  1430. case 3:
  1431. val = 'o'; // GL
  1432. val = '|'; // GR
  1433. val = 'O'; // Next Char Only
  1434. break;
  1435. }
  1436. return this._write('\x1b' + val);
  1437. };
  1438. /**
  1439. * OSC
  1440. */
  1441. // OSC Ps ; Pt ST
  1442. // OSC Ps ; Pt BEL
  1443. // Set Text Parameters.
  1444. Program.prototype.setTitle = function(title) {
  1445. if (this.term('screen')) {
  1446. // Tmux pane
  1447. // if (process.env.TMUX) {
  1448. // return this._write('\x1b]2;' + title + '\x1b\\');
  1449. // }
  1450. return this._write('\x1bk' + title + '\x1b\\');
  1451. }
  1452. return this._write('\x1b]0;' + title + '\x07');
  1453. };
  1454. // OSC Ps ; Pt ST
  1455. // OSC Ps ; Pt BEL
  1456. // Reset colors
  1457. Program.prototype.resetColors = function(param) {
  1458. if (this.has('Cr')) {
  1459. return this.put.Cr(param);
  1460. }
  1461. return this._write('\x1b]112\x07');
  1462. //return this._write('\x1b]112;' + param + '\x07');
  1463. };
  1464. // OSC Ps ; Pt ST
  1465. // OSC Ps ; Pt BEL
  1466. // Change dynamic colors
  1467. Program.prototype.dynamicColors = function(param) {
  1468. if (this.has('Cs')) {
  1469. return this.put.Cs(param);
  1470. }
  1471. return this._write('\x1b]12;' + param + '\x07');
  1472. };
  1473. // OSC Ps ; Pt ST
  1474. // OSC Ps ; Pt BEL
  1475. // Sel data
  1476. Program.prototype.selData = function(a, b) {
  1477. if (this.has('Ms')) {
  1478. return this.put.Ms(a, b);
  1479. }
  1480. return this._write('\x1b]52;' + a + ';' + b + '\x07');
  1481. };
  1482. /**
  1483. * CSI
  1484. */
  1485. // CSI Ps A
  1486. // Cursor Up Ps Times (default = 1) (CUU).
  1487. Program.prototype.cuu =
  1488. Program.prototype.up =
  1489. Program.prototype.cursorUp = function(param) {
  1490. this.y -= param || 1;
  1491. this._ncoords();
  1492. if (this.tput) return this.put.cuu(param);
  1493. return this._write('\x1b[' + (param || '') + 'A');
  1494. };
  1495. // CSI Ps B
  1496. // Cursor Down Ps Times (default = 1) (CUD).
  1497. Program.prototype.cud =
  1498. Program.prototype.down =
  1499. Program.prototype.cursorDown = function(param) {
  1500. this.y += param || 1;
  1501. this._ncoords();
  1502. if (this.tput) return this.put.cud(param);
  1503. return this._write('\x1b[' + (param || '') + 'B');
  1504. };
  1505. // CSI Ps C
  1506. // Cursor Forward Ps Times (default = 1) (CUF).
  1507. Program.prototype.cuf =
  1508. Program.prototype.right =
  1509. Program.prototype.forward =
  1510. Program.prototype.cursorForward = function(param) {
  1511. this.x += param || 1;
  1512. this._ncoords();
  1513. if (this.tput) return this.put.cuf(param);
  1514. return this._write('\x1b[' + (param || '') + 'C');
  1515. };
  1516. // CSI Ps D
  1517. // Cursor Backward Ps Times (default = 1) (CUB).
  1518. Program.prototype.cub =
  1519. Program.prototype.left =
  1520. Program.prototype.back =
  1521. Program.prototype.cursorBackward = function(param) {
  1522. this.x -= param || 1;
  1523. this._ncoords();
  1524. if (this.tput) return this.put.cub(param);
  1525. return this._write('\x1b[' + (param || '') + 'D');
  1526. };
  1527. // CSI Ps ; Ps H
  1528. // Cursor Position [row;column] (default = [1,1]) (CUP).
  1529. Program.prototype.cup =
  1530. Program.prototype.pos =
  1531. Program.prototype.cursorPos = function(row, col) {
  1532. if (!this.zero) {
  1533. row = (row || 1) - 1;
  1534. col = (col || 1) - 1;
  1535. } else {
  1536. row = row || 0;
  1537. col = col || 0;
  1538. }
  1539. this.x = col;
  1540. this.y = row;
  1541. this._ncoords();
  1542. if (this.tput) return this.put.cup(row, col);
  1543. return this._write('\x1b[' + (row + 1) + ';' + (col + 1) + 'H');
  1544. };
  1545. // CSI Ps J Erase in Display (ED).
  1546. // Ps = 0 -> Erase Below (default).
  1547. // Ps = 1 -> Erase Above.
  1548. // Ps = 2 -> Erase All.
  1549. // Ps = 3 -> Erase Saved Lines (xterm).
  1550. // CSI ? Ps J
  1551. // Erase in Display (DECSED).
  1552. // Ps = 0 -> Selective Erase Below (default).
  1553. // Ps = 1 -> Selective Erase Above.
  1554. // Ps = 2 -> Selective Erase All.
  1555. Program.prototype.ed =
  1556. Program.prototype.eraseInDisplay = function(param) {
  1557. if (this.tput) {
  1558. switch (param) {
  1559. case 'above':
  1560. param = 1;
  1561. break;
  1562. case 'all':
  1563. param = 2;
  1564. break;
  1565. case 'saved':
  1566. param = 3;
  1567. break;
  1568. case 'below':
  1569. default:
  1570. param = 0;
  1571. break;
  1572. }
  1573. // extended tput.E3 = ^[[3;J
  1574. return this.put.ed(param);
  1575. }
  1576. switch (param) {
  1577. case 'above':
  1578. return this._write('\X1b[1J');
  1579. case 'all':
  1580. return this._write('\x1b[2J');
  1581. case 'saved':
  1582. return this._write('\x1b[3J');
  1583. case 'below':
  1584. default:
  1585. return this._write('\x1b[J');
  1586. }
  1587. };
  1588. Program.prototype.clear = function() {
  1589. this.x = 0;
  1590. this.y = 0;
  1591. if (this.tput) return this.put.clear();
  1592. return this._write('\x1b[H\x1b[J');
  1593. };
  1594. // CSI Ps K Erase in Line (EL).
  1595. // Ps = 0 -> Erase to Right (default).
  1596. // Ps = 1 -> Erase to Left.
  1597. // Ps = 2 -> Erase All.
  1598. // CSI ? Ps K
  1599. // Erase in Line (DECSEL).
  1600. // Ps = 0 -> Selective Erase to Right (default).
  1601. // Ps = 1 -> Selective Erase to Left.
  1602. // Ps = 2 -> Selective Erase All.
  1603. Program.prototype.el =
  1604. Program.prototype.eraseInLine = function(param) {
  1605. if (this.tput) {
  1606. //if (this.tput.back_color_erase) ...
  1607. switch (param) {
  1608. case 'left':
  1609. param = 1;
  1610. break;
  1611. case 'all':
  1612. param = 2;
  1613. break;
  1614. case 'right':
  1615. default:
  1616. param = 0;
  1617. break;
  1618. }
  1619. return this.put.el(param);
  1620. }
  1621. switch (param) {
  1622. case 'left':
  1623. return this._write('\x1b[1K');
  1624. case 'all':
  1625. return this._write('\x1b[2K');
  1626. case 'right':
  1627. default:
  1628. return this._write('\x1b[K');
  1629. }
  1630. };
  1631. // CSI Pm m Character Attributes (SGR).
  1632. // Ps = 0 -> Normal (default).
  1633. // Ps = 1 -> Bold.
  1634. // Ps = 4 -> Underlined.
  1635. // Ps = 5 -> Blink (appears as Bold).
  1636. // Ps = 7 -> Inverse.
  1637. // Ps = 8 -> Invisible, i.e., hidden (VT300).
  1638. // Ps = 2 2 -> Normal (neither bold nor faint).
  1639. // Ps = 2 4 -> Not underlined.
  1640. // Ps = 2 5 -> Steady (not blinking).
  1641. // Ps = 2 7 -> Positive (not inverse).
  1642. // Ps = 2 8 -> Visible, i.e., not hidden (VT300).
  1643. // Ps = 3 0 -> Set foreground color to Black.
  1644. // Ps = 3 1 -> Set foreground color to Red.
  1645. // Ps = 3 2 -> Set foreground color to Green.
  1646. // Ps = 3 3 -> Set foreground color to Yellow.
  1647. // Ps = 3 4 -> Set foreground color to Blue.
  1648. // Ps = 3 5 -> Set foreground color to Magenta.
  1649. // Ps = 3 6 -> Set foreground color to Cyan.
  1650. // Ps = 3 7 -> Set foreground color to White.
  1651. // Ps = 3 9 -> Set foreground color to default (original).
  1652. // Ps = 4 0 -> Set background color to Black.
  1653. // Ps = 4 1 -> Set background color to Red.
  1654. // Ps = 4 2 -> Set background color to Green.
  1655. // Ps = 4 3 -> Set background color to Yellow.
  1656. // Ps = 4 4 -> Set background color to Blue.
  1657. // Ps = 4 5 -> Set background color to Magenta.
  1658. // Ps = 4 6 -> Set background color to Cyan.
  1659. // Ps = 4 7 -> Set background color to White.
  1660. // Ps = 4 9 -> Set background color to default (original).
  1661. // If 16-color support is compiled, the following apply. Assume
  1662. // that xterm's resources are set so that the ISO color codes are
  1663. // the first 8 of a set of 16. Then the aixterm colors are the
  1664. // bright versions of the ISO colors:
  1665. // Ps = 9 0 -> Set foreground color to Black.
  1666. // Ps = 9 1 -> Set foreground color to Red.
  1667. // Ps = 9 2 -> Set foreground color to Green.
  1668. // Ps = 9 3 -> Set foreground color to Yellow.
  1669. // Ps = 9 4 -> Set foreground color to Blue.
  1670. // Ps = 9 5 -> Set foreground color to Magenta.
  1671. // Ps = 9 6 -> Set foreground color to Cyan.
  1672. // Ps = 9 7 -> Set foreground color to White.
  1673. // Ps = 1 0 0 -> Set background color to Black.
  1674. // Ps = 1 0 1 -> Set background color to Red.
  1675. // Ps = 1 0 2 -> Set background color to Green.
  1676. // Ps = 1 0 3 -> Set background color to Yellow.
  1677. // Ps = 1 0 4 -> Set background color to Blue.
  1678. // Ps = 1 0 5 -> Set background color to Magenta.
  1679. // Ps = 1 0 6 -> Set background color to Cyan.
  1680. // Ps = 1 0 7 -> Set background color to White.
  1681. // If xterm is compiled with the 16-color support disabled, it
  1682. // supports the following, from rxvt:
  1683. // Ps = 1 0 0 -> Set foreground and background color to
  1684. // default.
  1685. // If 88- or 256-color support is compiled, the following apply.
  1686. // Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second
  1687. // Ps.
  1688. // Ps = 4 8 ; 5 ; Ps -> Set background color to the second
  1689. // Ps.
  1690. Program.prototype.sgr =
  1691. Program.prototype.attr =
  1692. Program.prototype.charAttributes = function(param, val) {
  1693. return this._write(this._attr(param, val));
  1694. };
  1695. Program.prototype.text = function(text, attr) {
  1696. return this._attr(attr, true) + text + this._attr(attr, false);
  1697. };
  1698. // NOTE: sun-color may not allow multiple params for SGR.
  1699. Program.prototype._attr = function(param, val) {
  1700. var self = this
  1701. , param
  1702. , parts
  1703. , color
  1704. , m;
  1705. if (Array.isArray(param)) {
  1706. parts = param;
  1707. param = parts[0] || 'normal';
  1708. } else {
  1709. param = param || 'normal';
  1710. parts = param.split(/\s*[,;]\s*/);
  1711. }
  1712. if (parts.length > 1) {
  1713. var used = {}
  1714. , out = [];
  1715. parts.forEach(function(part) {
  1716. part = self._attr(part, val).slice(2, -1);
  1717. if (part === '') return;
  1718. if (used[part]) return;
  1719. used[part] = true;
  1720. out.push(part);
  1721. });
  1722. return '\x1b[' + out.join(';') + 'm';
  1723. }
  1724. if (param.indexOf('no ') === 0) {
  1725. param = param.substring(3);
  1726. val = false;
  1727. } else if (param.indexOf('!') === 0) {
  1728. param = param.substring(1);
  1729. val = false;
  1730. }
  1731. switch (param) {
  1732. // attributes
  1733. case 'normal':
  1734. case 'default':
  1735. if (val === false) return '';
  1736. return '\x1b[m';
  1737. case 'bold':
  1738. return val === false
  1739. ? '\x1b[22m'
  1740. : '\x1b[1m';
  1741. case 'ul':
  1742. case 'underline':
  1743. case 'underlined':
  1744. return val === false
  1745. ? '\x1b[24m'
  1746. : '\x1b[4m';
  1747. case 'blink':
  1748. return val === false
  1749. ? '\x1b[25m'
  1750. : '\x1b[5m';
  1751. case 'inverse':
  1752. return val === false
  1753. ? '\x1b[27m'
  1754. : '\x1b[7m';
  1755. break;
  1756. case 'invisible':
  1757. return val === false
  1758. ? '\x1b[28m'
  1759. : '\x1b[8m';
  1760. // 8-color foreground
  1761. case 'black fg':
  1762. return val === false
  1763. ? '\x1b[39m'
  1764. : '\x1b[30m';
  1765. case 'red fg':
  1766. return val === false
  1767. ? '\x1b[39m'
  1768. : '\x1b[31m';
  1769. case 'green fg':
  1770. return val === false
  1771. ? '\x1b[39m'
  1772. : '\x1b[32m';
  1773. case 'yellow fg':
  1774. return val === false
  1775. ? '\x1b[39m'
  1776. : '\x1b[33m';
  1777. case 'blue fg':
  1778. return val === false
  1779. ? '\x1b[39m'
  1780. : '\x1b[34m';
  1781. case 'magenta fg':
  1782. return val === false
  1783. ? '\x1b[39m'
  1784. : '\x1b[35m';
  1785. case 'cyan fg':
  1786. return val === false
  1787. ? '\x1b[39m'
  1788. : '\x1b[36m';
  1789. case 'white fg':
  1790. return val === false
  1791. ? '\x1b[39m'
  1792. : '\x1b[37m';
  1793. case 'default fg':
  1794. if (val === false) return '';
  1795. return '\x1b[39m';
  1796. // 8-color background
  1797. case 'black bg':
  1798. return val === false
  1799. ? '\x1b[49m'
  1800. : '\x1b[40m';
  1801. case 'red bg':
  1802. return val === false
  1803. ? '\x1b[49m'
  1804. : '…

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