handler.dart 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import 'package:xterm/src/core/input/keys.dart';
  2. import 'package:xterm/src/core/input/keytab/keytab.dart';
  3. import 'package:xterm/src/utils/platform.dart';
  4. import 'package:xterm/src/core/state.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. }
  49. /// TerminalInputHandler contains the logic for translating a [TerminalKeyboardEvent]
  50. /// into escape sequences that can be recognized by the terminal.
  51. abstract class TerminalInputHandler {
  52. /// Translates a [TerminalKeyboardEvent] into an escape sequence. If the event
  53. /// cannot be translated, null is returned.
  54. String? call(TerminalKeyboardEvent event);
  55. }
  56. /// A [TerminalInputHandler] that chains multiple handlers together. If any
  57. /// handler returns a non-null value, it is returned. Otherwise, null is
  58. /// returned.
  59. class CascadeInputHandler implements TerminalInputHandler {
  60. final List<TerminalInputHandler> _handlers;
  61. const CascadeInputHandler(this._handlers);
  62. @override
  63. String? call(TerminalKeyboardEvent event) {
  64. for (var handler in _handlers) {
  65. final result = handler(event);
  66. if (result != null) {
  67. return result;
  68. }
  69. }
  70. return null;
  71. }
  72. }
  73. /// The default input handler for the terminal. That is composed of a
  74. /// [KeytabInputHandler], a [CtrlInputHandler], and a [AltInputHandler].
  75. ///
  76. /// It's possible to override the default input handler behavior by chaining
  77. /// another input handler before or after the default input handler using
  78. /// [CascadeInputHandler].
  79. ///
  80. /// See also:
  81. /// * [CascadeInputHandler]
  82. const defaultInputHandler = CascadeInputHandler([
  83. KeytabInputHandler(),
  84. CtrlInputHandler(),
  85. AltInputHandler(),
  86. ]);
  87. final _keytab = Keytab.defaultKeytab();
  88. /// A [TerminalInputHandler] that translates key events according to a keytab
  89. /// file.
  90. class KeytabInputHandler implements TerminalInputHandler {
  91. const KeytabInputHandler();
  92. @override
  93. String? call(TerminalKeyboardEvent event) {
  94. final action = _keytab.find(
  95. event.key,
  96. ctrl: event.ctrl,
  97. alt: event.alt,
  98. shift: event.shift,
  99. newLineMode: event.state.lineFeedMode,
  100. appCursorKeys: event.state.appKeypadMode,
  101. appKeyPad: event.state.appKeypadMode,
  102. appScreen: event.altBuffer,
  103. macos: event.platform == TerminalTargetPlatform.macos,
  104. );
  105. if (action == null) {
  106. return null;
  107. }
  108. return action.action.unescapedValue();
  109. }
  110. }
  111. /// A [TerminalInputHandler] that translates ctrl + key events into escape
  112. /// sequences. For example, ctrl + a becomes ^A.
  113. class CtrlInputHandler implements TerminalInputHandler {
  114. const CtrlInputHandler();
  115. @override
  116. String? call(TerminalKeyboardEvent event) {
  117. if (!event.ctrl || event.shift || event.alt) {
  118. return null;
  119. }
  120. final key = event.key;
  121. if (key.index >= TerminalKey.keyA.index &&
  122. key.index <= TerminalKey.keyZ.index) {
  123. final input = key.index - TerminalKey.keyA.index + 1;
  124. return String.fromCharCode(input);
  125. }
  126. return null;
  127. }
  128. }
  129. /// A [TerminalInputHandler] that translates alt + key events into escape
  130. /// sequences. For example, alt + a becomes ^[a.
  131. class AltInputHandler implements TerminalInputHandler {
  132. const AltInputHandler();
  133. @override
  134. String? call(TerminalKeyboardEvent event) {
  135. if (!event.alt || event.ctrl || event.shift) {
  136. return null;
  137. }
  138. if (event.platform == TerminalTargetPlatform.macos) {
  139. return null;
  140. }
  141. final key = event.key;
  142. if (key.index >= TerminalKey.keyA.index &&
  143. key.index <= TerminalKey.keyZ.index) {
  144. final charCode = key.index - TerminalKey.keyA.index + 65;
  145. final input = [0x1b, charCode];
  146. return String.fromCharCodes(input);
  147. }
  148. return null;
  149. }
  150. }