From a780139eeb9782c828d2343f7a38c686b09302aa Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Sat, 17 Oct 2026 12:34:15 +0900 Subject: [PATCH] =?UTF-8?q?WIP=20P0=20=ED=8C=8C=EC=9D=BC=20=ED=83=90?= =?UTF-8?q?=EC=83=89=EA=B8=B0:=20=EA=B8=80=EC=9E=90=20=EC=B9=B8=EC=97=90?= =?UTF-8?q?=20=EC=B9=98=EB=8A=94=20=EC=A4=91=EC=97=90=EB=8A=94=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=8B=A8=EC=B6=95=ED=82=A4=EA=B0=80=20=ED=82=A4?= =?UTF-8?q?=EB=A5=BC=20=EA=B0=80=EB=A1=9C=EC=B1=84=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B2=8C=20(15=20=EC=A7=80=EC=A0=81=20=C2=B7=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20P0)=20(=EA=B2=80=ED=86=A0=20=EB=8C=80?= =?UTF-8?q?=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 예전: 화면의 CallbackShortcuts 가 글자 칸 (빠른 필터 등) 보다 먼저 키를 받아, 칸에서 Delete 를 누르면 파일 지우기 창이 뜨고 Ctrl+A · C · X · V · Space · F8 · Backspace 도 파일 동작으로 쓰였다 (Ctrl+V 는 파일을 지금 폴더에 붙여넣을 수 있었다). Delete · F2 · Backspace 는 83 전부터 있던 것. - 이제 단축키는 Focus.onKeyEvent 로 받아, 글자 칸에 포커스가 있으면 넘긴다 (칸의 기본 동작). 모든 모양 · Rsync 화면 공통. Esc 는 지금처럼 필터 칸이 받아 필터를 닫는다. - 찾기 · 이름 바꾸기 · 경로 입력은 창 (다른 경로) 이라 이 단축키 아래에 있지 않다. - 시험: explorer_test P0 (windows · xplore · totalcmd 각각 - 필터 칸에서 Ctrl+A 는 글 전체 선택, Ctrl+C · V · Delete · Space · F8 · Backspace 가 파일을 표시 · 담기 · 지우기 하지 않고 칸이 그대로, Esc 로 닫힘). Co-Authored-By: Claude Opus 5.5 --- lib/ui/explorer_page.dart | 30 ++++++++++++++++++++----- test/explorer_test.dart | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index 5c2d0de..9d265e0 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -3167,8 +3167,9 @@ class _ExplorerPageState extends State { } Widget _buildPage(BuildContext context) { - return CallbackShortcuts( - bindings: { + // P0 (15 지적 10/17): 글자 칸 (빠른 필터 · 경로 등) 에 치는 중이면 파일 단축키를 쓰지 않는다 - 예전에는 CallbackShortcuts 가 + // 글자 칸보다 먼저 키를 받아, 칸에서 Delete 를 누르면 파일 지우기 창이 뜨고 Ctrl+A · C · V · Space 도 파일에 쓰였다 + final bindings = { // 83: 스타일에 맞는 단축키 (Total Commander: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert/Space 표시 · // Ctrl+R 새로 고침 / Windows 탐색기 · X-plore: Ctrl+C · X · V · A · F5 새로 고침) ..._styleKeys(), @@ -3195,7 +3196,11 @@ class _ExplorerPageState extends State { const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): () => _travel(_panes[_active], forward: true), const SingleActivator(LogicalKeyboardKey.tab): () => setState(() => _active = _dual ? 1 - _active : 0), - }, + }; + return Focus( + canRequestFocus: false, + skipTraversal: true, + onKeyEvent: (_, ev) => _shortcut(bindings, ev), child: Focus( focusNode: _keys, autofocus: true, @@ -3652,6 +3657,21 @@ class _ExplorerPageState extends State { } } + /// 글자 칸에 치는 중인지 (그 칸이 키를 받아야 한다) + static bool get _typing => FocusManager.instance.primaryFocus?.context?.findAncestorStateOfType() != null; + + /// 탐색기 단축키: 글자 칸에 치는 중이면 넘기고 (칸의 기본 동작 - 선택 · 복사 · 붙여넣기 · 지우기 · 빈칸), 아니면 맞는 것을 한다 + KeyEventResult _shortcut(Map bindings, KeyEvent ev) { + if (ev is KeyUpEvent || _typing) return KeyEventResult.ignored; + for (final b in bindings.entries) { + if (b.key.accepts(ev, HardwareKeyboard.instance)) { + b.value(); + return KeyEventResult.handled; + } + } + return KeyEventResult.ignored; + } + KeyEventResult _typeToFilter(KeyEvent ev) { if (ev is! KeyDownEvent) return KeyEventResult.ignored; final ch = ev.character; @@ -3661,9 +3681,7 @@ class _ExplorerPageState extends State { if (ch == ' ' && _tcKeys && !_panes[_active].filterOpen) return KeyEventResult.ignored; if (kb.isControlPressed || kb.isAltPressed || kb.isMetaPressed) return KeyEventResult.ignored; // 글자 칸 (필터 · 경로 입력 등) 에 치는 중이면 그 칸이 받는다 - if (FocusManager.instance.primaryFocus?.context?.findAncestorStateOfType() != null) { - return KeyEventResult.ignored; - } + if (_typing) return KeyEventResult.ignored; if (!_ready || _panes.isEmpty) return KeyEventResult.ignored; final pane = _panes[_active]; _openFilter(pane, pane.filterOpen ? pane.filterCtl.text + ch : ch); diff --git a/test/explorer_test.dart b/test/explorer_test.dart index 0cda6dc..023ef43 100644 --- a/test/explorer_test.dart +++ b/test/explorer_test.dart @@ -1013,6 +1013,52 @@ void main() { expect(find.textContaining('담았습니다'), findsOneWidget); }); + // P0 (15 지적 10/17): 글자 칸에 치는 중에는 파일 단축키가 키를 가로채지 않는다 (예전: 필터 칸의 Delete → 파일 지우기 창) + for (final style in const ['windows', 'xplore', 'totalcmd']) { + testWidgets('P0 $style: 빠른 필터 칸에서 Ctrl+A · C · V · Delete · Space · F8 · Backspace 는 파일에 쓰이지 않는다', (tester) async { + c.settings.explorerStyle = style; + await open(tester); + await act(tester, () => tester.tap(find.text(style == 'totalcmd' ? 'doc' : 'doc.txt'))); + await settle(tester, () => false, rounds: 15); + // Ctrl+F 로 필터 칸 열고 글자 + Future ctrl(LogicalKeyboardKey k) => act(tester, () async { + await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); + await tester.sendKeyEvent(k); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); + }); + await ctrl(LogicalKeyboardKey.keyF); + await calm(tester); + final field = find.byType(TextField); + expect(field, findsOneWidget); + await tester.enterText(field, 'do'); + await calm(tester); + await ctrl(LogicalKeyboardKey.keyA); + await calm(tester); + final ctl = tester.widget(field).controller!; + expect(ctl.selection, const TextSelection(baseOffset: 0, extentOffset: 2), reason: 'Ctrl+A 는 칸의 글 전체 선택'); + await ctrl(LogicalKeyboardKey.keyC); + await calm(tester); + await ctrl(LogicalKeyboardKey.keyV); + await calm(tester); + for (final k in [LogicalKeyboardKey.delete, LogicalKeyboardKey.space, LogicalKeyboardKey.f8, LogicalKeyboardKey.backspace]) { + await act(tester, () => tester.sendKeyEvent(k)); + await calm(tester); + } + expect(find.textContaining('표시함'), findsNothing, reason: '파일이 표시되지 않음'); + expect(find.textContaining('담았습니다'), findsNothing, reason: '파일을 담지 않음'); + expect(find.textContaining('휴지통'), findsNothing, reason: '지우기 창이 뜨지 않음'); + expect(find.textContaining('지울까요'), findsNothing); + expect(File(p.join(left, 'doc.txt')).existsSync(), isTrue); + expect(field, findsOneWidget, reason: '칸은 그대로 열려 있음 (Backspace 로 위 폴더로 가지 않음)'); + // Esc 는 지금처럼 필터를 닫는다 + await act(tester, () => tester.sendKeyEvent(LogicalKeyboardKey.escape)); + await calm(tester); + expect(find.byType(TextField), findsNothing); + ScaffoldMessenger.of(tester.element(find.byType(ExplorerPage))).clearSnackBars(); + await calm(tester); + }); + } + testWidgets('84: 열 머리를 누르면 그 기준으로 · 다시 누르면 거꾸로 (처음: 모든 폴더 같은 정렬)', (tester) async { File(p.join(left, 'a_big.txt')).writeAsStringSync('x' * 50); c.settings.explorerStyle = 'windows';