From dd585fe01290d5d598b5e6fe01ff664ca929f6c7 Mon Sep 17 00:00:00 2001 From: chaoszhu Date: Sat, 13 Jun 2026 13:08:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BC=96=E8=BE=91=E5=99=A8=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../terminal/terminal_bottom_menu.dart | 71 +++++++++++++++++++ native/lib/l10n/strings_en.dart | 19 +++++ native/lib/l10n/strings_zh.dart | 19 +++++ 3 files changed, 109 insertions(+) diff --git a/native/lib/features/terminal/terminal_bottom_menu.dart b/native/lib/features/terminal/terminal_bottom_menu.dart index 854378f..4dcce8e 100644 --- a/native/lib/features/terminal/terminal_bottom_menu.dart +++ b/native/lib/features/terminal/terminal_bottom_menu.dart @@ -240,6 +240,7 @@ class _ShortcutKeyPanel extends StatelessWidget { Widget build(BuildContext context) { final colors = Theme.of(context).colorScheme; final ctrlNotifier = controller?.ctrlPending; + final l = AppLocalizations.of(context); return Container( color: colors.surfaceContainerHighest, padding: const EdgeInsets.fromLTRB(4, 6, 4, 6), @@ -271,6 +272,35 @@ class _ShortcutKeyPanel extends StatelessWidget { _KeyBtn('PgDn', () => onInput('\x1b[6~')), ]), const SizedBox(height: _kSectionGap), + // Common Ctrl combinations + _buildRow([ + _HintKeyBtn('^C', () => onInput('\x03'), l.tr('terminal.hint.ctrlC')), + _HintKeyBtn('^Z', () => onInput('\x1a'), l.tr('terminal.hint.ctrlZ')), + _HintKeyBtn('^D', () => onInput('\x04'), l.tr('terminal.hint.ctrlD')), + _HintKeyBtn('^L', () => onInput('\x0c'), l.tr('terminal.hint.ctrlL')), + _HintKeyBtn('^U', () => onInput('\x15'), l.tr('terminal.hint.ctrlU')), + _HintKeyBtn('^K', () => onInput('\x0b'), l.tr('terminal.hint.ctrlK')), + ]), + const SizedBox(height: _kRowGap), + _buildRow([ + _HintKeyBtn('^A', () => onInput('\x01'), l.tr('terminal.hint.ctrlA')), + _HintKeyBtn('^E', () => onInput('\x05'), l.tr('terminal.hint.ctrlE')), + _HintKeyBtn('^W', () => onInput('\x17'), l.tr('terminal.hint.ctrlW')), + _HintKeyBtn('^Y', () => onInput('\x19'), l.tr('terminal.hint.ctrlY')), + _HintKeyBtn('^X', () => onInput('\x18'), l.tr('terminal.hint.ctrlX')), + _HintKeyBtn('^R', () => onInput('\x12'), l.tr('terminal.hint.ctrlR')), + ]), + const SizedBox(height: _kRowGap), + // Vim shortcuts + _buildRow([ + _HintKeyBtn(':w', () => onInput(':w\n'), l.tr('terminal.hint.vimW')), + _HintKeyBtn(':q', () => onInput(':q\n'), l.tr('terminal.hint.vimQ')), + _HintKeyBtn(':wq', () => onInput(':wq\n'), l.tr('terminal.hint.vimWQ')), + _HintKeyBtn(':q!', () => onInput(':q!\n'), l.tr('terminal.hint.vimQF')), + _HintKeyBtn('dd', () => onInput('dd'), l.tr('terminal.hint.vimDD')), + _HintKeyBtn('yy', () => onInput('yy'), l.tr('terminal.hint.vimYY')), + ]), + const SizedBox(height: _kSectionGap), // F1–F6 _buildRow([ _KeyBtn('F1', () => onInput('\x1bOP')), @@ -356,6 +386,47 @@ class _KeyBtn extends StatelessWidget { } } +class _HintKeyBtn extends StatelessWidget { + const _HintKeyBtn(this.label, this.onTap, this.hint); + + final String label; + final VoidCallback onTap; + final String hint; + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + return Material( + color: colors.surfaceContainer, + borderRadius: BorderRadius.circular(6), + child: InkWell( + borderRadius: BorderRadius.circular(6), + onTap: onTap, + onLongPress: () { + ScaffoldMessenger.of(context) + ..clearSnackBars() + ..showSnackBar( + SnackBar( + content: Text('$label $hint'), + duration: const Duration(seconds: 2), + behavior: SnackBarBehavior.floating, + ), + ); + }, + child: SizedBox( + height: _kKeyHeight, + child: Center( + child: Text( + label, + style: _kKeyStyle.copyWith(color: colors.onSurface), + ), + ), + ), + ), + ); + } +} + class _ToggleKeyBtn extends StatelessWidget { const _ToggleKeyBtn(this.label, this.notifier, this.onTap); diff --git a/native/lib/l10n/strings_en.dart b/native/lib/l10n/strings_en.dart index 2eac8e0..8b4ccbc 100644 --- a/native/lib/l10n/strings_en.dart +++ b/native/lib/l10n/strings_en.dart @@ -489,6 +489,25 @@ const Map stringsEn = { 'terminal.findNoResults': 'No results for: {0}', 'terminal.findResults': 'Find "{0}" ({1}/{2})', + 'terminal.hint.ctrlC': 'Interrupt', + 'terminal.hint.ctrlZ': 'Suspend', + 'terminal.hint.ctrlD': 'Exit / EOF', + 'terminal.hint.ctrlA': 'Line start', + 'terminal.hint.ctrlE': 'Line end', + 'terminal.hint.ctrlW': 'Del word / Save(nano)', + 'terminal.hint.ctrlX': 'Exit(nano)', + 'terminal.hint.ctrlL': 'Clear screen', + 'terminal.hint.ctrlU': 'Del to line start', + 'terminal.hint.ctrlK': 'Del to line end', + 'terminal.hint.ctrlY': 'Paste deleted text', + 'terminal.hint.ctrlR': 'Search / Read(nano)', + 'terminal.hint.vimW': 'Save(vim)', + 'terminal.hint.vimQ': 'Quit(vim)', + 'terminal.hint.vimWQ': 'Save & quit(vim)', + 'terminal.hint.vimQF': 'Force quit(vim)', + 'terminal.hint.vimDD': 'Delete line(vim)', + 'terminal.hint.vimYY': 'Copy line(vim)', + 'terminal.status.connecting': 'Connecting', 'terminal.status.connected': 'Connected', 'terminal.status.disconnected': 'Disconnected', diff --git a/native/lib/l10n/strings_zh.dart b/native/lib/l10n/strings_zh.dart index 06c5f79..4213ac3 100644 --- a/native/lib/l10n/strings_zh.dart +++ b/native/lib/l10n/strings_zh.dart @@ -462,6 +462,25 @@ const Map stringsZh = { 'terminal.findNoResults': '未找到:{0}', 'terminal.findResults': '查找 “{0}” ({1}/{2})', + 'terminal.hint.ctrlC': '中断进程', + 'terminal.hint.ctrlZ': '挂起进程', + 'terminal.hint.ctrlD': '退出 / EOF', + 'terminal.hint.ctrlA': '跳转行首', + 'terminal.hint.ctrlE': '跳转行尾', + 'terminal.hint.ctrlW': '删词 / 保存(nano)', + 'terminal.hint.ctrlX': '退出(nano)', + 'terminal.hint.ctrlL': '清屏', + 'terminal.hint.ctrlU': '删除到行首', + 'terminal.hint.ctrlK': '删除到行尾', + 'terminal.hint.ctrlY': '粘贴已删除文本', + 'terminal.hint.ctrlR': '搜索 / 读取(nano)', + 'terminal.hint.vimW': '保存(vim)', + 'terminal.hint.vimQ': '退出(vim)', + 'terminal.hint.vimWQ': '保存并退出(vim)', + 'terminal.hint.vimQF': '强制退出(vim)', + 'terminal.hint.vimDD': '删除行(vim)', + 'terminal.hint.vimYY': '复制行(vim)', + 'terminal.status.connecting': '正在连接', 'terminal.status.connected': '已连接', 'terminal.status.disconnected': '已断开',