parser.dart 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095
  1. import 'package:xterm/core/color.dart';
  2. import 'package:xterm/core/mouse.dart';
  3. import 'package:xterm/core/escape/handler.dart';
  4. import 'package:xterm/utils/ascii.dart';
  5. import 'package:xterm/utils/byte_consumer.dart';
  6. import 'package:xterm/utils/char_code.dart';
  7. import 'package:xterm/utils/lookup_table.dart';
  8. /// [EscapeParser] translates control characters and escape sequences into
  9. /// function calls that the terminal can handle.
  10. ///
  11. /// Design goals:
  12. /// * Zero object allocation during processing.
  13. /// * No internal state. Same input will always produce same output.
  14. class EscapeParser {
  15. final EscapeHandler handler;
  16. EscapeParser(this.handler);
  17. final _queue = ByteConsumer();
  18. /// Start of sequence or character being processed. Useful for debugging.
  19. var tokenBegin = 0;
  20. /// End of sequence or character being processed. Useful for debugging.
  21. int get tokenEnd => _queue.totalConsumed;
  22. void write(String chunk) {
  23. _queue.unrefConsumedBlocks();
  24. _queue.add(chunk);
  25. _process();
  26. }
  27. void _process() {
  28. while (_queue.isNotEmpty) {
  29. tokenBegin = _queue.totalConsumed;
  30. final char = _queue.consume();
  31. if (char == Ascii.ESC) {
  32. final processed = _processEscape();
  33. if (!processed) {
  34. _queue.rollback(tokenEnd - tokenBegin);
  35. return;
  36. }
  37. } else {
  38. _processChar(char);
  39. }
  40. }
  41. }
  42. void _processChar(int char) {
  43. if (char > _sbcHandlers.maxIndex) {
  44. handler.writeChar(char);
  45. return;
  46. }
  47. final sbcHandler = _sbcHandlers[char];
  48. if (sbcHandler == null) {
  49. handler.unkownEscape(char);
  50. return;
  51. }
  52. sbcHandler();
  53. }
  54. /// Processes a sequence of characters that starts with an escape character.
  55. /// Returns [true] if the sequence was processed, [false] if it was not.
  56. bool _processEscape() {
  57. if (_queue.isEmpty) return false;
  58. final escapeChar = _queue.consume();
  59. final escapeHandler = _escHandlers[escapeChar];
  60. if (escapeHandler == null) {
  61. handler.unkownEscape(escapeChar);
  62. return true;
  63. }
  64. return escapeHandler();
  65. }
  66. late final _sbcHandlers = FastLookupTable<_SbcHandler>({
  67. 0x07: handler.bell,
  68. 0x08: handler.backspaceReturn,
  69. 0x09: handler.tab,
  70. 0x0a: handler.lineFeed,
  71. 0x0b: handler.lineFeed,
  72. 0x0c: handler.lineFeed,
  73. 0x0d: handler.carriageReturn,
  74. 0x0e: handler.shiftOut,
  75. 0x0f: handler.shiftIn,
  76. });
  77. late final _escHandlers = FastLookupTable<_EscHandler>({
  78. '['.charCode: _escHandleCSI,
  79. ']'.charCode: _escHandleOSC,
  80. '7'.charCode: _escHandleSaveCursor,
  81. '8'.charCode: _escHandleRestoreCursor,
  82. 'D'.charCode: _escHandleIndex,
  83. 'E'.charCode: _escHandleNextLine,
  84. 'H'.charCode: _escHandleTabSet,
  85. 'M'.charCode: _escHandleReverseIndex,
  86. // 'P'.charCode: _unsupportedHandler, // Sixel
  87. // 'c'.charCode: _unsupportedHandler,
  88. // '#'.charCode: _unsupportedHandler,
  89. '('.charCode: _escHandleDesignateCharset0, // SCS - G0
  90. ')'.charCode: _escHandleDesignateCharset1, // SCS - G1
  91. // '*'.charCode: _voidHandler(1), // TODO: G2 (vt220)
  92. // '+'.charCode: _voidHandler(1), // TODO: G3 (vt220)
  93. '>'.charCode: _escHandleResetAppKeypadMode, // TODO: Normal Keypad
  94. '='.charCode: _escHandleSetAppKeypadMode, // TODO: Application Keypad
  95. });
  96. /// `ESC 7` Save Cursor (DECSC)
  97. ///
  98. /// https://terminalguide.namepad.de/seq/a_esc_a7/
  99. bool _escHandleSaveCursor() {
  100. handler.saveCursor();
  101. return true;
  102. }
  103. /// `ESC 8` Restore Cursor (DECRC)
  104. ///
  105. /// https://terminalguide.namepad.de/seq/a_esc_a8/
  106. bool _escHandleRestoreCursor() {
  107. handler.restoreCursor();
  108. return true;
  109. }
  110. /// `ESC D` Index (IND)
  111. ///
  112. /// https://terminalguide.namepad.de/seq/a_esc_cd/
  113. bool _escHandleIndex() {
  114. handler.index();
  115. return true;
  116. }
  117. /// `ESC E` Next Line (NEL)
  118. ///
  119. /// https://terminalguide.namepad.de/seq/a_esc_ce/
  120. bool _escHandleNextLine() {
  121. handler.nextLine();
  122. return true;
  123. }
  124. /// `ESC H` Horizontal Tab Set (HTS)
  125. ///
  126. /// https://terminalguide.namepad.de/seq/a_esc_ch/
  127. bool _escHandleTabSet() {
  128. handler.setTapStop();
  129. return true;
  130. }
  131. /// `ESC M` Reverse Index (RI)
  132. ///
  133. /// https://terminalguide.namepad.de/seq/a_esc_cm/
  134. bool _escHandleReverseIndex() {
  135. handler.reverseIndex();
  136. return true;
  137. }
  138. bool _escHandleDesignateCharset0() {
  139. if (_queue.isEmpty) return false;
  140. _queue.consume();
  141. handler.designateCharset(0);
  142. return true;
  143. }
  144. bool _escHandleDesignateCharset1() {
  145. if (_queue.isEmpty) return false;
  146. _queue.consume();
  147. handler.designateCharset(1);
  148. return true;
  149. }
  150. /// `ESC >` Reset Application Keypad Mode (DECKPNM)
  151. ///
  152. /// https://terminalguide.namepad.de/seq/a_esc_x3c_greater_than/
  153. bool _escHandleSetAppKeypadMode() {
  154. handler.setAppKeypadMode(true);
  155. return true;
  156. }
  157. /// `ESC =` Set Application Keypad Mode (DECKPAM)
  158. ///
  159. /// https://terminalguide.namepad.de/seq/a_esc_x3d_equals/
  160. bool _escHandleResetAppKeypadMode() {
  161. handler.setAppKeypadMode(false);
  162. return true;
  163. }
  164. bool _escHandleCSI() {
  165. final consumed = _consumeCsi();
  166. if (!consumed) return false;
  167. final csiHandler = _csiHandlers[_csi.finalByte];
  168. if (csiHandler == null) {
  169. handler.unknownCSI(_csi.finalByte);
  170. } else {
  171. csiHandler();
  172. }
  173. return true;
  174. }
  175. /// The last parsed [_Csi]. This is a mutable singletion by design to reduce
  176. /// object allocations.
  177. final _csi = _Csi(finalByte: 0, params: []);
  178. /// Parse a CSI from the head of the queue. Return false if the CSI isn't
  179. /// complete. After a CSI is successfully parsed, [_csi] is updated.
  180. bool _consumeCsi() {
  181. if (_queue.isEmpty) {
  182. return false;
  183. }
  184. _csi.params.clear();
  185. // test whether the csi is a `CSI ? Ps ...` or `CSI Ps ...`
  186. final prefix = _queue.peek();
  187. if (prefix >= Ascii.colon && prefix <= Ascii.questionMark) {
  188. _csi.prefix = prefix;
  189. _queue.consume();
  190. } else {
  191. _csi.prefix = null;
  192. }
  193. var param = 0;
  194. var hasParam = false;
  195. while (true) {
  196. // The sequence isn't completed, just ignore it.
  197. if (_queue.isEmpty) {
  198. return false;
  199. }
  200. final char = _queue.consume();
  201. if (char == Ascii.semicolon) {
  202. if (hasParam) {
  203. _csi.params.add(param);
  204. }
  205. param = 0;
  206. continue;
  207. }
  208. if (char >= Ascii.num0 && char <= Ascii.num9) {
  209. hasParam = true;
  210. param *= 10;
  211. param += char - Ascii.num0;
  212. continue;
  213. }
  214. if (char > Ascii.NULL && char < Ascii.num0) {
  215. // intermediates.add(char);
  216. continue;
  217. }
  218. if (char >= Ascii.atSign && char <= Ascii.tilde) {
  219. if (hasParam) {
  220. _csi.params.add(param);
  221. }
  222. _csi.finalByte = char;
  223. return true;
  224. }
  225. }
  226. }
  227. late final _csiHandlers = FastLookupTable<_CsiHandler>({
  228. // 'a'.codeUnitAt(0): _csiHandleCursorHorizontalRelative,
  229. 'b'.codeUnitAt(0): _csiHandleRepeatPreviousCharacter,
  230. 'c'.codeUnitAt(0): _csiHandleSendDeviceAttributes,
  231. 'd'.codeUnitAt(0): _csiHandleLinePositionAbsolute,
  232. 'f'.codeUnitAt(0): _csiHandleCursorPosition,
  233. 'g'.codeUnitAt(0): _csiHandelClearTabStop,
  234. 'h'.codeUnitAt(0): _csiHandleMode,
  235. 'l'.codeUnitAt(0): _csiHandleMode,
  236. 'm'.codeUnitAt(0): _csiHandleSgr,
  237. 'n'.codeUnitAt(0): _csiHandleDeviceStatusReport,
  238. 'r'.codeUnitAt(0): _csiHandleSetMargins,
  239. 't'.codeUnitAt(0): _csiWindowManipulation,
  240. 'A'.codeUnitAt(0): _csiHandleCursorUp,
  241. 'B'.codeUnitAt(0): _csiHandleCursorDown,
  242. 'C'.codeUnitAt(0): _csiHandleCursorForward,
  243. 'D'.codeUnitAt(0): _csiHandleCursorBackward,
  244. 'E'.codeUnitAt(0): _csiHandleCursorNextLine,
  245. 'F'.codeUnitAt(0): _csiHandleCursorPrecedingLine,
  246. 'G'.codeUnitAt(0): _csiHandleCursorHorizontalAbsolute,
  247. 'H'.codeUnitAt(0): _csiHandleCursorPosition,
  248. 'J'.codeUnitAt(0): _csiHandleEraseDisplay,
  249. 'K'.codeUnitAt(0): _csiHandleEraseLine,
  250. 'L'.codeUnitAt(0): _csiHandleInsertLines,
  251. 'M'.codeUnitAt(0): _csiHandleDeleteLines,
  252. 'P'.codeUnitAt(0): _csiHandleDelete,
  253. 'S'.codeUnitAt(0): _csiHandleScrollUp,
  254. 'T'.codeUnitAt(0): _csiHandleScrollDown,
  255. 'X'.codeUnitAt(0): _csiHandleEraseCharacters,
  256. '@'.codeUnitAt(0): _csiHandleInsertBlankCharacters,
  257. });
  258. /// `ESC [ Ps a` Cursor Horizontal Position Relative (HPR)
  259. ///
  260. /// https://terminalguide.namepad.de/seq/csi_sa/
  261. // void _csiHandleCursorHorizontalRelative() {
  262. // if (_csi.params.isEmpty) {
  263. // handler.cursorHorizontal(1);
  264. // } else {
  265. // handler.cursorHorizontal(_csi.params[0]);
  266. // }
  267. // }
  268. /// `ESC [ Ps b` Repeat Previous Character (REP)
  269. ///
  270. /// https://terminalguide.namepad.de/seq/csi_sb/
  271. void _csiHandleRepeatPreviousCharacter() {
  272. var amount = 1;
  273. if (_csi.params.isNotEmpty) {
  274. amount = _csi.params[0];
  275. if (amount == 0) amount = 1;
  276. }
  277. handler.repeatPreviousCharacter(amount);
  278. }
  279. /// `ESC [ Ps c` Device Attributes (DA)
  280. ///
  281. /// https://terminalguide.namepad.de/seq/csi_sc/
  282. void _csiHandleSendDeviceAttributes() {
  283. switch (_csi.prefix) {
  284. case Ascii.greaterThan:
  285. return handler.sendSecondaryDeviceAttributes();
  286. case Ascii.equal:
  287. return handler.sendTertiaryDeviceAttributes();
  288. default:
  289. handler.sendPrimaryDeviceAttributes();
  290. }
  291. }
  292. /// `ESC [ Ps d` Cursor Vertical Position Absolute (VPA)
  293. ///
  294. /// https://terminalguide.namepad.de/seq/csi_sd/
  295. void _csiHandleLinePositionAbsolute() {
  296. var y = 1;
  297. if (_csi.params.isNotEmpty) {
  298. y = _csi.params[0];
  299. }
  300. handler.setCursorY(y - 1);
  301. }
  302. /// `ESC [ Ps ; Ps f` Alias: Set Cursor Position
  303. ///
  304. /// https://terminalguide.namepad.de/seq/csi_sf/
  305. void _csiHandleCursorPosition() {
  306. var row = 1;
  307. var col = 1;
  308. if (_csi.params.length == 2) {
  309. row = _csi.params[0];
  310. col = _csi.params[1];
  311. }
  312. handler.setCursor(col - 1, row - 1);
  313. }
  314. /// `ESC [ Ps g` Tab Clear (TBC)
  315. ///
  316. /// https://terminalguide.namepad.de/seq/csi_sg/
  317. void _csiHandelClearTabStop() {
  318. var cmd = 0;
  319. if (_csi.params.length == 1) {
  320. cmd = _csi.params[0];
  321. }
  322. switch (cmd) {
  323. case 0:
  324. return handler.clearTabStopUnderCursor();
  325. default:
  326. return handler.clearAllTabStops();
  327. }
  328. }
  329. /// - `ESC [ [ Pm ] h Set Mode (SM)` https://terminalguide.namepad.de/seq/csi_sm/
  330. /// - `ESC [ ? [ Pm ] h` Set Mode (?) (SM) https://terminalguide.namepad.de/seq/csi_sh__p/
  331. /// - `ESC [ [ Pm ] l` Reset Mode (RM) https://terminalguide.namepad.de/seq/csi_rm/
  332. /// - `ESC [ ? [ Pm ] l` Reset Mode (?) (RM) https://terminalguide.namepad.de/seq/csi_sl__p/
  333. void _csiHandleMode() {
  334. final isEnabled = _csi.finalByte == Ascii.h;
  335. final isDecModes = _csi.prefix == Ascii.questionMark;
  336. if (isDecModes) {
  337. for (var mode in _csi.params) {
  338. _setDecMode(mode, isEnabled);
  339. }
  340. } else {
  341. for (var mode in _csi.params) {
  342. _setMode(mode, isEnabled);
  343. }
  344. }
  345. }
  346. /// `ESC [ [ Ps ] m` Select Graphic Rendition (SGR)
  347. ///
  348. /// https://terminalguide.namepad.de/seq/csi_sm/
  349. void _csiHandleSgr() {
  350. final params = _csi.params;
  351. if (params.isEmpty) {
  352. return handler.resetCursorStyle();
  353. }
  354. for (var i = 0; i < _csi.params.length; i++) {
  355. final param = params[i];
  356. switch (param) {
  357. case 0:
  358. handler.resetCursorStyle();
  359. continue;
  360. case 1:
  361. handler.setCursorBold();
  362. continue;
  363. case 2:
  364. handler.setCursorFaint();
  365. continue;
  366. case 3:
  367. handler.setCursorItalic();
  368. continue;
  369. case 4:
  370. handler.setCursorUnderline();
  371. continue;
  372. case 5:
  373. handler.setCursorBlink();
  374. continue;
  375. case 7:
  376. handler.setCursorInverse();
  377. continue;
  378. case 8:
  379. handler.setCursorInvisible();
  380. continue;
  381. case 9:
  382. handler.setCursorStrikethrough();
  383. continue;
  384. case 21:
  385. handler.unsetCursorBold();
  386. continue;
  387. case 22:
  388. handler.unsetCursorFaint();
  389. continue;
  390. case 23:
  391. handler.unsetCursorItalic();
  392. continue;
  393. case 24:
  394. handler.unsetCursorUnderline();
  395. continue;
  396. case 25:
  397. handler.unsetCursorBlink();
  398. continue;
  399. case 27:
  400. handler.unsetCursorInverse();
  401. continue;
  402. case 28:
  403. handler.unsetCursorInvisible();
  404. continue;
  405. case 29:
  406. handler.unsetCursorStrikethrough();
  407. continue;
  408. case 30:
  409. handler.setForegroundColor16(NamedColor.black);
  410. continue;
  411. case 31:
  412. handler.setForegroundColor16(NamedColor.red);
  413. continue;
  414. case 32:
  415. handler.setForegroundColor16(NamedColor.green);
  416. continue;
  417. case 33:
  418. handler.setForegroundColor16(NamedColor.yellow);
  419. continue;
  420. case 34:
  421. handler.setForegroundColor16(NamedColor.blue);
  422. continue;
  423. case 35:
  424. handler.setForegroundColor16(NamedColor.magenta);
  425. continue;
  426. case 36:
  427. handler.setForegroundColor16(NamedColor.cyan);
  428. continue;
  429. case 37:
  430. handler.setForegroundColor16(NamedColor.white);
  431. continue;
  432. case 38:
  433. final mode = params[i + 1];
  434. switch (mode) {
  435. case 2:
  436. final r = params[i + 2];
  437. final g = params[i + 3];
  438. final b = params[i + 4];
  439. handler.setForegroundColorRgb(r, g, b);
  440. i += 4;
  441. break;
  442. case 5:
  443. final index = params[i + 2];
  444. handler.setForegroundColor256(index);
  445. i += 2;
  446. break;
  447. }
  448. continue;
  449. case 39:
  450. handler.resetForeground();
  451. continue;
  452. case 40:
  453. handler.setBackgroundColor16(NamedColor.black);
  454. continue;
  455. case 41:
  456. handler.setBackgroundColor16(NamedColor.red);
  457. continue;
  458. case 42:
  459. handler.setBackgroundColor16(NamedColor.green);
  460. continue;
  461. case 43:
  462. handler.setBackgroundColor16(NamedColor.yellow);
  463. continue;
  464. case 44:
  465. handler.setBackgroundColor16(NamedColor.blue);
  466. continue;
  467. case 45:
  468. handler.setBackgroundColor16(NamedColor.magenta);
  469. continue;
  470. case 46:
  471. handler.setBackgroundColor16(NamedColor.cyan);
  472. continue;
  473. case 47:
  474. handler.setBackgroundColor16(NamedColor.white);
  475. continue;
  476. case 48:
  477. final mode = params[i + 1];
  478. switch (mode) {
  479. case 2:
  480. final r = params[i + 2];
  481. final g = params[i + 3];
  482. final b = params[i + 4];
  483. handler.setBackgroundColorRgb(r, g, b);
  484. i += 4;
  485. break;
  486. case 5:
  487. final index = params[i + 2];
  488. handler.setBackgroundColor256(index);
  489. i += 2;
  490. break;
  491. }
  492. continue;
  493. case 49:
  494. handler.resetBackground();
  495. continue;
  496. case 90:
  497. handler.setForegroundColor16(NamedColor.brightBlack);
  498. continue;
  499. case 91:
  500. handler.setForegroundColor16(NamedColor.brightRed);
  501. continue;
  502. case 92:
  503. handler.setForegroundColor16(NamedColor.brightGreen);
  504. continue;
  505. case 93:
  506. handler.setForegroundColor16(NamedColor.brightYellow);
  507. continue;
  508. case 94:
  509. handler.setForegroundColor16(NamedColor.brightBlue);
  510. continue;
  511. case 95:
  512. handler.setForegroundColor16(NamedColor.brightMagenta);
  513. continue;
  514. case 96:
  515. handler.setForegroundColor16(NamedColor.brightCyan);
  516. continue;
  517. case 97:
  518. handler.setForegroundColor16(NamedColor.brightWhite);
  519. continue;
  520. case 100:
  521. handler.setBackgroundColor16(NamedColor.brightBlack);
  522. continue;
  523. case 101:
  524. handler.setBackgroundColor16(NamedColor.brightRed);
  525. continue;
  526. case 102:
  527. handler.setBackgroundColor16(NamedColor.brightGreen);
  528. continue;
  529. case 103:
  530. handler.setBackgroundColor16(NamedColor.brightYellow);
  531. continue;
  532. case 104:
  533. handler.setBackgroundColor16(NamedColor.brightBlue);
  534. continue;
  535. case 105:
  536. handler.setBackgroundColor16(NamedColor.brightMagenta);
  537. continue;
  538. case 106:
  539. handler.setBackgroundColor16(NamedColor.brightCyan);
  540. continue;
  541. case 107:
  542. handler.setBackgroundColor16(NamedColor.brightWhite);
  543. continue;
  544. default:
  545. handler.unsupportedStyle(param);
  546. continue;
  547. }
  548. }
  549. }
  550. /// `ESC [ Ps n` Device Status Report [Dispatch] (DSR)
  551. ///
  552. /// https://terminalguide.namepad.de/seq/csi_sn/
  553. void _csiHandleDeviceStatusReport() {
  554. if (_csi.params.isEmpty) return;
  555. switch (_csi.params[0]) {
  556. case 5:
  557. return handler.sendOperatingStatus();
  558. case 6:
  559. return handler.sendCursorPosition();
  560. }
  561. }
  562. /// `ESC [ Ps ; Ps r` Set Top and Bottom Margins (DECSTBM)
  563. ///
  564. /// https://terminalguide.namepad.de/seq/csi_sr/
  565. void _csiHandleSetMargins() {
  566. var top = 1;
  567. int? bottom;
  568. if (_csi.params.length > 2) return;
  569. if (_csi.params.isNotEmpty) {
  570. top = _csi.params[0];
  571. if (_csi.params.length == 2) {
  572. bottom = _csi.params[1] - 1;
  573. }
  574. }
  575. handler.setMargins(top - 1, bottom);
  576. }
  577. /// `ESC [ Ps t` Window operations [DISPATCH]
  578. ///
  579. /// https://terminalguide.namepad.de/seq/csi_st/
  580. void _csiWindowManipulation() {
  581. // Not supported.
  582. }
  583. /// `ESC [ Ps A` Cursor Up (CUU)
  584. ///
  585. /// https://terminalguide.namepad.de/seq/csi_ca/
  586. void _csiHandleCursorUp() {
  587. var amount = 1;
  588. if (_csi.params.isNotEmpty) {
  589. amount = _csi.params[0];
  590. if (amount == 0) amount = 1;
  591. }
  592. handler.moveCursorY(-amount);
  593. }
  594. /// `ESC [ Ps B` Cursor Down (CUD)
  595. ///
  596. /// https://terminalguide.namepad.de/seq/csi_cb/
  597. void _csiHandleCursorDown() {
  598. var amount = 1;
  599. if (_csi.params.isNotEmpty) {
  600. amount = _csi.params[0];
  601. if (amount == 0) amount = 1;
  602. }
  603. handler.moveCursorY(amount);
  604. }
  605. /// `ESC [ Ps C` Cursor Right (CUF)
  606. ///
  607. /// Cursor Right (CUF)
  608. void _csiHandleCursorForward() {
  609. var amount = 1;
  610. if (_csi.params.isNotEmpty) {
  611. amount = _csi.params[0];
  612. if (amount == 0) amount = 1;
  613. }
  614. handler.moveCursorX(amount);
  615. }
  616. /// `ESC [ Ps D` Cursor Left (CUB)
  617. ///
  618. /// https://terminalguide.namepad.de/seq/csi_cd/
  619. void _csiHandleCursorBackward() {
  620. var amount = 1;
  621. if (_csi.params.isNotEmpty) {
  622. amount = _csi.params[0];
  623. if (amount == 0) amount = 1;
  624. }
  625. handler.moveCursorX(-amount);
  626. }
  627. /// `ESC [ Ps E` Cursor Next Line (CNL)
  628. ///
  629. /// https://terminalguide.namepad.de/seq/csi_ce/
  630. void _csiHandleCursorNextLine() {
  631. var amount = 1;
  632. if (_csi.params.isNotEmpty) {
  633. amount = _csi.params[0];
  634. if (amount == 0) amount = 1;
  635. }
  636. handler.cursorNextLine(amount);
  637. }
  638. /// `ESC [ Ps F` Cursor Previous Line (CPL)
  639. ///
  640. /// https://terminalguide.namepad.de/seq/csi_cf/
  641. void _csiHandleCursorPrecedingLine() {
  642. var amount = 1;
  643. if (_csi.params.isNotEmpty) {
  644. amount = _csi.params[0];
  645. if (amount == 0) amount = 1;
  646. }
  647. handler.cursorPrecedingLine(amount);
  648. }
  649. void _csiHandleCursorHorizontalAbsolute() {
  650. var x = 1;
  651. if (_csi.params.isNotEmpty) {
  652. x = _csi.params[0];
  653. if (x == 0) x = 1;
  654. }
  655. handler.setCursorX(x - 1);
  656. }
  657. /// ESC [ Ps J Erase Display [Dispatch] (ED)
  658. ///
  659. /// https://terminalguide.namepad.de/seq/csi_cj/
  660. void _csiHandleEraseDisplay() {
  661. var cmd = 0;
  662. if (_csi.params.length == 1) {
  663. cmd = _csi.params[0];
  664. }
  665. switch (cmd) {
  666. case 0:
  667. return handler.eraseDisplayBelow();
  668. case 1:
  669. return handler.eraseDisplayAbove();
  670. case 2:
  671. return handler.eraseDisplay();
  672. case 3:
  673. return handler.eraseScrollbackOnly();
  674. }
  675. }
  676. /// `ESC [ Ps K` Erase Line [Dispatch] (EL)
  677. ///
  678. /// https://terminalguide.namepad.de/seq/csi_ck/
  679. void _csiHandleEraseLine() {
  680. var cmd = 0;
  681. if (_csi.params.length == 1) {
  682. cmd = _csi.params[0];
  683. }
  684. switch (cmd) {
  685. case 0:
  686. return handler.eraseLineRight();
  687. case 1:
  688. return handler.eraseLineLeft();
  689. case 2:
  690. return handler.eraseLine();
  691. }
  692. }
  693. /// `ESC [ Ps L` Insert Line (IL)
  694. ///
  695. /// https://terminalguide.namepad.de/seq/csi_cl/
  696. void _csiHandleInsertLines() {
  697. var amount = 1;
  698. if (_csi.params.isNotEmpty) {
  699. amount = _csi.params[0];
  700. }
  701. handler.insertLines(amount);
  702. }
  703. /// ESC [ Ps M Delete Line (DL)
  704. ///
  705. /// https://terminalguide.namepad.de/seq/csi_cm/
  706. void _csiHandleDeleteLines() {
  707. var amount = 1;
  708. if (_csi.params.isNotEmpty) {
  709. amount = _csi.params[0];
  710. }
  711. handler.deleteLines(amount);
  712. }
  713. /// ESC [ Ps P Delete Character (DCH)
  714. ///
  715. /// https://terminalguide.namepad.de/seq/csi_cp/
  716. void _csiHandleDelete() {
  717. var amount = 1;
  718. if (_csi.params.isNotEmpty) {
  719. amount = _csi.params[0];
  720. }
  721. handler.deleteChars(amount);
  722. }
  723. /// `ESC [ Ps S` Scroll Up (SU)
  724. ///
  725. /// https://terminalguide.namepad.de/seq/csi_cs/
  726. void _csiHandleScrollUp() {
  727. var amount = 1;
  728. if (_csi.params.isNotEmpty) {
  729. amount = _csi.params[0];
  730. }
  731. handler.scrollUp(amount);
  732. }
  733. /// `ESC [ Ps T `Scroll Down (SD)
  734. ///
  735. /// https://terminalguide.namepad.de/seq/csi_ct_1param/
  736. void _csiHandleScrollDown() {
  737. var amount = 1;
  738. if (_csi.params.isNotEmpty) {
  739. amount = _csi.params[0];
  740. }
  741. handler.scrollDown(amount);
  742. }
  743. /// `ESC [ Ps X` Erase Character (ECH)
  744. ///
  745. /// https://terminalguide.namepad.de/seq/csi_cx/
  746. void _csiHandleEraseCharacters() {
  747. var amount = 1;
  748. if (_csi.params.isNotEmpty) {
  749. amount = _csi.params[0];
  750. }
  751. handler.eraseChars(amount);
  752. }
  753. /// `ESC [ Ps @` Insert Blanks (ICH)
  754. ///
  755. /// https://terminalguide.namepad.de/seq/csi_x40_at/
  756. ///
  757. /// Inserts amount spaces at current cursor position moving existing cell
  758. /// contents to the right. The contents of the amount right-most columns in
  759. /// the scroll region are lost. The cursor position is not changed.
  760. void _csiHandleInsertBlankCharacters() {
  761. var amount = 1;
  762. if (_csi.params.isNotEmpty) {
  763. amount = _csi.params[0];
  764. }
  765. handler.insertBlankChars(amount);
  766. }
  767. void _setMode(int mode, bool enabled) {
  768. switch (mode) {
  769. case 4:
  770. return handler.setInsertMode(enabled);
  771. case 20:
  772. return handler.setLineFeedMode(enabled);
  773. default:
  774. return handler.setUnknownMode(mode, enabled);
  775. }
  776. }
  777. void _setDecMode(int mode, bool enabled) {
  778. switch (mode) {
  779. case 1:
  780. return handler.setCursorKeysMode(enabled);
  781. case 3:
  782. return handler.setColumnMode(enabled);
  783. case 5:
  784. return handler.setReverseDisplayMode(enabled);
  785. case 6:
  786. return handler.setOriginMode(enabled);
  787. case 7:
  788. return handler.setAutoWrapMode(enabled);
  789. case 9:
  790. return enabled
  791. ? handler.setMouseMode(MouseMode.clickOnly)
  792. : handler.setMouseMode(MouseMode.none);
  793. case 12:
  794. case 13:
  795. return handler.setCursorBlinkMode(enabled);
  796. case 25:
  797. return handler.setCursorVisibleMode(enabled);
  798. case 47:
  799. if (enabled) {
  800. return handler.useAltBuffer();
  801. } else {
  802. return handler.useMainBuffer();
  803. }
  804. case 66:
  805. return handler.setAppKeypadMode(enabled);
  806. case 1000:
  807. case 10061000:
  808. return enabled
  809. ? handler.setMouseMode(MouseMode.upDownScroll)
  810. : handler.setMouseMode(MouseMode.none);
  811. case 1001:
  812. return enabled
  813. ? handler.setMouseMode(MouseMode.upDownScroll)
  814. : handler.setMouseMode(MouseMode.none);
  815. case 1002:
  816. return enabled
  817. ? handler.setMouseMode(MouseMode.upDownScrollDrag)
  818. : handler.setMouseMode(MouseMode.none);
  819. case 1003:
  820. return enabled
  821. ? handler.setMouseMode(MouseMode.upDownScrollMove)
  822. : handler.setMouseMode(MouseMode.none);
  823. case 1004:
  824. return handler.setReportFocusMode(enabled);
  825. case 1005:
  826. return enabled
  827. ? handler.setMouseReportMode(MouseReportMode.utf)
  828. : handler.setMouseReportMode(MouseReportMode.normal);
  829. case 1006:
  830. return enabled
  831. ? handler.setMouseReportMode(MouseReportMode.sgr)
  832. : handler.setMouseReportMode(MouseReportMode.normal);
  833. case 1007:
  834. return handler.setAltBufferMouseScrollMode(enabled);
  835. case 1015:
  836. return enabled
  837. ? handler.setMouseReportMode(MouseReportMode.urxvt)
  838. : handler.setMouseReportMode(MouseReportMode.normal);
  839. case 1047:
  840. if (enabled) {
  841. handler.useAltBuffer();
  842. } else {
  843. handler.clearAltBuffer();
  844. handler.useMainBuffer();
  845. }
  846. return;
  847. case 1048:
  848. if (enabled) {
  849. return handler.saveCursor();
  850. } else {
  851. return handler.restoreCursor();
  852. }
  853. case 1049:
  854. if (enabled) {
  855. handler.saveCursor();
  856. handler.clearAltBuffer();
  857. handler.useAltBuffer();
  858. } else {
  859. handler.useMainBuffer();
  860. }
  861. return;
  862. case 2004:
  863. return handler.setBracketedPasteMode(enabled);
  864. default:
  865. return handler.setUnknownDecMode(mode, enabled);
  866. }
  867. }
  868. bool _escHandleOSC() {
  869. final consumed = _consumeOsc();
  870. if (!consumed) return false;
  871. if (_osc.length < 2) {
  872. return true;
  873. }
  874. final ps = _osc[0];
  875. final pt = _osc[1];
  876. switch (ps) {
  877. case '0':
  878. handler.setTitle(pt);
  879. handler.setIconName(pt);
  880. break;
  881. case '1':
  882. handler.setIconName(pt);
  883. break;
  884. case '2':
  885. handler.setTitle(pt);
  886. break;
  887. default:
  888. handler.unknownOSC(ps);
  889. }
  890. return true;
  891. }
  892. final _osc = <String>[];
  893. bool _consumeOsc() {
  894. _osc.clear();
  895. final param = StringBuffer();
  896. while (true) {
  897. if (_queue.isEmpty) {
  898. return false;
  899. }
  900. final char = _queue.consume();
  901. // OSC terminates with BEL
  902. if (char == Ascii.BEL) {
  903. _osc.add(param.toString());
  904. return true;
  905. }
  906. /// OSC terminates with ST
  907. if (char == Ascii.ESC) {
  908. if (_queue.isEmpty) {
  909. return false;
  910. }
  911. if (_queue.consume() == Ascii.backslash) {
  912. _osc.add(param.toString());
  913. }
  914. return true;
  915. }
  916. /// Parse next parameter
  917. if (char == Ascii.semicolon) {
  918. _osc.add(param.toString());
  919. param.clear();
  920. continue;
  921. }
  922. param.writeCharCode(char);
  923. }
  924. }
  925. }
  926. class _Csi {
  927. _Csi({
  928. required this.params,
  929. required this.finalByte,
  930. // required this.intermediates,
  931. });
  932. int? prefix;
  933. List<int> params;
  934. int finalByte;
  935. // final List<int> intermediates;
  936. @override
  937. String toString() {
  938. return params.join(';') + String.fromCharCode(finalByte);
  939. }
  940. }
  941. /// Function that handles a sequence of characters that starts with an escape.
  942. /// Returns [true] if the sequence was processed, [false] if it was not.
  943. typedef _EscHandler = bool Function();
  944. typedef _SbcHandler = void Function();
  945. typedef _CsiHandler = void Function();