From 1e97eb3cb18f05d10130b3473d78471b6f5706db Mon Sep 17 00:00:00 2001 From: hejl Date: Sun, 11 May 2025 15:22:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=AB=98=E4=BA=AE=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- win_text_editor/lib/app/widgets/text_tab.dart | 132 +++++++++++++++--- win_text_editor/pubspec.lock | 16 +++ win_text_editor/pubspec.yaml | 4 +- 3 files changed, 131 insertions(+), 21 deletions(-) diff --git a/win_text_editor/lib/app/widgets/text_tab.dart b/win_text_editor/lib/app/widgets/text_tab.dart index c2b96cb..93743bd 100644 --- a/win_text_editor/lib/app/widgets/text_tab.dart +++ b/win_text_editor/lib/app/widgets/text_tab.dart @@ -3,7 +3,10 @@ import 'package:provider/provider.dart'; import 'package:flutter/services.dart'; // 复制功能需要 import 'package:win_text_editor/app/providers/editor_provider.dart'; import 'package:file_picker/file_picker.dart'; -import 'dart:io'; // 用于文件操作 +import 'dart:io'; +import 'package:flutter_highlight/flutter_highlight.dart'; +import 'package:flutter_highlight/themes/github.dart'; +import 'package:flutter_highlight/themes/monokai-sublime.dart'; class TextTab extends StatefulWidget { final String tabId; @@ -17,39 +20,91 @@ class TextTab extends StatefulWidget { class _TextTabState extends State { late TextEditingController _controller; late EditorProvider _provider; + late FocusNode _focusNode; + String _language = 'plaintext'; @override void initState() { super.initState(); _provider = Provider.of(context, listen: false); _controller = TextEditingController(text: _getCurrentContent()); + _focusNode = FocusNode(); + _detectLanguage(); } String _getCurrentContent() { return _provider.tabs.firstWhere((t) => t.id == widget.tabId).content; } + void _detectLanguage() { + final tab = _provider.tabs.firstWhere((t) => t.id == widget.tabId); + final fileName = tab.title.toLowerCase(); + final content = tab.content.trim(); + + if (fileName.endsWith('.dart')) { + _language = 'dart'; + } else if (fileName.endsWith('.java')) { + _language = 'java'; + } else if (fileName.endsWith('.py')) { + _language = 'python'; + } else if (fileName.endsWith('.js')) { + _language = 'javascript'; + } else if (fileName.endsWith('.html')) { + _language = 'html'; + } else if (fileName.endsWith('.css')) { + _language = 'css'; + } else if (fileName.endsWith('.json')) { + _language = 'json'; + } else if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) { + _language = 'yaml'; + } else if (fileName.endsWith('.xml')) { + _language = 'xml'; + } + // 如果扩展名未识别,检查内容特征 + else if (_isLikelyXml(content)) { + _language = 'xml'; + } else { + _language = 'plaintext'; + } + } + + bool _isLikelyXml(String content) { + if (content.isEmpty) return false; + + // 简单检查:内容以'<'开头,以'>'结尾 + final trimmed = content.trim(); + if (!trimmed.startsWith('<') || !trimmed.endsWith('>')) { + return false; + } + + // 更复杂的检查:包含常见的XML标签模式 + final xmlTagRegex = RegExp(r'<[^/>]+>.*<\/[^>]+>|<[^/>]+\/>'); + return xmlTagRegex.hasMatch(content); + } + @override void didUpdateWidget(TextTab oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.tabId != widget.tabId) { _controller.text = _getCurrentContent(); + _detectLanguage(); } } @override void dispose() { _controller.dispose(); + _focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - final tab = _provider.tabs.firstWhere((t) => t.id == widget.tabId); // Add this line + final tab = _provider.tabs.firstWhere((t) => t.id == widget.tabId); + final isDarkMode = Theme.of(context).brightness == Brightness.dark; return Column( children: [ - // 新增工具条 Container( height: 40, padding: const EdgeInsets.symmetric(horizontal: 16), @@ -82,21 +137,64 @@ class _TextTabState extends State { tooltip: '保存到文件', onPressed: tab.content.isEmpty ? null : () => _saveFile(context, tab.content), ), + DropdownButton( + value: _language, + icon: const Icon(Icons.code, size: 20), + underline: Container(), + onChanged: (String? newValue) { + setState(() { + _language = newValue!; + }); + }, + items: + [ + 'plaintext', + 'dart', + 'java', + 'python', + 'javascript', + 'html', + 'css', + 'json', + 'yaml', + ].map>((String value) { + return DropdownMenuItem(value: value, child: Text(value)); + }).toList(), + ), ], ), ], ), ), - // 文本编辑区 Expanded( - child: TextField( - controller: _controller, - maxLines: null, - onChanged: (text) => _provider.updateContent(widget.tabId, text), - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.all(16), - ), + child: Stack( + children: [ + // 高亮显示的背景文本 + HighlightView( + tab.content, + language: _language, + theme: isDarkMode ? monokaiSublimeTheme : githubTheme, + padding: const EdgeInsets.all(16), + textStyle: const TextStyle(fontFamily: 'monospace', fontSize: 14), + ), + // 实际可编辑的TextField + TextField( + controller: _controller, + focusNode: _focusNode, + maxLines: null, + onChanged: (text) => _provider.updateContent(widget.tabId, text), + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.all(16), + ), + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 14, + color: Colors.transparent, // 使文本透明,只显示高亮背景 + ), + cursorColor: Colors.black, // 光标颜色 + ), + ], ), ), ], @@ -110,14 +208,12 @@ class _TextTabState extends State { if (result != null && result.files.single.path != null) { final file = File(result.files.single.path!); - - // 尝试读取文件内容 final content = await file.readAsString(); - // 同时更新Provider和控制器 _provider.updateContent(widget.tabId, content); setState(() { _controller.text = content; + _detectLanguage(); }); // 强制刷新文本控制器 @@ -126,10 +222,8 @@ class _TextTabState extends State { } } } on FormatException { - // 二进制文件错误 ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('这不是可读的文本文件'))); } on FileSystemException catch (e) { - // 文件系统错误 ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('文件访问错误: ${e.message}'))); } catch (e) { if (context.mounted) { @@ -160,7 +254,7 @@ class _TextTabState extends State { type: FileType.any, ); - if (outputPath == null) return; // 用户取消了 + if (outputPath == null) return; final file = File(outputPath); @@ -186,7 +280,7 @@ class _TextTabState extends State { ), ); - if (shouldOverwrite != true) return; // 用户选择不覆盖 + if (shouldOverwrite != true) return; } // 写入文件 diff --git a/win_text_editor/pubspec.lock b/win_text_editor/pubspec.lock index b887ca7..f27c3d5 100644 --- a/win_text_editor/pubspec.lock +++ b/win_text_editor/pubspec.lock @@ -126,6 +126,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_highlight: + dependency: "direct main" + description: + name: flutter_highlight + sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" flutter_lints: dependency: "direct dev" description: @@ -160,6 +168,14 @@ packages: description: flutter source: sdk version: "0.0.0" + highlight: + dependency: "direct main" + description: + name: highlight + sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" leak_tracker: dependency: transitive description: diff --git a/win_text_editor/pubspec.yaml b/win_text_editor/pubspec.yaml index 1065a37..b3a9afd 100644 --- a/win_text_editor/pubspec.yaml +++ b/win_text_editor/pubspec.yaml @@ -12,12 +12,12 @@ dependencies: provider: ^6.1.5 path_provider: ^2.1.5 file_picker: ^10.1.5 - # flutter_highlight: ^0.7.0 - # highlight: ^0.7.0 window_manager: ^0.3.1 bitsdojo_window: ^0.1.1+2 flutter_syntax_view: ^4.1.7 expandable: ^5.0.1 + flutter_highlight: ^0.7.0 + highlight: ^0.7.0 dev_dependencies: