From f3d5864cd5cb865cb8888aac57ffbefdd99d2554 Mon Sep 17 00:00:00 2001 From: Georg Wechslberger Date: Tue, 20 Sep 2022 04:43:49 +0200 Subject: [PATCH 1/4] feat: shortcuts may provide a custom callbacks --- lib/src/terminal_view.dart | 30 +++++- lib/src/ui/shortcut/actions.dart | 52 ++-------- lib/src/ui/shortcut/shortcuts.dart | 160 +++++++++++++++++++++++------ 3 files changed, 163 insertions(+), 79 deletions(-) diff --git a/lib/src/terminal_view.dart b/lib/src/terminal_view.dart index f0e441b7..acac32a2 100644 --- a/lib/src/terminal_view.dart +++ b/lib/src/terminal_view.dart @@ -116,7 +116,8 @@ class TerminalView extends StatefulWidget { /// Shortcuts for this terminal. This has higher priority than input handler /// of the terminal If not provided, [defaultTerminalShortcuts] will be used. - final Map? shortcuts; + //final Map? shortcuts; + final List? shortcuts; /// True if no input should send to the terminal. final bool readOnly; @@ -132,7 +133,9 @@ class TerminalView extends StatefulWidget { class TerminalViewState extends State { late FocusNode _focusNode; - late final ShortcutManager _shortcutManager; + late ShortcutManager _shortcutManager; + + late List _shortcuts; final _customTextEditKey = GlobalKey(); @@ -154,9 +157,7 @@ class TerminalViewState extends State { _focusNode = widget.focusNode ?? FocusNode(); _controller = widget.controller ?? TerminalController(); _scrollController = widget.scrollController ?? ScrollController(); - _shortcutManager = ShortcutManager( - shortcuts: widget.shortcuts ?? defaultTerminalShortcuts, - ); + _initShortcuts(); super.initState(); } @@ -180,6 +181,12 @@ class TerminalViewState extends State { } _scrollController = widget.scrollController ?? ScrollController(); } + if (oldWidget.shortcuts != widget.shortcuts) { + // The current ShortcutManager has to be disposed + // before the new one is created. + _shortcutManager.dispose(); + _initShortcuts(); + } super.didUpdateWidget(oldWidget); } @@ -198,6 +205,18 @@ class TerminalViewState extends State { super.dispose(); } + // Initialize the specified shortcut or set the default shortcuts. + void _initShortcuts() { + // Convert the list of shortcuts to a map suitable for the shortcut manager. + _shortcuts = widget.shortcuts ?? TerminalShortcut.defaults; + final shortcutsMap = Map.fromEntries(_shortcuts + .map((shortcut) => MapEntry(shortcut.activator, shortcut.intent))); + // Create a shortcut manager. + _shortcutManager = ShortcutManager( + shortcuts: shortcutsMap, + ); + } + @override Widget build(BuildContext context) { Widget child = Scrollable( @@ -261,6 +280,7 @@ class TerminalViewState extends State { child = TerminalActions( terminal: widget.terminal, controller: _controller, + shortcuts: _shortcuts, child: child, ); diff --git a/lib/src/ui/shortcut/actions.dart b/lib/src/ui/shortcut/actions.dart index 8c5b472c..0e5f8156 100644 --- a/lib/src/ui/shortcut/actions.dart +++ b/lib/src/ui/shortcut/actions.dart @@ -1,15 +1,14 @@ -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:xterm/src/core/buffer/cell_offset.dart'; -import 'package:xterm/src/core/buffer/range_line.dart'; import 'package:xterm/src/terminal.dart'; import 'package:xterm/src/ui/controller.dart'; +import 'package:xterm/src/ui/shortcut/shortcuts.dart'; class TerminalActions extends StatelessWidget { const TerminalActions({ super.key, required this.terminal, required this.controller, + required this.shortcuts, required this.child, }); @@ -17,51 +16,16 @@ class TerminalActions extends StatelessWidget { final TerminalController controller; + final List shortcuts; + final Widget child; @override Widget build(BuildContext context) { - return Actions( - actions: { - PasteTextIntent: CallbackAction( - onInvoke: (intent) async { - final data = await Clipboard.getData(Clipboard.kTextPlain); - final text = data?.text; - if (text != null) { - terminal.paste(text); - controller.clearSelection(); - } - return null; - }, - ), - CopySelectionTextIntent: CallbackAction( - onInvoke: (intent) async { - final selection = controller.selection; - - if (selection == null) { - return; - } - - final text = terminal.buffer.getText(selection); - - await Clipboard.setData(ClipboardData(text: text)); - - return null; - }, - ), - SelectAllTextIntent: CallbackAction( - onInvoke: (intent) { - controller.setSelection( - BufferRangeLine( - CellOffset(0, terminal.buffer.height - terminal.viewHeight), - CellOffset(terminal.viewWidth, terminal.buffer.height - 1), - ), - ); - return null; - }, - ), - }, - child: child, + // Convert the list of shortcuts to a callback map. + final actions = Map.fromEntries( + shortcuts.map((e) => e.toActionMapEntry(terminal, controller)), ); + return Actions(actions: actions, child: child); } } diff --git a/lib/src/ui/shortcut/shortcuts.dart b/lib/src/ui/shortcut/shortcuts.dart index a7164ba9..08ff8a62 100644 --- a/lib/src/ui/shortcut/shortcuts.dart +++ b/lib/src/ui/shortcut/shortcuts.dart @@ -1,34 +1,134 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; - -Map get defaultTerminalShortcuts { - switch (defaultTargetPlatform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - return _defaultShortcuts; - case TargetPlatform.iOS: - case TargetPlatform.macOS: - return _defaultAppleShortcuts; + +import 'package:xterm/src/core/buffer/cell_offset.dart'; +import 'package:xterm/src/core/buffer/range_line.dart'; +import 'package:xterm/src/terminal.dart'; +import 'package:xterm/src/ui/controller.dart'; + +class TerminalShortcut { + /// The activator which triggers the the intent. + final ShortcutActivator activator; + + /// The intent that is triggered bt the activator. + final T intent; + + /// The action to run when the shortcut is invoked. + final Object? Function(T, Terminal, TerminalController) action; + + const TerminalShortcut(this.activator, this.intent, this.action); + + /// Use the default modifier key for the current platform so assemble a + /// key combination for the shortcut. On iOS the macOS the shortcut will be + /// triggered my META + [key], otherwise CTRL + [key] triggers the shortcut. + factory TerminalShortcut.platformDefault( + LogicalKeyboardKey key, + T intent, + Object? Function(T, Terminal, TerminalController) action, + ) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return TerminalShortcut( + SingleActivator(key, control: true), + intent, + action, + ); + case TargetPlatform.iOS: + case TargetPlatform.macOS: + return TerminalShortcut( + SingleActivator(key, meta: true), + intent, + action, + ); + } } -} -final _defaultShortcuts = { - SingleActivator(LogicalKeyboardKey.keyC, control: true): - CopySelectionTextIntent.copy, - SingleActivator(LogicalKeyboardKey.keyV, control: true): - const PasteTextIntent(SelectionChangedCause.keyboard), - SingleActivator(LogicalKeyboardKey.keyA, control: true): - const SelectAllTextIntent(SelectionChangedCause.keyboard), -}; - -final _defaultAppleShortcuts = { - SingleActivator(LogicalKeyboardKey.keyC, meta: true): - CopySelectionTextIntent.copy, - SingleActivator(LogicalKeyboardKey.keyV, meta: true): - const PasteTextIntent(SelectionChangedCause.keyboard), - SingleActivator(LogicalKeyboardKey.keyA, meta: true): - const SelectAllTextIntent(SelectionChangedCause.keyboard), -}; + /// Convert the shortcut to a [MapEntry] for passing it to an [Action] Widget. + MapEntry> toActionMapEntry( + Terminal terminal, + TerminalController terminalController, + ) { + return MapEntry( + intent.runtimeType, + CallbackAction( + onInvoke: (intent) => action(intent, terminal, terminalController), + ), + ); + } + + /// Generate a list of default shortcuts for the current platform. + static List get defaults { + return [ + TerminalShortcut.platformDefault( + LogicalKeyboardKey.keyC, + CopySelectionTextIntent.copy, + TerminalShortcut.defaultCopy, + ), + TerminalShortcut.platformDefault( + LogicalKeyboardKey.keyV, + const PasteTextIntent(SelectionChangedCause.keyboard), + TerminalShortcut.defaultPaste, + ), + TerminalShortcut.platformDefault( + LogicalKeyboardKey.keyA, + const SelectAllTextIntent(SelectionChangedCause.keyboard), + TerminalShortcut.defaultSelectAll, + ), + ]; + } + + /// Default handler for [CopySelectionTextIntent]. + static Object? defaultCopy( + CopySelectionTextIntent intent, + Terminal terminal, + TerminalController controller, + ) async { + final selection = controller.selection; + + if (selection == null) { + return null; + } + + final text = terminal.buffer.getText(selection); + + await Clipboard.setData(ClipboardData(text: text)); + + return null; + } + + /// Default handler for [PasteTextIntent]. + static Object? defaultPaste( + PasteTextIntent intent, + Terminal terminal, + TerminalController controller, + ) async { + final data = await Clipboard.getData(Clipboard.kTextPlain); + final text = data?.text; + if (text != null) { + terminal.paste(text); + controller.clearSelection(); + } + + return null; + } + + + /// Default handler for [SelectAllTextIntent]. + static Object? defaultSelectAll( + SelectAllTextIntent intent, + Terminal terminal, + TerminalController controller, + ) async { + controller.setSelection( + BufferRangeLine( + CellOffset(0, terminal.buffer.height - terminal.viewHeight), + CellOffset(terminal.viewWidth, terminal.buffer.height - 1), + ), + ); + return null; + } +} From fefed185e844945a19a77abf1ab228e50d7a7193 Mon Sep 17 00:00:00 2001 From: Georg Wechslberger Date: Tue, 20 Sep 2022 12:54:03 +0200 Subject: [PATCH 2/4] chore: fix formatting --- lib/src/ui/shortcut/shortcuts.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/ui/shortcut/shortcuts.dart b/lib/src/ui/shortcut/shortcuts.dart index 08ff8a62..919f7ca8 100644 --- a/lib/src/ui/shortcut/shortcuts.dart +++ b/lib/src/ui/shortcut/shortcuts.dart @@ -116,7 +116,6 @@ class TerminalShortcut { return null; } - /// Default handler for [SelectAllTextIntent]. static Object? defaultSelectAll( SelectAllTextIntent intent, From 1196c53cbf19ca970a9de0440edb0cc16561d36a Mon Sep 17 00:00:00 2001 From: Georg Wechslberger Date: Tue, 20 Sep 2022 16:15:21 +0200 Subject: [PATCH 3/4] chore: make default TerminalShortcut list final --- lib/src/ui/shortcut/shortcuts.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/src/ui/shortcut/shortcuts.dart b/lib/src/ui/shortcut/shortcuts.dart index 919f7ca8..36e48f9b 100644 --- a/lib/src/ui/shortcut/shortcuts.dart +++ b/lib/src/ui/shortcut/shortcuts.dart @@ -61,8 +61,7 @@ class TerminalShortcut { } /// Generate a list of default shortcuts for the current platform. - static List get defaults { - return [ + static final List defaults = [ TerminalShortcut.platformDefault( LogicalKeyboardKey.keyC, CopySelectionTextIntent.copy, @@ -78,8 +77,7 @@ class TerminalShortcut { const SelectAllTextIntent(SelectionChangedCause.keyboard), TerminalShortcut.defaultSelectAll, ), - ]; - } + ]; /// Default handler for [CopySelectionTextIntent]. static Object? defaultCopy( From 8bc8e28a8c1cfccbe80a41b1ab7ae9195394ac89 Mon Sep 17 00:00:00 2001 From: Georg Wechslberger Date: Tue, 20 Sep 2022 16:25:49 +0200 Subject: [PATCH 4/4] chore: fix format --- lib/src/ui/shortcut/shortcuts.dart | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/src/ui/shortcut/shortcuts.dart b/lib/src/ui/shortcut/shortcuts.dart index 36e48f9b..9b21cde8 100644 --- a/lib/src/ui/shortcut/shortcuts.dart +++ b/lib/src/ui/shortcut/shortcuts.dart @@ -62,21 +62,21 @@ class TerminalShortcut { /// Generate a list of default shortcuts for the current platform. static final List defaults = [ - TerminalShortcut.platformDefault( - LogicalKeyboardKey.keyC, - CopySelectionTextIntent.copy, - TerminalShortcut.defaultCopy, - ), - TerminalShortcut.platformDefault( - LogicalKeyboardKey.keyV, - const PasteTextIntent(SelectionChangedCause.keyboard), - TerminalShortcut.defaultPaste, - ), - TerminalShortcut.platformDefault( - LogicalKeyboardKey.keyA, - const SelectAllTextIntent(SelectionChangedCause.keyboard), - TerminalShortcut.defaultSelectAll, - ), + TerminalShortcut.platformDefault( + LogicalKeyboardKey.keyC, + CopySelectionTextIntent.copy, + TerminalShortcut.defaultCopy, + ), + TerminalShortcut.platformDefault( + LogicalKeyboardKey.keyV, + const PasteTextIntent(SelectionChangedCause.keyboard), + TerminalShortcut.defaultPaste, + ), + TerminalShortcut.platformDefault( + LogicalKeyboardKey.keyA, + const SelectAllTextIntent(SelectionChangedCause.keyboard), + TerminalShortcut.defaultSelectAll, + ), ]; /// Default handler for [CopySelectionTextIntent].