Skip to content

Commit f3d5864

Browse files
committed
feat: shortcuts may provide a custom callbacks
1 parent 0e10f22 commit f3d5864

File tree

3 files changed

+163
-79
lines changed

3 files changed

+163
-79
lines changed

lib/src/terminal_view.dart

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ class TerminalView extends StatefulWidget {
116116

117117
/// Shortcuts for this terminal. This has higher priority than input handler
118118
/// of the terminal If not provided, [defaultTerminalShortcuts] will be used.
119-
final Map<ShortcutActivator, Intent>? shortcuts;
119+
//final Map<ShortcutActivator, Intent>? shortcuts;
120+
final List<TerminalShortcut>? shortcuts;
120121

121122
/// True if no input should send to the terminal.
122123
final bool readOnly;
@@ -132,7 +133,9 @@ class TerminalView extends StatefulWidget {
132133
class TerminalViewState extends State<TerminalView> {
133134
late FocusNode _focusNode;
134135

135-
late final ShortcutManager _shortcutManager;
136+
late ShortcutManager _shortcutManager;
137+
138+
late List<TerminalShortcut> _shortcuts;
136139

137140
final _customTextEditKey = GlobalKey<CustomTextEditState>();
138141

@@ -154,9 +157,7 @@ class TerminalViewState extends State<TerminalView> {
154157
_focusNode = widget.focusNode ?? FocusNode();
155158
_controller = widget.controller ?? TerminalController();
156159
_scrollController = widget.scrollController ?? ScrollController();
157-
_shortcutManager = ShortcutManager(
158-
shortcuts: widget.shortcuts ?? defaultTerminalShortcuts,
159-
);
160+
_initShortcuts();
160161
super.initState();
161162
}
162163

@@ -180,6 +181,12 @@ class TerminalViewState extends State<TerminalView> {
180181
}
181182
_scrollController = widget.scrollController ?? ScrollController();
182183
}
184+
if (oldWidget.shortcuts != widget.shortcuts) {
185+
// The current ShortcutManager has to be disposed
186+
// before the new one is created.
187+
_shortcutManager.dispose();
188+
_initShortcuts();
189+
}
183190
super.didUpdateWidget(oldWidget);
184191
}
185192

@@ -198,6 +205,18 @@ class TerminalViewState extends State<TerminalView> {
198205
super.dispose();
199206
}
200207

208+
// Initialize the specified shortcut or set the default shortcuts.
209+
void _initShortcuts() {
210+
// Convert the list of shortcuts to a map suitable for the shortcut manager.
211+
_shortcuts = widget.shortcuts ?? TerminalShortcut.defaults;
212+
final shortcutsMap = Map.fromEntries(_shortcuts
213+
.map((shortcut) => MapEntry(shortcut.activator, shortcut.intent)));
214+
// Create a shortcut manager.
215+
_shortcutManager = ShortcutManager(
216+
shortcuts: shortcutsMap,
217+
);
218+
}
219+
201220
@override
202221
Widget build(BuildContext context) {
203222
Widget child = Scrollable(
@@ -261,6 +280,7 @@ class TerminalViewState extends State<TerminalView> {
261280
child = TerminalActions(
262281
terminal: widget.terminal,
263282
controller: _controller,
283+
shortcuts: _shortcuts,
264284
child: child,
265285
);
266286

lib/src/ui/shortcut/actions.dart

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,31 @@
1-
import 'package:flutter/services.dart';
21
import 'package:flutter/widgets.dart';
3-
import 'package:xterm/src/core/buffer/cell_offset.dart';
4-
import 'package:xterm/src/core/buffer/range_line.dart';
52
import 'package:xterm/src/terminal.dart';
63
import 'package:xterm/src/ui/controller.dart';
4+
import 'package:xterm/src/ui/shortcut/shortcuts.dart';
75

86
class TerminalActions extends StatelessWidget {
97
const TerminalActions({
108
super.key,
119
required this.terminal,
1210
required this.controller,
11+
required this.shortcuts,
1312
required this.child,
1413
});
1514

1615
final Terminal terminal;
1716

1817
final TerminalController controller;
1918

19+
final List<TerminalShortcut> shortcuts;
20+
2021
final Widget child;
2122

2223
@override
2324
Widget build(BuildContext context) {
24-
return Actions(
25-
actions: {
26-
PasteTextIntent: CallbackAction<PasteTextIntent>(
27-
onInvoke: (intent) async {
28-
final data = await Clipboard.getData(Clipboard.kTextPlain);
29-
final text = data?.text;
30-
if (text != null) {
31-
terminal.paste(text);
32-
controller.clearSelection();
33-
}
34-
return null;
35-
},
36-
),
37-
CopySelectionTextIntent: CallbackAction<CopySelectionTextIntent>(
38-
onInvoke: (intent) async {
39-
final selection = controller.selection;
40-
41-
if (selection == null) {
42-
return;
43-
}
44-
45-
final text = terminal.buffer.getText(selection);
46-
47-
await Clipboard.setData(ClipboardData(text: text));
48-
49-
return null;
50-
},
51-
),
52-
SelectAllTextIntent: CallbackAction<SelectAllTextIntent>(
53-
onInvoke: (intent) {
54-
controller.setSelection(
55-
BufferRangeLine(
56-
CellOffset(0, terminal.buffer.height - terminal.viewHeight),
57-
CellOffset(terminal.viewWidth, terminal.buffer.height - 1),
58-
),
59-
);
60-
return null;
61-
},
62-
),
63-
},
64-
child: child,
25+
// Convert the list of shortcuts to a callback map.
26+
final actions = Map.fromEntries(
27+
shortcuts.map((e) => e.toActionMapEntry(terminal, controller)),
6528
);
29+
return Actions(actions: actions, child: child);
6630
}
6731
}

lib/src/ui/shortcut/shortcuts.dart

Lines changed: 130 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,134 @@
11
import 'package:flutter/foundation.dart';
2+
import 'package:flutter/material.dart';
23
import 'package:flutter/services.dart';
3-
import 'package:flutter/widgets.dart';
4-
5-
Map<ShortcutActivator, Intent> get defaultTerminalShortcuts {
6-
switch (defaultTargetPlatform) {
7-
case TargetPlatform.android:
8-
case TargetPlatform.fuchsia:
9-
case TargetPlatform.linux:
10-
case TargetPlatform.windows:
11-
return _defaultShortcuts;
12-
case TargetPlatform.iOS:
13-
case TargetPlatform.macOS:
14-
return _defaultAppleShortcuts;
4+
5+
import 'package:xterm/src/core/buffer/cell_offset.dart';
6+
import 'package:xterm/src/core/buffer/range_line.dart';
7+
import 'package:xterm/src/terminal.dart';
8+
import 'package:xterm/src/ui/controller.dart';
9+
10+
class TerminalShortcut<T extends Intent> {
11+
/// The activator which triggers the the intent.
12+
final ShortcutActivator activator;
13+
14+
/// The intent that is triggered bt the activator.
15+
final T intent;
16+
17+
/// The action to run when the shortcut is invoked.
18+
final Object? Function(T, Terminal, TerminalController) action;
19+
20+
const TerminalShortcut(this.activator, this.intent, this.action);
21+
22+
/// Use the default modifier key for the current platform so assemble a
23+
/// key combination for the shortcut. On iOS the macOS the shortcut will be
24+
/// triggered my META + [key], otherwise CTRL + [key] triggers the shortcut.
25+
factory TerminalShortcut.platformDefault(
26+
LogicalKeyboardKey key,
27+
T intent,
28+
Object? Function(T, Terminal, TerminalController) action,
29+
) {
30+
switch (defaultTargetPlatform) {
31+
case TargetPlatform.android:
32+
case TargetPlatform.fuchsia:
33+
case TargetPlatform.linux:
34+
case TargetPlatform.windows:
35+
return TerminalShortcut(
36+
SingleActivator(key, control: true),
37+
intent,
38+
action,
39+
);
40+
case TargetPlatform.iOS:
41+
case TargetPlatform.macOS:
42+
return TerminalShortcut(
43+
SingleActivator(key, meta: true),
44+
intent,
45+
action,
46+
);
47+
}
1548
}
16-
}
1749

18-
final _defaultShortcuts = {
19-
SingleActivator(LogicalKeyboardKey.keyC, control: true):
20-
CopySelectionTextIntent.copy,
21-
SingleActivator(LogicalKeyboardKey.keyV, control: true):
22-
const PasteTextIntent(SelectionChangedCause.keyboard),
23-
SingleActivator(LogicalKeyboardKey.keyA, control: true):
24-
const SelectAllTextIntent(SelectionChangedCause.keyboard),
25-
};
26-
27-
final _defaultAppleShortcuts = {
28-
SingleActivator(LogicalKeyboardKey.keyC, meta: true):
29-
CopySelectionTextIntent.copy,
30-
SingleActivator(LogicalKeyboardKey.keyV, meta: true):
31-
const PasteTextIntent(SelectionChangedCause.keyboard),
32-
SingleActivator(LogicalKeyboardKey.keyA, meta: true):
33-
const SelectAllTextIntent(SelectionChangedCause.keyboard),
34-
};
50+
/// Convert the shortcut to a [MapEntry] for passing it to an [Action] Widget.
51+
MapEntry<Type, Action<T>> toActionMapEntry(
52+
Terminal terminal,
53+
TerminalController terminalController,
54+
) {
55+
return MapEntry(
56+
intent.runtimeType,
57+
CallbackAction<T>(
58+
onInvoke: (intent) => action(intent, terminal, terminalController),
59+
),
60+
);
61+
}
62+
63+
/// Generate a list of default shortcuts for the current platform.
64+
static List<TerminalShortcut> get defaults {
65+
return <TerminalShortcut>[
66+
TerminalShortcut<CopySelectionTextIntent>.platformDefault(
67+
LogicalKeyboardKey.keyC,
68+
CopySelectionTextIntent.copy,
69+
TerminalShortcut.defaultCopy,
70+
),
71+
TerminalShortcut<PasteTextIntent>.platformDefault(
72+
LogicalKeyboardKey.keyV,
73+
const PasteTextIntent(SelectionChangedCause.keyboard),
74+
TerminalShortcut.defaultPaste,
75+
),
76+
TerminalShortcut<SelectAllTextIntent>.platformDefault(
77+
LogicalKeyboardKey.keyA,
78+
const SelectAllTextIntent(SelectionChangedCause.keyboard),
79+
TerminalShortcut.defaultSelectAll,
80+
),
81+
];
82+
}
83+
84+
/// Default handler for [CopySelectionTextIntent].
85+
static Object? defaultCopy(
86+
CopySelectionTextIntent intent,
87+
Terminal terminal,
88+
TerminalController controller,
89+
) async {
90+
final selection = controller.selection;
91+
92+
if (selection == null) {
93+
return null;
94+
}
95+
96+
final text = terminal.buffer.getText(selection);
97+
98+
await Clipboard.setData(ClipboardData(text: text));
99+
100+
return null;
101+
}
102+
103+
/// Default handler for [PasteTextIntent].
104+
static Object? defaultPaste(
105+
PasteTextIntent intent,
106+
Terminal terminal,
107+
TerminalController controller,
108+
) async {
109+
final data = await Clipboard.getData(Clipboard.kTextPlain);
110+
final text = data?.text;
111+
if (text != null) {
112+
terminal.paste(text);
113+
controller.clearSelection();
114+
}
115+
116+
return null;
117+
}
118+
119+
120+
/// Default handler for [SelectAllTextIntent].
121+
static Object? defaultSelectAll(
122+
SelectAllTextIntent intent,
123+
Terminal terminal,
124+
TerminalController controller,
125+
) async {
126+
controller.setSelection(
127+
BufferRangeLine(
128+
CellOffset(0, terminal.buffer.height - terminal.viewHeight),
129+
CellOffset(terminal.viewWidth, terminal.buffer.height - 1),
130+
),
131+
);
132+
return null;
133+
}
134+
}

0 commit comments

Comments
 (0)