import 'package:xterm/src/core/input/keys.dart'; import 'package:xterm/src/core/input/keytab/keytab.dart'; import 'package:xterm/src/core/state.dart'; import 'package:xterm/src/core/platform.dart'; /// The key event received from the keyboard, along with the state of the /// modifier keys and state of the terminal. Typically consumed by the /// [TerminalInputHandler] to produce a escape sequence that can be recognized /// by the terminal. /// /// See also: /// - [TerminalInputHandler] class TerminalKeyboardEvent { final TerminalKey key; final bool shift; final bool ctrl; final bool alt; final TerminalState state; final bool altBuffer; final TerminalTargetPlatform platform; TerminalKeyboardEvent({ required this.key, required this.shift, required this.ctrl, required this.alt, required this.state, required this.altBuffer, required this.platform, }); TerminalKeyboardEvent copyWith({ TerminalKey? key, bool? shift, bool? ctrl, bool? alt, TerminalState? state, bool? altBuffer, TerminalTargetPlatform? platform, }) { return TerminalKeyboardEvent( key: key ?? this.key, shift: shift ?? this.shift, ctrl: ctrl ?? this.ctrl, alt: alt ?? this.alt, state: state ?? this.state, altBuffer: altBuffer ?? this.altBuffer, platform: platform ?? this.platform, ); } @override String toString() { return 'TerminalKeyboardEvent(key: $key, shift: $shift, ctrl: $ctrl, alt: $alt, state: $state, altBuffer: $altBuffer, platform: $platform)'; } } /// TerminalInputHandler contains the logic for translating a [TerminalKeyboardEvent] /// into escape sequences that can be recognized by the terminal. abstract class TerminalInputHandler { /// Translates a [TerminalKeyboardEvent] into an escape sequence. If the event /// cannot be translated, null is returned. String? call(TerminalKeyboardEvent event); } /// A [TerminalInputHandler] that chains multiple handlers together. If any /// handler returns a non-null value, it is returned. Otherwise, null is /// returned. class CascadeInputHandler implements TerminalInputHandler { final List _handlers; const CascadeInputHandler(this._handlers); @override String? call(TerminalKeyboardEvent event) { for (var handler in _handlers) { final result = handler(event); if (result != null) { return result; } } return null; } } /// The default input handler for the terminal. That is composed of a /// [KeytabInputHandler], a [CtrlInputHandler], and a [AltInputHandler]. /// /// It's possible to override the default input handler behavior by chaining /// another input handler before or after the default input handler using /// [CascadeInputHandler]. /// /// See also: /// * [CascadeInputHandler] const defaultInputHandler = CascadeInputHandler([ KeytabInputHandler(), CtrlInputHandler(), AltInputHandler(), ]); /// A [TerminalInputHandler] that translates key events according to a keytab /// file. If no keytab is provided, [Keytab.defaultKeytab] is used. class KeytabInputHandler implements TerminalInputHandler { const KeytabInputHandler([this.keytab]); final Keytab? keytab; @override String? call(TerminalKeyboardEvent event) { final keytab = this.keytab ?? Keytab.defaultKeytab; final record = keytab.find( event.key, ctrl: event.ctrl, alt: event.alt, shift: event.shift, newLineMode: event.state.lineFeedMode, appCursorKeys: event.state.appKeypadMode, appKeyPad: event.state.appKeypadMode, appScreen: event.altBuffer, macos: event.platform == TerminalTargetPlatform.macos, ); if (record == null) { return null; } var result = record.action.unescapedValue(); result = insertModifiers(event, result); return result; } String insertModifiers(TerminalKeyboardEvent event, String action) { String? code; if (event.shift && event.alt && event.ctrl) { code = '8'; } else if (event.ctrl && event.alt) { code = '7'; } else if (event.shift && event.ctrl) { code = '6'; } else if (event.ctrl) { code = '5'; } else if (event.shift && event.alt) { code = '4'; } else if (event.alt) { code = '3'; } else if (event.shift) { code = '2'; } if (code != null) { return action.replaceAll('*', code); } return action; } } /// A [TerminalInputHandler] that translates ctrl + key events into escape /// sequences. For example, ctrl + a becomes ^A. class CtrlInputHandler implements TerminalInputHandler { const CtrlInputHandler(); @override String? call(TerminalKeyboardEvent event) { if (!event.ctrl || event.shift || event.alt) { return null; } final key = event.key; if (key.index >= TerminalKey.keyA.index && key.index <= TerminalKey.keyZ.index) { final input = key.index - TerminalKey.keyA.index + 1; return String.fromCharCode(input); } return null; } } /// A [TerminalInputHandler] that translates alt + key events into escape /// sequences. For example, alt + a becomes ^[a. class AltInputHandler implements TerminalInputHandler { const AltInputHandler(); @override String? call(TerminalKeyboardEvent event) { if (!event.alt || event.ctrl || event.shift) { return null; } if (event.platform == TerminalTargetPlatform.macos) { return null; } final key = event.key; if (key.index >= TerminalKey.keyA.index && key.index <= TerminalKey.keyZ.index) { final charCode = key.index - TerminalKey.keyA.index + 65; final input = [0x1b, charCode]; return String.fromCharCodes(input); } return null; } }