diff --git a/win_text_editor/lib/app/core/app_scaffold.dart b/win_text_editor/lib/app/core/app_scaffold.dart index 3620ac3..71bb864 100644 --- a/win_text_editor/lib/app/core/app_scaffold.dart +++ b/win_text_editor/lib/app/core/app_scaffold.dart @@ -15,18 +15,25 @@ class AppScaffold extends StatelessWidget { return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => FileProvider()), - ChangeNotifierProvider(create: (_) => TabManager()), // 改名为TabManager + ChangeNotifierProvider(create: (_) => TabManager()), ], child: Scaffold( backgroundColor: Colors.grey[100], body: Column( children: [ - const AppMenu(), // 顶部菜单 + const AppMenu(), Expanded( child: Row( children: [ - // 左侧文件树 - const FileExplorerPane(), + // 左侧文件树 - 传递双击事件回调 + Consumer( + builder: (context, tabManager, child) { + return FileExplorerPane( + onFileDoubleTap: (path) => tabManager.handleFileDoubleTap(path), + onFolderDoubleTap: (path) => tabManager.handleFolderDoubleTap(path), + ); + }, + ), // 主内容区 Expanded( child: Consumer( @@ -38,7 +45,7 @@ class AppScaffold extends StatelessWidget { ], ), ), - const ConsolePanel(), // 底部状态栏 + const ConsolePanel(), ], ), ), diff --git a/win_text_editor/lib/app/core/tab_manager.dart b/win_text_editor/lib/app/core/tab_manager.dart index f1c2e3c..7041ec1 100644 --- a/win_text_editor/lib/app/core/tab_manager.dart +++ b/win_text_editor/lib/app/core/tab_manager.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:win_text_editor/app/models/tab_model.dart'; -import 'package:win_text_editor/app/modules/template_parser/template_parser_view.dart'; +import 'package:win_text_editor/app/modules/base_view.dart'; import 'package:win_text_editor/app/providers/logger.dart'; class TabManager with ChangeNotifier { @@ -10,15 +10,7 @@ class TabManager with ChangeNotifier { List get tabs => _tabs; String? get activeTabId => _activeTabId; - final Map _tabControllers = {}; - - void registerTextTabController(String tabId, TemplateParserViewState controller) { - _tabControllers[tabId] = controller; - } - - void unregisterTextTabController(String tabId) { - _tabControllers.remove(tabId); - } + BaseViewState? _activeViewState; AppTab? get activeTab { if (_activeTabId == null) return null; @@ -70,47 +62,18 @@ class TabManager with ChangeNotifier { notifyListeners(); } - void updateContent(String tabId, String content, String? name) { - try { - final tab = _tabs.firstWhere((t) => t.id == tabId); - tab.content = content; - - if (name != null) { - tab.fileName = name; - } - // Logger().debug("内容更新成功,文件:${tab.fileName}, ${tab.content.length}"); - notifyListeners(); - } catch (e) { - Logger().error("更新内容失败: ${e.toString()}", source: 'EditorProvider'); - } + void handleFolderDoubleTap(String folderPath) { + _activeViewState?.onOpenFolder(folderPath); } - Future requestLoadFile(BuildContext context, String filePath) async { - if (_activeTabId == null) { - Logger().warning("没有活动选项卡,无法加载文件"); - return; - } - - final parserTabState = _tabControllers[_activeTabId]; - if (parserTabState == null) { - Logger().warning("找不到 TextTab 状态"); - return; - } - - if (!parserTabState.mounted) { - Logger().warning("TextTab 状态组件未挂载"); - return; - } + void handleFileDoubleTap(String filePath) { + _activeViewState?.onOpenFile(filePath); + } - try { - await parserTabState.loadFile(context, filePath); - } catch (e) { - Logger().error("加载文件失败: ${e.toString()}"); - if (context.mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('加载文件失败: ${e.toString()}'))); - } + void setActiveViewState(BaseViewState? state) { + if (_activeViewState != state) { + _activeViewState = state; + // 不需要 notifyListeners(),因为这只是内部状态更新 } } } diff --git a/win_text_editor/lib/app/core/tab_view.dart b/win_text_editor/lib/app/core/tab_view.dart index 68f0dcb..e743bf1 100644 --- a/win_text_editor/lib/app/core/tab_view.dart +++ b/win_text_editor/lib/app/core/tab_view.dart @@ -1,10 +1,10 @@ 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/core/tab_manager.dart'; import 'package:win_text_editor/app/models/tab_model.dart'; import 'package:win_text_editor/app/modules/content_search/content_search_view.dart'; import 'package:win_text_editor/app/modules/template_parser/template_parser_view.dart'; -import 'package:provider/provider.dart'; class TabView extends StatelessWidget { final List tabs; @@ -58,17 +58,22 @@ class TabView extends StatelessWidget { index: tabs.indexWhere((tab) => tab.id == currentTabId).clamp(0, tabs.length - 1), children: tabs.map((tab) { - switch (tab.type) { - case 'template_parser': - return TemplateParserView(tabId: tab.id); - case 'content_search': - return ContentSearchView(tabId: tab.id); - default: - return TextEditor(tabId: tab.id, initialContent: tab.content); - } + return findActiveView(tab); }).toList(), ); } + + // 查找当前活动的视图 + Widget findActiveView(AppTab tab) { + switch (tab.type) { + case 'template_parser': + return TemplateParserView(tabId: tab.id); + case 'content_search': + return ContentSearchView(tabId: tab.id); + default: + return TextEditor(tabId: tab.id, initialContent: tab.content); + } + } } class _TabItem extends StatelessWidget { diff --git a/win_text_editor/lib/app/models/search_model.dart b/win_text_editor/lib/app/models/search_model.dart new file mode 100644 index 0000000..7eb1e64 --- /dev/null +++ b/win_text_editor/lib/app/models/search_model.dart @@ -0,0 +1,24 @@ +class SearchResult { + final String filePath; + final int lineNumber; + final String lineContent; + final List matches; + final String queryTerm; // 记录匹配的查询项 + + SearchResult({ + required this.filePath, + required this.lineNumber, + required this.lineContent, + required this.matches, + required this.queryTerm, + }); +} + +class MatchResult { + final int start; + final int end; + + const MatchResult({required this.start, required this.end}); +} + +enum SearchMode { locate, count } diff --git a/win_text_editor/lib/app/modules/base_view.dart b/win_text_editor/lib/app/modules/base_view.dart index 513c727..a6282fc 100644 --- a/win_text_editor/lib/app/modules/base_view.dart +++ b/win_text_editor/lib/app/modules/base_view.dart @@ -2,34 +2,40 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:win_text_editor/app/core/tab_manager.dart'; +// 修改后的base_view.dart abstract class BaseView extends StatefulWidget { final String tabId; - const BaseView({super.key, required this.tabId}); @override - State createState() => BaseViewState(); - - // 打开文件夹回调 - void openFolder(String folderPath); - - // 打开文件回调 - void openFile(String filePath); + BaseViewState createState(); } -class BaseViewState extends State { - late TabManager _tabManager; - - TabManager get tabManager => _tabManager; +abstract class BaseViewState extends State { + late final TabManager tabManager; @override void initState() { super.initState(); - _tabManager = Provider.of(context, listen: false); + tabManager = Provider.of(context, listen: false); } @override - Widget build(BuildContext context) { - return Container(); // 具体实现由子类完成 + void didChangeDependencies() { + super.didChangeDependencies(); + if (tabManager.activeTabId == widget.tabId) { + tabManager.setActiveViewState(this); + } } + + @override + void dispose() { + // 清理注册 + tabManager.setActiveViewState(null); + super.dispose(); + } + + // 抽象方法改为在State中定义 + void onOpenFolder(String folderPath); + void onOpenFile(String filePath); } 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 7792642..9b92a08 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,5 +1,6 @@ // content_search_controller.dart +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -7,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:file_picker/file_picker.dart'; import 'package:path/path.dart' as path; import 'package:win_text_editor/app/core/tab_manager.dart'; +import 'package:win_text_editor/app/models/search_model.dart'; import 'package:win_text_editor/app/providers/logger.dart'; import 'content_search_service.dart'; import 'package:flutter_js/flutter_js.dart'; @@ -53,10 +55,13 @@ class ContentSearchController with ChangeNotifier { } // Setters with notifyListeners + Timer? _searchDebounce; set searchQuery(String value) { - if (_searchQuery == value) return; - _searchQuery = value; - notifyListeners(); + _searchDebounce?.cancel(); + _searchDebounce = Timer(const Duration(milliseconds: 500), () { + _searchQuery = value; + notifyListeners(); + }); } set searchDirectory(String value) { @@ -186,9 +191,6 @@ class ContentSearchController with ChangeNotifier { Logger().error('自定规则逻辑错误,没有返回布尔值,返回值为: ${test.stringResult}'); return; } - - await performCustomSearch(); - // 语法检查通过,进行自定义规则搜索 } catch (e) { Logger().error('JavaScript 语法检查错误: $e'); return; @@ -196,6 +198,14 @@ class ContentSearchController with ChangeNotifier { notifyListeners(); _jsRuntime.dispose(); } + _results.addAll( + await ContentSearchService.performCustomSearch( + directory: searchDirectory, + fileType: fileType, + jsFunction: searchQuery, + searchMode: searchMode, + ), + ); } else { try { if (searchMode == SearchMode.locate) { @@ -252,5 +262,3 @@ class ContentSearchController with ChangeNotifier { return ext == fileType.toLowerCase(); } } - -enum SearchMode { locate, count } diff --git a/win_text_editor/lib/app/modules/content_search/content_search_service.dart b/win_text_editor/lib/app/modules/content_search/content_search_service.dart index b85b0ac..a09b7cd 100644 --- a/win_text_editor/lib/app/modules/content_search/content_search_service.dart +++ b/win_text_editor/lib/app/modules/content_search/content_search_service.dart @@ -1,9 +1,14 @@ // lib/app/modules/content_search/content_search_service.dart +import 'dart:convert'; import 'dart:io'; +import 'package:flutter_js/flutter_js.dart'; import 'package:path/path.dart' as path; +import 'package:win_text_editor/app/models/search_model.dart'; +import 'package:win_text_editor/app/providers/logger.dart'; class ContentSearchService { + /// 执行定位搜索(返回所有匹配项) static Future> performLocateSearch({ required String directory, required String query, @@ -65,6 +70,78 @@ class ContentSearchService { return counts; } + /// 新增方法:执行自定义 JavaScript 规则搜索 + static Future> performCustomSearch({ + required String directory, + required String fileType, + required String jsFunction, + required SearchMode searchMode, + }) async { + final results = []; + int count = 0; + final dir = Directory(directory); + final jsRuntime = getJavascriptRuntime(); + + try { + // 定义 JavaScript 函数 + final jsCode = 'function match(content){$jsFunction};'; + + jsRuntime.evaluate(jsCode); + + await for (final entity in dir.list(recursive: true)) { + if (entity is File && _matchesFileType(entity.path, fileType)) { + try { + final lines = await entity.readAsLines(); + for (int i = 0; i < lines.length; i++) { + final line = lines[i].trim(); + if (line.length < 3) continue; // 跳过短行 + + final result = jsRuntime.evaluate('match(${jsonEncode(line)});'); + if (result.isError) { + throw Exception('JS Error: ${result.stringResult}'); + } + + if (result.stringResult == 'true') { + if (searchMode == SearchMode.locate) { + results.add( + SearchResult( + filePath: entity.path, + lineNumber: i + 1, + lineContent: line, + matches: [], + queryTerm: "Custom Rule", + ), + ); + } else { + count++; + } + } + } + } catch (e) { + Logger().error('Error in file ${entity.path}: $e'); + } + } + } + + // 处理计数模式结果 + if (searchMode == SearchMode.count) { + results.add( + SearchResult( + filePath: "Custom Rule", + lineNumber: count, + lineContent: '', + matches: [], + queryTerm: "Custom Rule", + ), + ); + } + } finally { + jsRuntime.dispose(); + } + + return results; + } + /// 分割查询字符串(按半角逗号分隔,并去除空格) static List _splitQuery(String query) { return query.split(',').map((q) => q.trim()).where((q) => q.isNotEmpty).toList(); @@ -206,31 +283,7 @@ class ContentSearchService { counts[queryTerm] = (counts[queryTerm] ?? 0) + matches.length; } } catch (e) { - print('Error reading file ${file.path}: $e'); + Logger().error('Error reading file ${file.path}: $e'); } } } - -/// 搜索结果类(新增 queryTerm 字段) -class SearchResult { - final String filePath; - final int lineNumber; - final String lineContent; - final List matches; - final String queryTerm; // 记录匹配的查询项 - - SearchResult({ - required this.filePath, - required this.lineNumber, - required this.lineContent, - required this.matches, - required this.queryTerm, - }); -} - -class MatchResult { - final int start; - final int end; - - const MatchResult({required this.start, required this.end}); -} 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 23be0ca..c6af628 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 @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:win_text_editor/app/core/tab_manager.dart'; import 'package:win_text_editor/app/modules/base_view.dart'; +import 'package:win_text_editor/app/providers/logger.dart'; import 'content_search_controller.dart'; import 'directory_settings.dart'; import 'search_settings.dart'; @@ -11,19 +11,7 @@ class ContentSearchView extends BaseView { const ContentSearchView({super.key, required String tabId}) : super(tabId: tabId); @override - void openFolder(String folderPath) { - // 实现打开文件夹的逻辑 - print('Opening folder: $folderPath'); - } - - @override - void openFile(String filePath) { - // 实现打开文件的逻辑 - print('Opening file: $filePath'); - } - - @override - State createState() => ContentSearchViewState(); + ContentSearchViewState createState() => ContentSearchViewState(); } class ContentSearchViewState extends BaseViewState { @@ -32,7 +20,26 @@ class ContentSearchViewState extends BaseViewState { @override void initState() { super.initState(); - _controller = ContentSearchController(tabManager: super.tabManager); + _controller = ContentSearchController(tabManager: tabManager); + } + + @override + void dispose() { + _controller.dispose(); // 清理控制器 + super.dispose(); + } + + @override + void onOpenFolder(String folderPath) { + // 实现打开文件夹的逻辑 + Logger().debug('Opening folder: $folderPath'); + _controller.searchDirectory = folderPath; + } + + @override + void onOpenFile(String filePath) { + // 实现打开文件的逻辑 + Logger().debug('Opening file: $filePath'); } @override 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 b773814..cfab711 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 @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_datagrid/datagrid.dart'; import 'package:path/path.dart' as path; +import 'package:win_text_editor/app/models/search_model.dart'; import 'package:win_text_editor/app/modules/content_search/content_search_controller.dart'; -import 'package:win_text_editor/app/modules/content_search/content_search_service.dart'; import 'package:file_picker/file_picker.dart'; import 'dart:io'; 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 0bca41b..e56a03f 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,6 +1,7 @@ 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/models/search_model.dart'; import 'package:win_text_editor/app/modules/content_search/content_search_controller.dart'; class SearchSettings extends StatelessWidget { diff --git a/win_text_editor/lib/app/modules/template_parser/template_parser_view.dart b/win_text_editor/lib/app/modules/template_parser/template_parser_view.dart index 7c2b703..83b2ed3 100644 --- a/win_text_editor/lib/app/modules/template_parser/template_parser_view.dart +++ b/win_text_editor/lib/app/modules/template_parser/template_parser_view.dart @@ -6,19 +6,7 @@ class TemplateParserView extends BaseView { const TemplateParserView({super.key, required String tabId}) : super(tabId: tabId); @override - void openFolder(String folderPath) { - // 实现打开文件夹的逻辑 - print('Opening folder: $folderPath'); - } - - @override - void openFile(String filePath) { - // 实现打开文件的逻辑 - print('Opening file: $filePath'); - } - - @override - State createState() => TemplateParserViewState(); + TemplateParserViewState createState() => TemplateParserViewState(); } class TemplateParserViewState extends BaseViewState { @@ -37,6 +25,24 @@ class TemplateParserViewState extends BaseViewState { }); } + @override + void dispose() { + //_controller.dispose(); // 清理控制器 + super.dispose(); + } + + @override + void onOpenFolder(String folderPath) { + // 实现打开文件夹的逻辑 + print('Opening folder: $folderPath'); + } + + @override + void onOpenFile(String filePath) { + // 实现打开文件的逻辑 + print('Opening file: $filePath'); + } + // 修改loadFile方法使用_activeEditorIndex Future loadFile(BuildContext context, String filePath) async {}