From 5313a960ad60d8f73dd607a55150eaeb7d4eadda Mon Sep 17 00:00:00 2001 From: hejl Date: Thu, 15 May 2025 09:59:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E6=90=9E=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content_search_controller.dart | 73 +++++- .../content_search/content_search_view.dart | 21 +- .../content_search/directory_settings.dart | 25 +- .../modules/content_search/results_view.dart | 16 +- .../content_search/search_settings.dart | 218 ++++++++++++------ 5 files changed, 244 insertions(+), 109 deletions(-) diff --git a/win_text_editor/lib/app/modules/content_search/content_search_controller.dart b/win_text_editor/lib/app/modules/content_search/content_search_controller.dart index 95447ee..4d99fe6 100644 --- a/win_text_editor/lib/app/modules/content_search/content_search_controller.dart +++ b/win_text_editor/lib/app/modules/content_search/content_search_controller.dart @@ -1,21 +1,74 @@ -import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:win_text_editor/app/core/tab_manager.dart'; -class ContentSearchController { +class ContentSearchController with ChangeNotifier { final TabManager tabManager; - String searchQuery = ''; - String searchDirectory = ''; - String fileType = '*.*'; - bool caseSensitive = false; - bool wholeWord = false; - bool useRegex = false; - SearchMode searchMode = SearchMode.locate; - final List results = []; + String _searchQuery = ''; + String _searchDirectory = ''; + String _fileType = '*.*'; + bool _caseSensitive = false; + bool _wholeWord = false; + bool _useRegex = false; + bool _customRule = false; + SearchMode _searchMode = SearchMode.locate; + final List _results = []; ContentSearchController({required this.tabManager}); + // Getters + String get searchQuery => _searchQuery; + String get searchDirectory => _searchDirectory; + String get fileType => _fileType; + bool get caseSensitive => _caseSensitive; + bool get wholeWord => _wholeWord; + bool get useRegex => _useRegex; + bool get customRule => _customRule; + SearchMode get searchMode => _searchMode; + List get results => _results; + + set customRule(bool value) { + _customRule = value; + notifyListeners(); + } + + // Setters with notifyListeners + set searchQuery(String value) { + _searchQuery = value; + notifyListeners(); + } + + set searchDirectory(String value) { + _searchDirectory = value; + notifyListeners(); + } + + set fileType(String value) { + _fileType = value; + notifyListeners(); + } + + set caseSensitive(bool value) { + _caseSensitive = value; + notifyListeners(); + } + + set wholeWord(bool value) { + _wholeWord = value; + notifyListeners(); + } + + set useRegex(bool value) { + _useRegex = value; + notifyListeners(); + } + + set searchMode(SearchMode value) { + _searchMode = value; + notifyListeners(); + } + Future startSearch() async { results.clear(); // 模拟搜索结果 diff --git a/win_text_editor/lib/app/modules/content_search/content_search_view.dart b/win_text_editor/lib/app/modules/content_search/content_search_view.dart index b15eef7..6acf130 100644 --- a/win_text_editor/lib/app/modules/content_search/content_search_view.dart +++ b/win_text_editor/lib/app/modules/content_search/content_search_view.dart @@ -28,16 +28,17 @@ class ContentSearchViewState extends State { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - DirectorySettings(controller: _controller), - const SizedBox(height: 16), - SearchSettings(controller: _controller), - const SizedBox(height: 16), - Expanded(child: ResultsView(controller: _controller)), - ], + return ChangeNotifierProvider.value( + value: _controller, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + children: [ + const DirectorySettings(), // 不再手动传递controller + SearchSettings(), + const Expanded(child: ResultsView()), + ], + ), ), ); } diff --git a/win_text_editor/lib/app/modules/content_search/directory_settings.dart b/win_text_editor/lib/app/modules/content_search/directory_settings.dart index 9cebb33..2d06870 100644 --- a/win_text_editor/lib/app/modules/content_search/directory_settings.dart +++ b/win_text_editor/lib/app/modules/content_search/directory_settings.dart @@ -1,22 +1,23 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:win_text_editor/app/modules/content_search/content_search_controller.dart'; class DirectorySettings extends StatelessWidget { - final ContentSearchController controller; - - const DirectorySettings({super.key, required this.controller}); + const DirectorySettings({super.key}); @override Widget build(BuildContext context) { + final controller = context.watch(); + final searchDirectoryController = TextEditingController(text: controller.searchDirectory); + return Card( child: Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ - const Icon(Icons.folder, color: Colors.blue), - const SizedBox(width: 8), Expanded( child: TextField( + controller: searchDirectoryController, decoration: const InputDecoration(labelText: '搜索目录', border: OutlineInputBorder()), onChanged: (value) => controller.searchDirectory = value, ), @@ -25,17 +26,19 @@ class DirectorySettings extends StatelessWidget { SizedBox( width: 100, child: TextField( - decoration: const InputDecoration( - labelText: '文件类型', - border: OutlineInputBorder(), - contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 12), - ), + decoration: const InputDecoration(labelText: '文件类型', border: OutlineInputBorder()), controller: TextEditingController(text: controller.fileType), onChanged: (value) => controller.fileType = value, ), ), const SizedBox(width: 8), - IconButton(icon: const Icon(Icons.folder_open), onPressed: controller.pickDirectory), + IconButton( + icon: const Icon(Icons.folder_open), + onPressed: () async { + await controller.pickDirectory(); + searchDirectoryController.text = controller.searchDirectory; + }, + ), ], ), ), diff --git a/win_text_editor/lib/app/modules/content_search/results_view.dart b/win_text_editor/lib/app/modules/content_search/results_view.dart index ab691b2..5c7d4d0 100644 --- a/win_text_editor/lib/app/modules/content_search/results_view.dart +++ b/win_text_editor/lib/app/modules/content_search/results_view.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:win_text_editor/app/modules/content_search/content_search_controller.dart'; class ResultsView extends StatelessWidget { - final ContentSearchController controller; - - const ResultsView({super.key, required this.controller}); + const ResultsView({super.key}); @override Widget build(BuildContext context) { + final controller = context.watch(); return Card( child: Column( children: [ @@ -21,15 +21,15 @@ class ResultsView extends StatelessWidget { Expanded( child: controller.searchMode == SearchMode.locate - ? _buildLocateResults() - : _buildCountResults(), + ? _buildLocateResults(controller) + : _buildCountResults(controller), ), ], ), ); } - Widget _buildLocateResults() { + Widget _buildLocateResults(controller) { return ListView.builder( itemCount: controller.results.length, itemBuilder: (ctx, index) { @@ -47,10 +47,10 @@ class ResultsView extends StatelessWidget { ); } - Widget _buildCountResults() { + Widget _buildCountResults(controller) { final counts = {}; for (var r in controller.results) { - counts[r.filePath] = (counts[r.filePath] ?? 0) + r.matches.length; + // counts[r.filePath] = (counts[r.filePath] ?? 0) + r.matches.length; } return ListView.builder( diff --git a/win_text_editor/lib/app/modules/content_search/search_settings.dart b/win_text_editor/lib/app/modules/content_search/search_settings.dart index a8c3971..52aae5d 100644 --- a/win_text_editor/lib/app/modules/content_search/search_settings.dart +++ b/win_text_editor/lib/app/modules/content_search/search_settings.dart @@ -1,24 +1,26 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:win_text_editor/app/components/text_editor.dart'; import 'package:win_text_editor/app/modules/content_search/content_search_controller.dart'; class SearchSettings extends StatelessWidget { - final ContentSearchController controller; final GlobalKey _searchEditorKey = GlobalKey(); - SearchSettings({super.key, required this.controller}); + SearchSettings({super.key}); @override Widget build(BuildContext context) { + final controller = context.watch(); + return Card( child: Padding( padding: const EdgeInsets.all(8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 搜索内容框 + // 搜索内容框 (保持原样) SizedBox( - width: MediaQuery.of(context).size.width * 0.5, // 占据一半宽度 + width: MediaQuery.of(context).size.width * 0.5, height: 300, child: TextEditor( key: _searchEditorKey, @@ -31,84 +33,160 @@ class SearchSettings extends StatelessWidget { // 设置按钮区域 Expanded( child: Container( + height: 300, decoration: BoxDecoration( - border: Border.all(color: Colors.grey), // 设置边框颜色 - borderRadius: BorderRadius.circular(4), // 设置边框圆角 + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), ), padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 搜索方式 - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('搜索方式:', style: TextStyle(fontSize: 12)), - Row( - children: [ - Radio( - value: SearchMode.locate, - groupValue: controller.searchMode, - onChanged: (value) => controller.searchMode = value!, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 搜索方式 - 两选项排在一行 + const Text('搜索方式:', style: TextStyle(fontSize: 12)), + const SizedBox(height: 4), + Row( + children: [ + Expanded( + child: Row( + children: [ + Radio( + value: SearchMode.locate, + groupValue: controller.searchMode, + onChanged: (value) => controller.searchMode = value!, + ), + const Text('定位', style: TextStyle(fontSize: 12)), + ], ), - const Text('定位', style: TextStyle(fontSize: 12)), - ], - ), - Row( + ), + Expanded( + child: Row( + children: [ + Radio( + value: SearchMode.count, + groupValue: controller.searchMode, + onChanged: (value) => controller.searchMode = value!, + ), + const Text('计数', style: TextStyle(fontSize: 12)), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + // 匹配规则 + const Text('匹配规则:', style: TextStyle(fontSize: 12)), + const SizedBox(height: 4), + // 第一行复选框 + Row( + children: [ + Expanded( + child: CheckboxListTile( + dense: true, + contentPadding: EdgeInsets.zero, + controlAffinity: ListTileControlAffinity.leading, + title: const Text('大小写敏感', style: TextStyle(fontSize: 12)), + value: controller.caseSensitive, + onChanged: + controller.customRule + ? null + : (value) { + controller.caseSensitive = value!; + controller.customRule = false; + }, + activeColor: controller.customRule ? Colors.grey : null, + ), + ), + Expanded( + child: CheckboxListTile( + dense: true, + contentPadding: EdgeInsets.zero, + controlAffinity: ListTileControlAffinity.leading, + title: const Text('全字匹配', style: TextStyle(fontSize: 12)), + value: controller.wholeWord, + onChanged: + controller.customRule + ? null + : (value) { + controller.wholeWord = value!; + controller.customRule = false; + }, + activeColor: controller.customRule ? Colors.grey : null, + ), + ), + ], + ), + // 第二行复选框 + Row( + children: [ + Expanded( + child: CheckboxListTile( + dense: true, + contentPadding: EdgeInsets.zero, + controlAffinity: ListTileControlAffinity.leading, + title: const Text('正则匹配', style: TextStyle(fontSize: 12)), + value: controller.useRegex, + onChanged: + controller.customRule + ? null + : (value) { + controller.useRegex = value!; + controller.customRule = false; + }, + activeColor: controller.customRule ? Colors.grey : null, + ), + ), + Expanded( + child: CheckboxListTile( + dense: true, + contentPadding: EdgeInsets.zero, + controlAffinity: ListTileControlAffinity.leading, + title: const Text('自定义规则', style: TextStyle(fontSize: 12)), + value: controller.customRule, + onChanged: (value) { + controller.customRule = value!; + if (value) { + controller.caseSensitive = false; + controller.wholeWord = false; + controller.useRegex = false; + } + }, + ), + ), + ], + ), + const SizedBox(height: 8), + // 仅在自定义规则选中时显示的JS函数说明 + Visibility( + visible: controller.customRule, + child: const Column( children: [ - Radio( - value: SearchMode.count, - groupValue: controller.searchMode, - onChanged: (value) => controller.searchMode = value!, + Text.rich( + TextSpan( + text: + "在搜索内容输入框中输入js函数体:\n" + "function boolean match(content) {//content为单行文本\n" + "}//返回true或者false", + style: TextStyle(fontSize: 12, fontFamily: 'monospace'), + ), ), - const Text('计数', style: TextStyle(fontSize: 12)), + SizedBox(height: 8), ], ), - ], - ), - const SizedBox(height: 8), - // 匹配规则 - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('匹配规则:', style: TextStyle(fontSize: 12)), - CheckboxListTile( - contentPadding: EdgeInsets.zero, - controlAffinity: ListTileControlAffinity.leading, - title: const Text('大小写敏感', style: TextStyle(fontSize: 12)), - value: controller.caseSensitive, - onChanged: (value) => controller.caseSensitive = value!, - ), - CheckboxListTile( - contentPadding: EdgeInsets.zero, - controlAffinity: ListTileControlAffinity.leading, - title: const Text('全字匹配', style: TextStyle(fontSize: 12)), - value: controller.wholeWord, - onChanged: (value) => controller.wholeWord = value!, - ), - CheckboxListTile( - contentPadding: EdgeInsets.zero, - controlAffinity: ListTileControlAffinity.leading, - title: const Text('正则匹配', style: TextStyle(fontSize: 12)), - value: controller.useRegex, - onChanged: (value) => controller.useRegex = value!, + ), + // 开始搜索按钮 + Align( + alignment: Alignment.bottomCenter, + child: ElevatedButton.icon( + icon: const Icon(Icons.search, size: 20), + label: const Text('开始搜索'), + onPressed: controller.startSearch, ), - ], - ), - const SizedBox(height: 8), - // 开始搜索按钮 - Align( - alignment: Alignment.centerLeft, - child: ElevatedButton.icon( - icon: const Icon(Icons.search, size: 20), - label: const Text('开始搜索'), - onPressed: controller.startSearch, ), - ), - ], + ], + ), ), ), - ), ], ), ),