Skip to content

Commit

Permalink
Lollipopkit/issue382 (#383)
Browse files Browse the repository at this point in the history
  • Loading branch information
lollipopkit authored Jun 10, 2024
1 parent edceb59 commit 22c43c7
Show file tree
Hide file tree
Showing 20 changed files with 208 additions and 41 deletions.
2 changes: 2 additions & 0 deletions lib/core/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,14 @@ class AppRoutes {
Key? key,
required ServerPrivateInfo spi,
String? initCmd,
Snippet? initSnippet,
}) {
return AppRoutes(
SSHPage(
key: key,
spi: spi,
initCmd: initCmd,
initSnippet: initSnippet,
),
'ssh_term',
);
Expand Down
152 changes: 144 additions & 8 deletions lib/data/model/server/snippet.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import 'dart:async';

import 'package:fl_lib/fl_lib.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:toolbox/data/model/server/server_private_info.dart';
import 'package:xterm/core.dart';

import '../app/tag_pickable.dart';

Expand Down Expand Up @@ -53,19 +57,116 @@ class Snippet implements TagPickable {
@override
String get tagName => name;

String fmtWith(ServerPrivateInfo spi) {
final fmted = script.replaceAllMapped(
RegExp(r'\${.+?}'),
static final fmtFinder = RegExp(r'\$\{[^{}]+\}');

String fmtWithSpi(ServerPrivateInfo spi) {
return script.replaceAllMapped(
fmtFinder,
(match) {
final key = match.group(0);
final func = fmtArgs[key];
if (func == null) {
return key!;
}
return func(spi);
if (func != null) return func(spi);
// If not found, return the original content for further processing
return key ?? '';
},
);
return fmted;
}

Future<void> runInTerm(
Terminal terminal,
ServerPrivateInfo spi, {
bool autoEnter = false,
}) async {
final argsFmted = fmtWithSpi(spi);
final matches = fmtFinder.allMatches(argsFmted);

/// There is no [TerminalKey] in the script
if (matches.isEmpty) {
terminal.textInput(argsFmted);
if (autoEnter) terminal.keyInput(TerminalKey.enter);
return;
}

// Records all start and end indexes of the matches
final (starts, ends) = matches.fold((<int>[], <int>[]), (pre, e) {
pre.$1.add(e.start);
pre.$2.add(e.end);
return pre;
});

// Check all indexes, `(idx + 1).start` must >= `idx.end`
for (var i = 0; i < starts.length - 1; i++) {
final lastEnd = ends[i];
final nextStart = starts[i + 1];
if (nextStart < lastEnd) {
throw 'Invalid format: $nextStart < $lastEnd';
}
}

// Start term input
if (starts.first > 0) {
terminal.textInput(argsFmted.substring(0, starts.first));
}

// Process matched
for (var idx = 0; idx < starts.length; idx++) {
final start = starts[idx];
final end = ends[idx];
final key = argsFmted.substring(start, end).toLowerCase();

// Special funcs
final special = _find(SnippetFuncs.specialCtrl, key);
if (special != null) {
final raw = key.substring(special.key.length + 1, key.length - 1);
await special.value((term: terminal, raw: raw));
}

// Term keys
final termKey = _find(fmtTermKeys, key);
if (termKey != null) await _doTermKeys(terminal, termKey, key);
}

// End term input
if (ends.last < argsFmted.length) {
terminal.textInput(argsFmted.substring(ends.last));
}

if (autoEnter) terminal.keyInput(TerminalKey.enter);
}

Future<void> _doTermKeys(
Terminal terminal,
MapEntry<String, TerminalKey> termKey,
String key,
) async {
if (termKey.value == TerminalKey.enter) {
terminal.keyInput(TerminalKey.enter);
return;
}

final ctrlAlt = switch (termKey.value) {
TerminalKey.control => (ctrl: true, alt: false),
TerminalKey.alt => (ctrl: false, alt: true),
_ => (ctrl: false, alt: false),
};

// `${ctrl+ad}` -> `ctrla + d`
final chars = key.substring(termKey.key.length + 1, key.length - 1);
if (chars.isEmpty) return;
final ok = terminal.charInput(
chars.codeUnitAt(0),
ctrl: ctrlAlt.ctrl,
alt: ctrlAlt.alt,
);
if (!ok) {
Loggers.app.warning('Failed to input: $key');
}

terminal.textInput(chars.substring(1));
}

MapEntry<String, T>? _find<T>(Map<String, T> map, String key) {
return map.entries.firstWhereOrNull((e) => key.startsWith(e.key));
}

static final fmtArgs = {
Expand All @@ -76,6 +177,12 @@ class Snippet implements TagPickable {
r'${id}': (ServerPrivateInfo spi) => spi.id,
r'${name}': (ServerPrivateInfo spi) => spi.name,
};

/// r'${ctrl+ad}' -> TerminalKey.control, a, d
static final fmtTermKeys = {
r'${ctrl': TerminalKey.control,
r'${alt': TerminalKey.alt,
};
}

class SnippetResult {
Expand All @@ -89,3 +196,32 @@ class SnippetResult {
required this.time,
});
}

typedef SnippetFuncCtx = ({Terminal term, String raw});

abstract final class SnippetFuncs {
static final specialCtrl = {
// `${sleep 3}` -> sleep 3 seconds
r'${sleep': SnippetFuncs.sleep,
r'${enter': SnippetFuncs.enter,
};

static const help = {
'sleep': 'Sleep for a few seconds',
'enter': 'Enter a few times',
};

static FutureOr<void> sleep(SnippetFuncCtx ctx) async {
final seconds = int.tryParse(ctx.raw);
if (seconds == null) return;
final duration = Duration(seconds: seconds);
await Future.delayed(duration);
}

static FutureOr<void> enter(SnippetFuncCtx ctx) async {
final times = int.tryParse(ctx.raw) ?? 1;
for (var i = 0; i < times; i++) {
ctx.term.keyInput(TerminalKey.enter);
}
}
}
29 changes: 14 additions & 15 deletions lib/data/provider/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import '../../core/utils/server.dart';
import '../model/server/server.dart';
import '../model/server/server_private_info.dart';
import '../model/server/server_status_update_req.dart';
import '../model/server/snippet.dart';
import '../model/server/try_limiter.dart';
import '../res/status.dart';

Expand Down Expand Up @@ -460,20 +459,20 @@ class ServerProvider extends ChangeNotifier {
TryLimiter.reset(sid);
}

Future<SnippetResult?> runSnippet(String id, Snippet snippet) async {
final server = _servers[id];
if (server == null) return null;
final watch = Stopwatch()..start();
final result = await server.client?.run(snippet.fmtWith(server.spi)).string;
final time = watch.elapsed;
watch.stop();
if (result == null) return null;
return SnippetResult(
dest: _servers[id]?.spi.name,
result: result,
time: time,
);
}
// Future<SnippetResult?> runSnippet(String id, Snippet snippet) async {
// final server = _servers[id];
// if (server == null) return null;
// final watch = Stopwatch()..start();
// final result = await server.client?.run(snippet.fmtWithArgs(server.spi)).string;
// final time = watch.elapsed;
// watch.stop();
// if (result == null) return null;
// return SnippetResult(
// dest: _servers[id]?.spi.name,
// result: result,
// time: time,
// );
// }

// Future<List<SnippetResult?>> runSnippetsMulti(
// List<String> ids,
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "System verfolgen",
"font": "Schriftarten",
"fontSize": "Schriftgröße",
"forExample": "Zum Beispiel",
"force": "freiwillig",
"foundNUpdate": "Update {count} gefunden",
"fullScreen": "Vollbildmodus",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "Follow system",
"font": "Font",
"fontSize": "Font size",
"forExample": "For example",
"force": "Force",
"foundNUpdate": "Found {count} update",
"fullScreen": "Full screen mode",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "Seguir al sistema",
"font": "Fuente",
"fontSize": "Tamaño de fuente",
"forExample": "Por ejemplo",
"force": "Forzar",
"foundNUpdate": "Encontradas {count} actualizaciones",
"fullScreen": "Modo pantalla completa",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "Suivre le système",
"font": "Police",
"fontSize": "Taille de la police",
"forExample": "Par exemple",
"force": "Forcer",
"foundNUpdate": "{count} mise à jour trouvée",
"fullScreen": "Mode plein écran",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_id.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "Ikuti sistem",
"font": "Font",
"fontSize": "Ukuran huruf",
"forExample": "Sebagai contoh",
"force": "sukarela",
"foundNUpdate": "Menemukan {count} pembaruan",
"fullScreen": "Mode Layar Penuh",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_ja.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "システムに従う",
"font": "フォント",
"fontSize": "フォントサイズ",
"forExample": "例えば",
"force": "強制",
"foundNUpdate": "{count}個の更新が見つかりました",
"fullScreen": "フルスクリーンモード",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_nl.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "Volg systeem",
"font": "Lettertype",
"fontSize": "Lettergrootte",
"forExample": "Bijvoorbeeld",
"force": "Forceer",
"foundNUpdate": "{count} update gevonden",
"fullScreen": "Volledig schermmodus",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_pt.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "Seguir sistema",
"font": "Fonte",
"fontSize": "Tamanho da fonte",
"forExample": "Por exemplo",
"force": "Forçar",
"foundNUpdate": "Encontradas {count} atualizações",
"fullScreen": "Modo tela cheia",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_ru.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "следовать за системой",
"font": "шрифт",
"fontSize": "размер шрифта",
"forExample": "Например",
"force": "принудительно",
"foundNUpdate": "Найдено {count} обновлений",
"fullScreen": "полноэкранный режим",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_zh.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "跟随系统",
"font": "字体",
"fontSize": "字体大小",
"forExample": "例如",
"force": "强制",
"foundNUpdate": "找到 {count} 个更新",
"fullScreen": "全屏模式",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_zh_tw.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"followSystem": "跟隨系統",
"font": "字體",
"fontSize": "字體大小",
"forExample": "例如",
"force": "強制",
"foundNUpdate": "找到 {count} 個更新",
"fullScreen": "全屏模式",
Expand Down
5 changes: 4 additions & 1 deletion lib/view/page/container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,10 @@ class _ContainerPageState extends State<ContainerPage> {
case ContainerMenu.terminal:
AppRoutes.ssh(
spi: widget.spi,
initCmd: 'docker exec -it ${dItem.id} sh',
initCmd: '${switch (_container.type) {
ContainerType.podman => 'podman',
ContainerType.docker => 'docker',
}} exec -it ${dItem.id} sh',
).go(context);
break;
// case DockerMenuType.stats:
Expand Down
3 changes: 2 additions & 1 deletion lib/view/page/server/tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ class _ServerPageState extends State<ServerPage>
),
floatingActionButton: AutoHide(
key: _autoHideKey,
direction: AxisDirection.right,
direction: AxisDirection.down,
offset: 75,
controller: _scrollController,
child: FloatingActionButton(
heroTag: 'addServer',
Expand Down
8 changes: 6 additions & 2 deletions lib/view/page/snippet/edit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,13 @@ class _SnippetEditPageState extends State<SnippetEditPage>
padding: const EdgeInsets.all(13),
child: SimpleMarkdown(
data: '''
📌 ${l10n.supportFmtArgs}
📌 ${l10n.supportFmtArgs}\n
${Snippet.fmtArgs.keys.map((e) => '`$e`').join(', ')}\n
${Snippet.fmtArgs.keys.map((e) => '`$e`').join(', ')}
${Snippet.fmtTermKeys.keys.map((e) => '`$e+?}`').join(', ')}\n
${l10n.forExample}:
- `\${ctrl+c}` (Control + C)
- `\${ctrl+b}d` (Tmux Detach)
''',
styleSheet: MarkdownStyleSheet(
codeblockDecoration: const BoxDecoration(
Expand Down
Loading

0 comments on commit 22c43c7

Please sign in to comment.