Răsfoiți Sursa

选区操作栏改成图标;选中后可直接退格删除

  · 有选区时按退格 = 删掉选中的那段(谁都会先试的直觉),
    没有选区时还是原来的逐字/逐节点删。
  · 去掉"删除"按钮 —— 退格已经能删了,留三个按钮里两个功能重叠。
  · 复制/剪切/粘贴改成图标按钮(原来是文字按钮,太占地方)。

81 个测试通过(新增 2 个覆盖"有选区时退格"和"没选区时退格如常")。

Co-Authored-By: Claude Code <noreply@anthropic.com>
nonvioce 1 lună în urmă
părinte
comite
4a951b6534
4 a modificat fișierele cu 41 adăugiri și 21 ștergeri
  1. 15 17
      lib/calculator_page.dart
  2. 6 0
      lib/expr_node.dart
  3. 16 0
      test/expr_node_test.dart
  4. 4 4
      test/widget_test.dart

+ 15 - 17
lib/calculator_page.dart

@@ -100,6 +100,16 @@ class _CalculatorPageState extends State<CalculatorPage> {
 
   // ------------------------------------------------------------------ 选区操作
 
+  /// 操作栏上的图标按钮。压扁一点,别把这条栏撑高。
+  Widget _barButton(IconData icon, String tip, VoidCallback onTap) => IconButton(
+        tooltip: tip,
+        icon: Icon(icon, size: 20),
+        visualDensity: VisualDensity.compact,
+        padding: const EdgeInsets.symmetric(horizontal: 6),
+        constraints: const BoxConstraints(minWidth: 36, minHeight: 36),
+        onPressed: onTap,
+      );
+
   Future<void> _copySelection() async {
     final t = _doc.selectionText;
     if (t.isEmpty) return;
@@ -119,14 +129,6 @@ class _CalculatorPageState extends State<CalculatorPage> {
     _snack('已剪切:$t');
   }
 
-  void _deleteSelection() {
-    setState(() {
-      _doc.deleteSelection();
-      _pastePrompt = false;
-    });
-    _onDocChanged();
-  }
-
   void _dismissBar() {
     setState(() {
       _pastePrompt = false;
@@ -155,17 +157,13 @@ class _CalculatorPageState extends State<CalculatorPage> {
                 style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
               ),
             ),
+            // 用图标而不是文字按钮 —— 文字太占地方,一条栏塞不下几个
             if (hasSel) ...[
-              TextButton(onPressed: _copySelection, child: const Text('复制')),
-              TextButton(onPressed: _cutSelection, child: const Text('剪切')),
-              TextButton(onPressed: _deleteSelection, child: const Text('删除')),
+              _barButton(Icons.copy, '复制', _copySelection),
+              _barButton(Icons.cut, '剪切', _cutSelection),
             ] else
-              TextButton(onPressed: _paste, child: const Text('粘贴')),
-            IconButton(
-              tooltip: '关闭',
-              icon: const Icon(Icons.close, size: 18),
-              onPressed: _dismissBar,
-            ),
+              _barButton(Icons.paste, '粘贴', _paste),
+            _barButton(Icons.close, '取消选择', _dismissBar),
           ],
         ),
       ),

+ 6 - 0
lib/expr_node.dart

@@ -423,6 +423,12 @@ class ExprDoc {
   ///  3. 左边是**已经填完的模板**就整个删掉 —— 否则光标会永远在模板内部打转,
   ///     根本删不掉它。
   void backspace() {
+    // 有选区就是"删掉选中的那段",这是谁都会先试的直觉
+    if (selection != null && !selection!.isEmpty) {
+      deleteSelection();
+      return;
+    }
+
     final seq = current;
 
     if (cursor.offset > 0 && seq.children.isNotEmpty) {

+ 16 - 0
test/expr_node_test.dart

@@ -300,6 +300,22 @@ void main() {
       expect(d.toSymja(), '21');
     });
 
+    test('有选区时按退格 = 删掉选中的那段', () {
+      final d = twoXPlusOne();
+      d.selectNodeAt(SlotPath.root, 1); // x
+      d.extendSelectionTo(SlotPath.root, 2); // +
+      expect(d.selectionText, 'x+');
+      d.backspace();
+      expect(d.toSymja(), '21');
+      expect(d.selection, isNull);
+    });
+
+    test('没有选区时退格还是老行为', () {
+      final d = twoXPlusOne();
+      d.backspace();
+      expect(d.toSymja(), '2x+');
+    });
+
     test('插入到光标处(粘贴进 Log 的括号里)', () {
       final d = ExprDoc();
       d.makeFunction('Log'); // Log[□],光标在参数槽里

+ 4 - 4
test/widget_test.dart

@@ -346,7 +346,7 @@ void main() {
       await selectRange(tester, '2', '1');
       expect(find.textContaining('已选中  2x+1'), findsOneWidget);
 
-      await tester.tap(find.text('复制'));
+      await tester.tap(find.byTooltip('复制'));
       await tester.pumpAndSettle();
       expect(copied, ['2x+1']);
     });
@@ -358,7 +358,7 @@ void main() {
         await press(tester, k);
       }
       await selectRange(tester, '2', '1');
-      await tester.tap(find.text('剪切'));
+      await tester.tap(find.byTooltip('剪切'));
       await tester.pumpAndSettle();
       expect(find.text('Null'), findsOneWidget); // 全剪掉了
 
@@ -368,7 +368,7 @@ void main() {
       await tapCaret(tester);
       expect(find.text('粘贴到光标处'), findsOneWidget);
 
-      await tester.tap(find.text('粘贴'));
+      await tester.tap(find.byTooltip('粘贴'));
       await tester.pumpAndSettle();
       expect(await lastExpr(tester), 'Log[2x+1]');
     });
@@ -381,7 +381,7 @@ void main() {
       // 往剪贴板里塞一段解析不了的,再粘
       await Clipboard.setData(const ClipboardData(text: 'x@y'));
       await tapCaret(tester);
-      await tester.tap(find.text('粘贴'));
+      await tester.tap(find.byTooltip('粘贴'));
       await tester.pumpAndSettle();
 
       expect(find.textContaining('没法转成按钮式输入'), findsOneWidget);