diff --git a/win_text_editor/lib/app/providers/editor_provider.dart b/win_text_editor/lib/app/providers/editor_provider.dart new file mode 100644 index 0000000..7d84b06 --- /dev/null +++ b/win_text_editor/lib/app/providers/editor_provider.dart @@ -0,0 +1,142 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:win_text_editor/app/providers/logger.dart'; + +class EditorProvider with ChangeNotifier { + final List _tabs = []; + String? _activeTabId; + bool _isLoading = false; + + List get tabs => _tabs; + String? get activeTabId => _activeTabId; + bool get isLoading => _isLoading; + + int _templateTabCounter = 1; + + final Map _tabLoadingStates = {}; + final Map _tabLoadedChunks = {}; + + bool isTabLoading(String tabId) => _tabLoadingStates[tabId] ?? false; + int getLoadedChunks(String tabId) => _tabLoadedChunks[tabId] ?? 0; + + EditorTab? get activeTab { + if (_activeTabId == null) return null; + try { + return _tabs.firstWhere((tab) => tab.id == _activeTabId); + } catch (e) { + Logger().error("找不到活动选项卡: $_activeTabId", source: 'EditorProvider'); + return null; + } + } + + void addTab() { + final tabId = DateTime.now().millisecondsSinceEpoch.toString(); + _tabs.add( + EditorTab( + id: tabId, + title: '模板解析[$_templateTabCounter]', + chunks: [], // 显式初始化 + content: '', + ), + ); + _templateTabCounter++; + _activeTabId = tabId; + notifyListeners(); + } + + void closeTab(String tabId) { + Logger().info('关闭选项卡: $tabId'); + _tabs.removeWhere((tab) => tab.id == tabId); + if (_activeTabId == tabId) { + _activeTabId = _tabs.isNotEmpty ? _tabs.last.id : null; + } + notifyListeners(); + } + + void setActiveTab(String tabId) { + Logger().info('设置活动选项卡: $tabId'); + _activeTabId = tabId; + notifyListeners(); + } + + Future updateContent(String tabId, String content, String? name) async { + _isLoading = true; + notifyListeners(); + + try { + final index = _tabs.indexWhere((t) => t.id == tabId); + if (index == -1) throw Exception("Tab not found"); + + _tabs[index] = EditorTab( + id: _tabs[index].id, + title: _tabs[index].title, + chunks: content.split('\n'), // 同步更新 chunks + fileName: name ?? _tabs[index].fileName, + content: content, + ); + } finally { + _isLoading = false; + notifyListeners(); + } + } + + // 在 EditorProvider 中修改文件加载逻辑 + Future updateContentInChunks(String tabId, String fullContent, String? fileName) async { + final lines = fullContent.split('\n'); + const linesPerChunk = 1000; // 每1000行一个块 + + _tabLoadingStates[tabId] = true; + notifyListeners(); + + try { + // 清空现有内容 + final index = _tabs.indexWhere((t) => t.id == tabId); + _tabs[index] = EditorTab( + id: tabId, + title: fileName ?? _tabs[index].title, + chunks: [], + fileName: fileName, + ); + + // 分块加载行 + for (int i = 0; i < lines.length; i += linesPerChunk) { + if (!_tabLoadingStates[tabId]!) break; // 检查是否取消 + + final chunkLines = lines.sublist(i, min(i + linesPerChunk, lines.length)); + _tabs[index].chunks!.addAll(chunkLines); + + notifyListeners(); + await Future.delayed(Duration.zero); // 让UI更新 + } + } finally { + _tabLoadingStates.remove(tabId); + notifyListeners(); + } + } + + void cancelLoading(String tabId) { + _tabLoadingStates[tabId] = false; + notifyListeners(); + } +} + +class EditorTab { + final String id; + String title; + List chunks; // 移除可空标记,改为非空 + String? fileName; + bool isHighlightEnabled; + String content; + + EditorTab({ + required this.id, + required this.title, + List? chunks, // 参数可选,但类内部非空 + this.fileName, + this.isHighlightEnabled = true, + this.content = '', + }) : chunks = chunks ?? []; // 默认值为空列表 + + String get fullContent => chunks.join('\n'); // 用换行符连接 +} diff --git a/win_text_editor/lib/app/utils/file_utils.dart b/win_text_editor/lib/app/utils/file_utils.dart new file mode 100644 index 0000000..f8b7ef1 --- /dev/null +++ b/win_text_editor/lib/app/utils/file_utils.dart @@ -0,0 +1,59 @@ +// file_utils.dart +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:win_text_editor/app/providers/logger.dart'; + +class FileUtils { + static Future pickFile(BuildContext context) async { + try { + final result = await FilePicker.platform.pickFiles(type: FileType.any, allowMultiple: false); + return result?.files.single.path; + } catch (e) { + Logger().error('选择文件失败: ${e.toString()}'); + if (context.mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('选择文件失败: ${e.toString()}'))); + } + return null; + } + } + + static Future readFileContent( + BuildContext context, + String filePath, { + Duration timeout = const Duration(seconds: 30), + }) async { + try { + final content = await File(filePath).readAsString().timeout( + timeout, + onTimeout: () { + throw TimeoutException('文件加载超时,可能文件过大'); + }, + ); + return content; + } on FormatException { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('这不是可读的文本文件'))); + } + return null; + } on FileSystemException catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('文件访问错误: ${e.message}'))); + } + return null; + } catch (e) { + Logger().error('读取文件失败: ${e.toString()}'); + if (context.mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('读取失败: ${e.toString()}'))); + } + return null; + } + } + + // 移除showLoadingDialog方法,因为现在直接在调用处处理 +} diff --git a/win_text_editor/lib/app/widgets/file_explorer.dart b/win_text_editor/lib/app/widgets/file_explorer.dart index af9ea21..93ad8de 100644 --- a/win_text_editor/lib/app/widgets/file_explorer.dart +++ b/win_text_editor/lib/app/widgets/file_explorer.dart @@ -1,11 +1,13 @@ +import 'dart:io'; + import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:win_text_editor/app/providers/tab_provider.dart'; import 'package:win_text_editor/app/providers/logger.dart'; import '../models/file_node.dart'; import '../providers/file_provider.dart'; +import '../providers/editor_provider.dart'; import 'dart:math'; @@ -55,8 +57,11 @@ class _FileExplorerState extends State { final fileProvider = Provider.of(context, listen: false); if (node.isDirectory) { await fileProvider.loadDirectoryContents(node); + } else { + // Handle file opening + print("No active tab found"); + _openFileInEditor(context, node); } - // 单击文件节点时不处理 } Future _openFileInEditor(BuildContext context, FileNode node) async {