handler.dart 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import 'package:xterm/src/core/input/keys.dart';
  2. import 'package:xterm/src/core/input/keytab/keytab.dart';
  3. import 'package:xterm/src/core/state.dart';
  4. import 'package:xterm/src/core/platform.dart';
  5. /// The key event received from the keyboard, along with the state of the
  6. /// modifier keys and state of the terminal. Typically consumed by the
  7. /// [TerminalInputHandler] to produce a escape sequence that can be recognized
  8. /// by the terminal.
  9. ///
  10. /// See also:
  11. /// - [TerminalInputHandler]
  12. class TerminalKeyboardEvent {
  13. final TerminalKey key;
  14. final bool shift;
  15. final bool ctrl;
  16. final bool alt;
  17. final TerminalState state;
  18. final bool altBuffer;
  19. final TerminalTargetPlatform platform;
  20. TerminalKeyboardEvent({
  21. required this.key,
  22. required this.shift,
  23. required this.ctrl,
  24. required this.alt,
  25. required this.state,
  26. required this.altBuffer,
  27. required this.platform,
  28. });
  29. TerminalKeyboardEvent copyWith({
  30. TerminalKey? key,
  31. bool? shift,
  32. bool? ctrl,
  33. bool? alt,
  34. TerminalState? state,
  35. bool? altBuffer,
  36. TerminalTargetPlatform? platform,
  37. }) {
  38. return TerminalKeyboardEvent(
  39. key: key ?? this.key,
  40. shift: shift ?? this.shift,
  41. ctrl: ctrl ?? this.ctrl,
  42. alt: alt ?? this.alt,
  43. state: state ?? this.state,
  44. altBuffer: altBuffer ?? this.altBuffer,
  45. platform: platform ?? this.platform,
  46. );
  47. }
  48. @override
  49. String toString() {
  50. return 'TerminalKeyboardEvent(key: $key, shift: $shift, ctrl: $ctrl, alt: $alt, state: $state, altBuffer: $altBuffer, platform: $platform)';
  51. }
  52. }
  53. /// TerminalInputHandler contains the logic for translating a [TerminalKeyboardEvent]
  54. /// into escape sequences that can be recognized by the terminal.
  55. abstract class TerminalInputHandler {
  56. /// Translates a [TerminalKeyboardEvent] into an escape sequence. If the event
  57. /// cannot be translated, null is returned.
  58. String? call(TerminalKeyboardEvent event);
  59. }
  60. /// A [TerminalInputHandler] that chains multiple handlers together. If any
  61. /// handler returns a non-null value, it is returned. Otherwise, null is
  62. /// returned.
  63. class CascadeInputHandler implements TerminalInputHandler {
  64. final List<TerminalInputHandler> _handlers;
  65. const CascadeInputHandler(this._handlers);
  66. @override
  67. String? call(TerminalKeyboardEvent event) {
  68. for (var handler in _handlers) {
  69. final result = handler(event);
  70. if (result != null) {
  71. return result;
  72. }
  73. }
  74. return null;
  75. }
  76. }
  77. /// The default input handler for the terminal. That is composed of a
  78. /// [KeytabInputHandler], a [CtrlInputHandler], and a [AltInputHandler].
  79. ///
  80. /// It's possible to override the default input handler behavior by chaining
  81. /// another input handler before or after the default input handler using
  82. /// [CascadeInputHandler].
  83. ///
  84. /// See also:
  85. /// * [CascadeInputHandler]
  86. const defaultInputHandler = CascadeInputHandler([
  87. KeytabInputHandler(),
  88. CtrlInputHandler(),
  89. AltInputHandler(),
  90. ]);
  91. /// A [TerminalInputHandler] that translates key events according to a keytab
  92. /// file. If no keytab is provided, [Keytab.defaultKeytab] is used.
  93. class KeytabInputHandler implements TerminalInputHandler {
  94. const KeytabInputHandler([this.keytab]);
  95. final Keytab? keytab;
  96. @override
  97. String? call(TerminalKeyboardEvent event) {
  98. final keytab = this.keytab ?? Keytab.defaultKeytab;
  99. final record = keytab.find(
  100. event.key,
  101. ctrl: event.ctrl,
  102. alt: event.alt,
  103. shift: event.shift,
  104. newLineMode: event.state.lineFeedMode,
  105. appCursorKeys: event.state.appKeypadMode,
  106. appKeyPad: event.state.appKeypadMode,
  107. appScreen: event.altBuffer,
  108. macos: event.platform == TerminalTargetPlatform.macos,
  109. );
  110. if (record == null) {
  111. return null;
  112. }
  113. var result = record.action.unescapedValue();
  114. result = insertModifiers(event, result);
  115. return result;
  116. }
  117. String insertModifiers(TerminalKeyboardEvent event, String action) {
  118. String? code;
  119. if (event.shift && event.alt && event.ctrl) {
  120. code = '8';
  121. } else if (event.ctrl && event.alt) {
  122. code = '7';
  123. } else if (event.shift && event.ctrl) {
  124. code = '6';
  125. } else if (event.ctrl) {
  126. code = '5';
  127. } else if (event.shift && event.alt) {
  128. code = '4';
  129. } else if (event.alt) {
  130. code = '3';
  131. } else if (event.shift) {
  132. code = '2';
  133. }
  134. if (code != null) {
  135. return action.replaceAll('*', code);
  136. }
  137. return action;
  138. }
  139. }
  140. /// A [TerminalInputHandler] that translates ctrl + key events into escape
  141. /// sequences. For example, ctrl + a becomes ^A.
  142. class CtrlInputHandler implements TerminalInputHandler {
  143. const CtrlInputHandler();
  144. @override
  145. String? call(TerminalKeyboardEvent event) {
  146. if (!event.ctrl || event.shift || event.alt) {
  147. return null;
  148. }
  149. final key = event.key;
  150. if (key.index >= TerminalKey.keyA.index &&
  151. key.index <= TerminalKey.keyZ.index) {
  152. final input = key.index - TerminalKey.keyA.index + 1;
  153. return String.fromCharCode(input);
  154. }
  155. return null;
  156. }
  157. }
  158. /// A [TerminalInputHandler] that translates alt + key events into escape
  159. /// sequences. For example, alt + a becomes ^[a.
  160. class AltInputHandler implements TerminalInputHandler {
  161. const AltInputHandler();
  162. @override
  163. String? call(TerminalKeyboardEvent event) {
  164. if (!event.alt || event.ctrl || event.shift) {
  165. return null;
  166. }
  167. if (event.platform == TerminalTargetPlatform.macos) {
  168. return null;
  169. }
  170. final key = event.key;
  171. if (key.index >= TerminalKey.keyA.index &&
  172. key.index <= TerminalKey.keyZ.index) {
  173. final charCode = key.index - TerminalKey.keyA.index + 65;
  174. final input = [0x1b, charCode];
  175. return String.fromCharCodes(input);
  176. }
  177. return null;
  178. }
  179. }