keypress.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. 'use strict';
  2. const readline = require('readline');
  3. const combos = require('./combos');
  4. const Queue = require('./queue');
  5. /* eslint-disable no-control-regex */
  6. const metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/;
  7. const fnKeyRe = /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
  8. const keyName = {
  9. /* xterm/gnome ESC O letter */
  10. 'OP': 'f1',
  11. 'OQ': 'f2',
  12. 'OR': 'f3',
  13. 'OS': 'f4',
  14. /* xterm/rxvt ESC [ number ~ */
  15. '[11~': 'f1',
  16. '[12~': 'f2',
  17. '[13~': 'f3',
  18. '[14~': 'f4',
  19. /* from Cygwin and used in libuv */
  20. '[[A': 'f1',
  21. '[[B': 'f2',
  22. '[[C': 'f3',
  23. '[[D': 'f4',
  24. '[[E': 'f5',
  25. /* common */
  26. '[15~': 'f5',
  27. '[17~': 'f6',
  28. '[18~': 'f7',
  29. '[19~': 'f8',
  30. '[20~': 'f9',
  31. '[21~': 'f10',
  32. '[23~': 'f11',
  33. '[24~': 'f12',
  34. /* xterm ESC [ letter */
  35. '[A': 'up',
  36. '[B': 'down',
  37. '[C': 'right',
  38. '[D': 'left',
  39. '[E': 'clear',
  40. '[F': 'end',
  41. '[H': 'home',
  42. /* xterm/gnome ESC O letter */
  43. 'OA': 'up',
  44. 'OB': 'down',
  45. 'OC': 'right',
  46. 'OD': 'left',
  47. 'OE': 'clear',
  48. 'OF': 'end',
  49. 'OH': 'home',
  50. /* xterm/rxvt ESC [ number ~ */
  51. '[1~': 'home',
  52. '[2~': 'insert',
  53. '[3~': 'delete',
  54. '[4~': 'end',
  55. '[5~': 'pageup',
  56. '[6~': 'pagedown',
  57. /* putty */
  58. '[[5~': 'pageup',
  59. '[[6~': 'pagedown',
  60. /* rxvt */
  61. '[7~': 'home',
  62. '[8~': 'end',
  63. /* rxvt keys with modifiers */
  64. '[a': 'up',
  65. '[b': 'down',
  66. '[c': 'right',
  67. '[d': 'left',
  68. '[e': 'clear',
  69. '[2$': 'insert',
  70. '[3$': 'delete',
  71. '[5$': 'pageup',
  72. '[6$': 'pagedown',
  73. '[7$': 'home',
  74. '[8$': 'end',
  75. 'Oa': 'up',
  76. 'Ob': 'down',
  77. 'Oc': 'right',
  78. 'Od': 'left',
  79. 'Oe': 'clear',
  80. '[2^': 'insert',
  81. '[3^': 'delete',
  82. '[5^': 'pageup',
  83. '[6^': 'pagedown',
  84. '[7^': 'home',
  85. '[8^': 'end',
  86. /* misc. */
  87. '[Z': 'tab'
  88. };
  89. function isShiftKey(code) {
  90. return ['[a', '[b', '[c', '[d', '[e', '[2$', '[3$', '[5$', '[6$', '[7$', '[8$', '[Z'].includes(code);
  91. }
  92. function isCtrlKey(code) {
  93. return [ 'Oa', 'Ob', 'Oc', 'Od', 'Oe', '[2^', '[3^', '[5^', '[6^', '[7^', '[8^'].includes(code);
  94. }
  95. const keypress = (s = '', event = {}) => {
  96. let parts;
  97. let key = {
  98. name: event.name,
  99. ctrl: false,
  100. meta: false,
  101. shift: false,
  102. option: false,
  103. sequence: s,
  104. raw: s,
  105. ...event
  106. };
  107. if (Buffer.isBuffer(s)) {
  108. if (s[0] > 127 && s[1] === void 0) {
  109. s[0] -= 128;
  110. s = '\x1b' + String(s);
  111. } else {
  112. s = String(s);
  113. }
  114. } else if (s !== void 0 && typeof s !== 'string') {
  115. s = String(s);
  116. } else if (!s) {
  117. s = key.sequence || '';
  118. }
  119. key.sequence = key.sequence || s || key.name;
  120. if (s === '\r') {
  121. // carriage return
  122. key.raw = void 0;
  123. key.name = 'return';
  124. } else if (s === '\n') {
  125. // enter, should have been called linefeed
  126. key.name = 'enter';
  127. } else if (s === '\t') {
  128. // tab
  129. key.name = 'tab';
  130. } else if (s === '\b' || s === '\x7f' || s === '\x1b\x7f' || s === '\x1b\b') {
  131. // backspace or ctrl+h
  132. key.name = 'backspace';
  133. key.meta = s.charAt(0) === '\x1b';
  134. } else if (s === '\x1b' || s === '\x1b\x1b') {
  135. // escape key
  136. key.name = 'escape';
  137. key.meta = s.length === 2;
  138. } else if (s === ' ' || s === '\x1b ') {
  139. key.name = 'space';
  140. key.meta = s.length === 2;
  141. } else if (s <= '\x1a') {
  142. // ctrl+letter
  143. key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
  144. key.ctrl = true;
  145. } else if (s.length === 1 && s >= '0' && s <= '9') {
  146. // number
  147. key.name = 'number';
  148. } else if (s.length === 1 && s >= 'a' && s <= 'z') {
  149. // lowercase letter
  150. key.name = s;
  151. } else if (s.length === 1 && s >= 'A' && s <= 'Z') {
  152. // shift+letter
  153. key.name = s.toLowerCase();
  154. key.shift = true;
  155. } else if ((parts = metaKeyCodeRe.exec(s))) {
  156. // meta+character key
  157. key.meta = true;
  158. key.shift = /^[A-Z]$/.test(parts[1]);
  159. } else if ((parts = fnKeyRe.exec(s))) {
  160. let segs = [...s];
  161. if (segs[0] === '\u001b' && segs[1] === '\u001b') {
  162. key.option = true;
  163. }
  164. // ansi escape sequence
  165. // reassemble the key code leaving out leading \x1b's,
  166. // the modifier key bitflag and any meaningless "1;" sequence
  167. let code = [parts[1], parts[2], parts[4], parts[6]].filter(Boolean).join('');
  168. let modifier = (parts[3] || parts[5] || 1) - 1;
  169. // Parse the key modifier
  170. key.ctrl = !!(modifier & 4);
  171. key.meta = !!(modifier & 10);
  172. key.shift = !!(modifier & 1);
  173. key.code = code;
  174. key.name = keyName[code];
  175. key.shift = isShiftKey(code) || key.shift;
  176. key.ctrl = isCtrlKey(code) || key.ctrl;
  177. }
  178. return key;
  179. };
  180. keypress.listen = (options = {}, onKeypress) => {
  181. let { stdin } = options;
  182. if (!stdin || (stdin !== process.stdin && !stdin.isTTY)) {
  183. throw new Error('Invalid stream passed');
  184. }
  185. let rl = readline.createInterface({ terminal: true, input: stdin });
  186. readline.emitKeypressEvents(stdin, rl);
  187. const queue = new Queue((buf, key) => onKeypress(buf, keypress(buf, key), rl));
  188. let isRaw = stdin.isRaw;
  189. if (stdin.isTTY) stdin.setRawMode(true);
  190. stdin.on('keypress', queue.enqueue);
  191. rl.resume();
  192. let off = () => {
  193. if (stdin.isTTY) stdin.setRawMode(isRaw);
  194. stdin.removeListener('keypress', queue.enqueue);
  195. queue.destroy();
  196. rl.pause();
  197. rl.close();
  198. };
  199. return off;
  200. };
  201. keypress.action = (buf, key, customActions) => {
  202. let obj = { ...combos, ...customActions };
  203. if (key.ctrl) {
  204. key.action = obj.ctrl[key.name];
  205. return key;
  206. }
  207. if (key.option && obj.option) {
  208. key.action = obj.option[key.name];
  209. return key;
  210. }
  211. if (key.shift) {
  212. key.action = obj.shift[key.name];
  213. return key;
  214. }
  215. key.action = obj.keys[key.name];
  216. return key;
  217. };
  218. module.exports = keypress;