Browse Source

分词及搜索功能完成

master
hejl 1 month ago
parent
commit
c71b7a7c07
  1. 3
      win_text_editor/assets/config/words_classes.yaml
  2. 349045
      win_text_editor/assets/dict.txt
  3. 0
      win_text_editor/assets/prob_emit.txt
  4. 160
      win_text_editor/lib/modules/outline/controllers/outline_provider.dart
  5. 65
      win_text_editor/lib/modules/outline/models/outline_node.dart
  6. 225
      win_text_editor/lib/modules/outline/services/outline_service.dart
  7. 173
      win_text_editor/lib/modules/outline/widgets/outline_explorer.dart
  8. 28
      win_text_editor/lib/modules/outline/widgets/outline_view.dart
  9. 74
      win_text_editor/lib/process.dart
  10. 8
      win_text_editor/pubspec.lock
  11. 3
      win_text_editor/pubspec.yaml

3
win_text_editor/assets/config/words_classes.yaml

@ -0,0 +1,3 @@
outline_name_black_list:
- 的,了,和,是,在
- 历史,日志

349045
win_text_editor/assets/dict.txt

File diff suppressed because it is too large Load Diff

0
win_text_editor/assets/prob_emit.txt

160
win_text_editor/lib/modules/outline/controllers/outline_provider.dart

@ -1,19 +1,16 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:win_text_editor/framework/controllers/logger.dart'; import 'package:win_text_editor/framework/controllers/logger.dart';
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; import 'package:win_text_editor/modules/outline/models/outline_node.dart';
import 'package:win_text_editor/modules/outline/services/outline_service.dart'; import 'package:win_text_editor/modules/outline/services/outline_service.dart';
class OutlineProvider with ChangeNotifier { class OutlineProvider with ChangeNotifier {
List<OutlineNode> _fileNodes = []; List<OutlineNode> _outlineNode = [];
bool _isLoading = false; bool _isLoading = true;
String _searchQuery = ''; String _searchQuery = '';
String? _currentRootPath; // String? _currentRootPath; //
bool get isLoading => _isLoading; bool get isLoading => _isLoading;
bool get hasRoot => _fileNodes.isNotEmpty && _fileNodes[0].isRoot; bool get hasRoot => _outlineNode.isNotEmpty && _outlineNode[0].isRoot;
// _initOutlineTree调用 // _initOutlineTree调用
OutlineProvider(); OutlineProvider();
@ -26,8 +23,10 @@ class OutlineProvider with ChangeNotifier {
await _loadRootDirectory(); await _loadRootDirectory();
} }
List<OutlineNode> get fileNodes => List<OutlineNode> get outlineNode =>
_searchQuery.isEmpty ? _fileNodes : _fileNodes.where((node) => _filterNode(node)).toList(); _searchQuery.isEmpty
? _outlineNode
: _outlineNode.where((node) => _filterNode(node)).toList();
bool _filterNode(OutlineNode node) { bool _filterNode(OutlineNode node) {
if (node.name.toLowerCase().contains(_searchQuery.toLowerCase())) { if (node.name.toLowerCase().contains(_searchQuery.toLowerCase())) {
@ -46,38 +45,15 @@ class OutlineProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> pickAndOpenOutline() async {
final result = await FilePicker.platform.pickFiles();
if (result != null && result.files.single.path != null) {
//
Logger().info('Outline selected: ${result.files.single.path}');
}
}
Future<void> loadDirectory(String path) async { Future<void> loadDirectory(String path) async {
_isLoading = true; _isLoading = true;
notifyListeners(); notifyListeners();
try {
final directory = Directory(path);
final displayName = await OutlineService.getModuleDisplayName(directory.path);
final rootNode = OutlineNode(
name: displayName ?? directory.path.split(Platform.pathSeparator).last,
path: directory.path,
isDirectory: true,
isRoot: true,
children: await OutlineService.buildOutlineTree(directory.path),
);
_fileNodes = [rootNode];
} catch (e) {
Logger().error('Error loading directory: $e');
_fileNodes = [];
}
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
} }
// outline_provider.dart _loadRootDirectory
Future<void> _loadRootDirectory() async { Future<void> _loadRootDirectory() async {
if (_currentRootPath == null) return; if (_currentRootPath == null) return;
@ -85,48 +61,35 @@ class OutlineProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
final displayName = await OutlineService.getModuleDisplayName(_currentRootPath!); //
_fileNodes = [ final isValid = await OutlineService.validateDirectoryStructure(_currentRootPath!);
OutlineNode(
name: displayName ?? _currentRootPath!.split(Platform.pathSeparator).last, if (!isValid) {
path: _currentRootPath!, Logger().error('目录结构不完整,请检查必需的文件和目录');
isDirectory: true, _outlineNode = []; //
isRoot: true, } else {
depth: 0, // 0 //
children: [], // final wordNodes = await OutlineService.getWordNodes(_currentRootPath!);
), Logger().info('获取到 ${wordNodes.length} 个分词结果');
]; // "所有"
} catch (e) { final rootNode = OutlineNode(
Logger().error('Error loading root directory: $e'); name: '所有',
_fileNodes = []; frequency: 0,
}
_isLoading = false;
notifyListeners();
}
Future<void> loadRootDirectory(String path) async {
_isLoading = true;
notifyListeners();
try {
final displayName = await OutlineService.getModuleDisplayName(path);
_fileNodes = [
OutlineNode(
name: displayName ?? path.split(Platform.pathSeparator).last,
path: path,
isDirectory: true, isDirectory: true,
isRoot: true, isRoot: true,
children: [], // isExpanded: true,
), depth: 1,
]; children: wordNodes,
);
_outlineNode = [rootNode];
}
} catch (e) { } catch (e) {
Logger().error('Error loading root: $e'); Logger().error('加载根目录时出错: $e');
_fileNodes = []; _outlineNode = [];
} finally {
_isLoading = false;
notifyListeners();
} }
_isLoading = false;
notifyListeners();
} }
Future<void> toggleDirectory(OutlineNode dirNode) async { Future<void> toggleDirectory(OutlineNode dirNode) async {
@ -135,14 +98,6 @@ class OutlineProvider with ChangeNotifier {
_isLoading = true; _isLoading = true;
notifyListeners(); notifyListeners();
try {
dirNode.children = await OutlineService.listDirectory(dirNode.path);
dirNode.isExpanded = true;
} catch (e) {
Logger().error('Error loading directory: $e');
dirNode.children = [];
}
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
} else { } else {
@ -163,63 +118,14 @@ class OutlineProvider with ChangeNotifier {
_isLoading = true; _isLoading = true;
notifyListeners(); notifyListeners();
try {
final contents = await OutlineService.listDirectory(dirNode.path, parentDepth: dirNode.depth);
final updatedNode = dirNode.copyWith(children: contents, isExpanded: true);
_replaceNodeInTree(dirNode, updatedNode);
} catch (e) {
Logger().error('Error loading directory contents: $e');
final updatedNode = dirNode.copyWith(children: []);
_replaceNodeInTree(dirNode, updatedNode);
}
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
} }
void _replaceNodeInTree(OutlineNode oldNode, OutlineNode newNode) {
for (int i = 0; i < _fileNodes.length; i++) {
if (_fileNodes[i] == oldNode) {
_fileNodes[i] = newNode;
return;
}
_replaceNodeInChildren(_fileNodes[i], oldNode, newNode);
}
}
void _replaceNodeInChildren(OutlineNode parent, OutlineNode oldNode, OutlineNode newNode) {
for (int i = 0; i < parent.children.length; i++) {
if (parent.children[i] == oldNode) {
parent.children[i] = newNode;
return;
}
_replaceNodeInChildren(parent.children[i], oldNode, newNode);
}
}
Future<void> refreshOutlineTree({bool loadContent = false}) async { Future<void> refreshOutlineTree({bool loadContent = false}) async {
_isLoading = true; _isLoading = true;
notifyListeners(); notifyListeners();
try {
final rootDir = await getApplicationDocumentsDirectory();
_fileNodes = [
OutlineNode(
name: rootDir.path.split(Platform.pathSeparator).last,
path: rootDir.path,
isDirectory: true,
isRoot: true,
//
children: loadContent ? await OutlineService.listDirectory(rootDir.path) : [],
),
];
} catch (e) {
Logger().error('Error refreshing file tree: $e');
_fileNodes = [];
}
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
} }

65
win_text_editor/lib/modules/outline/models/outline_node.dart

@ -5,7 +5,6 @@ import 'package:win_text_editor/shared/components/tree_view.dart';
class OutlineNode implements TreeNode { class OutlineNode implements TreeNode {
@override @override
final String name; final String name;
final String path;
@override @override
final bool isDirectory; final bool isDirectory;
final bool isRoot; final bool isRoot;
@ -15,10 +14,11 @@ class OutlineNode implements TreeNode {
List<OutlineNode> children; List<OutlineNode> children;
@override @override
bool isExpanded; bool isExpanded;
final int frequency;
OutlineNode({ OutlineNode({
required this.name, required this.name,
required this.path, required this.frequency,
required this.isDirectory, required this.isDirectory,
this.isRoot = false, this.isRoot = false,
this.depth = 0, this.depth = 0,
@ -27,7 +27,7 @@ class OutlineNode implements TreeNode {
}) : children = children ?? []; }) : children = children ?? [];
@override @override
String get id => path; String get id => '$depth-$name';
// //
@override @override
@ -36,56 +36,7 @@ class OutlineNode implements TreeNode {
return Icons.folder; return Icons.folder;
} }
final ext = name.split('.').last.toLowerCase(); return Icons.insert_drive_file;
//
switch (ext) {
case 'pdf':
return Icons.picture_as_pdf;
case 'doc':
case 'docx':
return Icons.article;
case 'xls':
case 'xlsx':
return Icons.table_chart;
case 'ppt':
case 'pptx':
return Icons.slideshow;
case 'txt':
return Icons.text_snippet;
case 'dart':
return Icons.code;
case 'js':
return Icons.javascript;
case 'java':
return Icons.coffee;
case 'py':
return Icons.data_object;
case 'html':
return Icons.html;
case 'css':
return Icons.css;
case 'json':
return Icons.data_array;
case 'png':
case 'jpg':
case 'jpeg':
case 'gif':
return Icons.image;
case 'mp3':
case 'wav':
return Icons.audiotrack;
case 'mp4':
case 'avi':
case 'mov':
return Icons.videocam;
case 'zip':
case 'rar':
case '7z':
return Icons.archive;
default:
return Icons.insert_drive_file;
}
} }
// //
@ -95,7 +46,7 @@ class OutlineNode implements TreeNode {
OutlineNode copyWith({ OutlineNode copyWith({
String? name, String? name,
String? path, int? frequency,
bool? isDirectory, bool? isDirectory,
bool? isExpanded, bool? isExpanded,
bool? isRoot, bool? isRoot,
@ -104,7 +55,7 @@ class OutlineNode implements TreeNode {
}) { }) {
return OutlineNode( return OutlineNode(
name: name ?? this.name, name: name ?? this.name,
path: path ?? this.path, frequency: frequency ?? this.frequency,
isDirectory: isDirectory ?? this.isDirectory, isDirectory: isDirectory ?? this.isDirectory,
isExpanded: isExpanded ?? this.isExpanded, isExpanded: isExpanded ?? this.isExpanded,
isRoot: isRoot ?? this.isRoot, isRoot: isRoot ?? this.isRoot,
@ -118,7 +69,7 @@ class OutlineNode implements TreeNode {
if (identical(this, other)) return true; if (identical(this, other)) return true;
return other is OutlineNode && return other is OutlineNode &&
other.name == name && other.name == name &&
other.path == path && other.frequency == frequency &&
other.isDirectory == isDirectory && other.isDirectory == isDirectory &&
other.isRoot == isRoot && other.isRoot == isRoot &&
listEquals(other.children, children) && listEquals(other.children, children) &&
@ -128,7 +79,7 @@ class OutlineNode implements TreeNode {
@override @override
int get hashCode { int get hashCode {
return name.hashCode ^ return name.hashCode ^
path.hashCode ^ frequency.hashCode ^
isDirectory.hashCode ^ isDirectory.hashCode ^
isRoot.hashCode ^ isRoot.hashCode ^
children.hashCode ^ children.hashCode ^

225
win_text_editor/lib/modules/outline/services/outline_service.dart

@ -1,128 +1,151 @@
// outline_service.dart
import 'dart:io'; import 'dart:io';
import 'package:win_text_editor/framework/controllers/logger.dart'; import 'package:flutter/services.dart';
import 'package:win_text_editor/framework/services/fast_xml_parser.dart'; import 'package:jieba_flutter/analysis/jieba_segmenter.dart';
import 'package:jieba_flutter/analysis/seg_token.dart';
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; import 'package:win_text_editor/modules/outline/models/outline_node.dart';
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
import 'package:yaml/yaml.dart';
import 'package:win_text_editor/framework/controllers/logger.dart';
class OutlineService { class OutlineService {
static const _specialExtensions = [ static List<String> _blackList = [];
'.uftfunction', static bool _isJiebaInitialized = false; //
'.uftservice', static JiebaSegmenter? _segmenter; //
'.uftatomfunction',
'.uftatomservice', //
'.uftfactorfunction', static Future<void> _initJieba() async {
'.uftfactorservice', if (!_isJiebaInitialized) {
]; await JiebaSegmenter.init(); // 使
static const Map<String, String> _uftFloders = { _segmenter = JiebaSegmenter(); //
'.settings': '项目设置', _isJiebaInitialized = true;
'metadata': '元数据',
'tools': '工具资源',
'uftatom': 'UFT原子',
'uftbusiness': 'UFT业务逻辑',
'uftfactor': 'UFT因子',
'uftstructure': 'UFT对象',
};
static const _hiddenFiles = ['.classpath', '.project', '.respath', 'project.xml', 'module.xml'];
static Future<String?> getSpecialFileName(String filePath) async {
final extension = filePath.substring(filePath.lastIndexOf('.'));
if (!_specialExtensions.contains(extension)) {
return null;
} }
}
//
// -
static Future<void> _initBlackList() async {
try { try {
final result = await FastXmlParser.parse(filePath); final yamlString = await rootBundle.loadString('assets/config/words_classes.yaml');
return ('[${result['objectId']}]${result['chineseName']}'); final yamlMap = loadYaml(yamlString);
} catch (e) {
Logger().debug('Error reading special file: $e');
}
return null;
}
/// //
static Future<List<OutlineNode>> listDirectory(String path, {int parentDepth = 0}) async { final blackListItems = (yamlMap['outline_name_black_list'] as List?)?.cast<String>() ?? [];
final dir = Directory(path);
final List<FileSystemEntity> entities = await dir.list().toList();
final List<OutlineNode> nodes = [];
// final stopwatch = Stopwatch()..start();
for (final entity in entities) {
final pathName = entity.path.split(Platform.pathSeparator).last;
if (_hiddenFiles.contains(pathName)) continue;
final isDirectory = await FileSystemEntity.isDirectory(entity.path);
final displayName =
isDirectory
? await getModuleDisplayName(entity.path)
: await getSpecialFileName(entity.path);
nodes.add(
OutlineNode(
name: displayName ?? pathName,
path: entity.path,
isDirectory: isDirectory,
depth: parentDepth + 1,
),
);
}
// stopwatch.stop(); //
// Logger().debug('执行耗时: ${stopwatch.elapsedMilliseconds} 毫秒 (ms)'); _blackList =
blackListItems.expand<String>((item) {
return item.split(',').map((word) => word.trim()).where((word) => word.isNotEmpty);
}).toList();
return nodes; Logger().info('加载黑名单成功: ${_blackList.length}个词');
} catch (e) {
Logger().error('加载黑名单失败: $e');
_blackList = [];
}
} }
static Future<String?> getModuleDisplayName(String dirPath) async { // stdfield文件获取中文名称
static Future<List<String>> _parseChineseNames(String filePath) async {
try { try {
final floderName = dirPath.split(Platform.pathSeparator).last; final file = File(filePath);
if (_uftFloders.containsKey(floderName)) return _uftFloders[floderName]; final content = await file.readAsString();
final document = XmlDocument.parse(content);
final moduleFile = File('$dirPath${Platform.pathSeparator}module.xml');
if (await moduleFile.exists()) { return document
final content = await moduleFile.readAsString(); .findAllElements('items')
final xmlDoc = XmlDocument.parse(content); .map((e) => e.getAttribute('chineseName') ?? '')
final infoNode = xmlDoc.findAllElements('info').firstOrNull; .where((name) => name.isNotEmpty)
return infoNode?.getAttribute('cname'); .toList();
}
} catch (e) { } catch (e) {
Logger().debug('Error reading module.xml: $e'); Logger().error('解析stdfield文件失败: $e');
return [];
} }
return null;
} }
/// //
static Future<List<OutlineNode>> buildOutlineTree(String rootPath) async { static Future<Map<String, int>> _analyzeWords(List<String> chineseNames) async {
final rootDirectory = Directory(rootPath); await _initJieba(); //
final List<OutlineNode> nodes = []; final wordFrequency = <String, int>{};
Logger().info('开始分词,共有 ${chineseNames.length} 个中文名称');
if (await rootDirectory.exists()) { for (final name in chineseNames) {
final entities = rootDirectory.listSync(); List<SegToken> tokens = _segmenter!.process(name, SegMode.SEARCH); // 使
for (final token in tokens) {
for (final entity in entities) { final word = token.word.trim();
final pathName = entity.path.split(Platform.pathSeparator).last; if (word.length > 1 && !_blackList.contains(word)) {
if (_hiddenFiles.contains(pathName)) continue; wordFrequency[word] = (wordFrequency[word] ?? 0) + 1;
final node = OutlineNode(
name: pathName,
path: entity.path,
isDirectory: entity is Directory,
);
if (entity is Directory) {
node.children.addAll(await buildOutlineTree(entity.path));
} }
nodes.add(node);
} }
} }
Logger().info('分词完成,共找到 ${wordFrequency.length} 个有效词语');
return nodes; return wordFrequency;
} }
static Future<String> readFile(String filePath) async { //
return await File(filePath).readAsString(); static Future<List<OutlineNode>> getWordNodes(String rootPath) async {
await _initBlackList();
final stdfieldPath = '$rootPath/metadata/stdfield.stdfield';
if (!await File(stdfieldPath).exists()) {
Logger().error('stdfield文件不存在');
return [];
}
final chineseNames = await _parseChineseNames(stdfieldPath);
if (chineseNames.isEmpty) {
Logger().error('未找到有效的中文名称');
return [];
}
Logger().info('找到 ${chineseNames.length} 个标准字段');
final wordFrequency = await _analyzeWords(chineseNames);
final sortedWords = wordFrequency.entries.toList()..sort((a, b) => b.value.compareTo(a.value));
return sortedWords
.map(
(entry) => OutlineNode(
name: '${entry.key}(${entry.value})',
frequency: entry.value,
isDirectory: true,
depth: 2,
),
)
.toList();
} }
static Future<void> writeFile(String filePath, String content) async { //
await File(filePath).writeAsString(content); static Future<bool> validateDirectoryStructure(String rootPath) async {
try {
//
final requiredFiles = [
'metadata/stdfield.stdfield',
'metadata/stdobj.xml',
'metadata/component.xml',
];
for (var filePath in requiredFiles) {
final file = File('$rootPath/$filePath');
if (!await file.exists()) {
Logger().error('缺少必要文件: $filePath');
return false;
}
}
//
final requiredDirs = ['uftstructure', 'uftatom', 'uftbusiness', 'uftfactor'];
for (var dirPath in requiredDirs) {
final dir = Directory('$rootPath/$dirPath');
if (!await dir.exists()) {
Logger().error('缺少必要目录: $dirPath');
return false;
}
}
return true;
} catch (e) {
Logger().error('验证目录结构时出错: $e');
return false;
}
} }
} }

173
win_text_editor/lib/modules/outline/widgets/outline_explorer.dart

@ -2,6 +2,8 @@ import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:win_text_editor/framework/controllers/logger.dart';
import 'package:win_text_editor/framework/services/file_path_manager.dart';
import 'package:win_text_editor/modules/outline/controllers/outline_provider.dart'; import 'package:win_text_editor/modules/outline/controllers/outline_provider.dart';
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; import 'package:win_text_editor/modules/outline/models/outline_node.dart';
import 'package:win_text_editor/shared/components/tree_view.dart'; import 'package:win_text_editor/shared/components/tree_view.dart';
@ -17,17 +19,20 @@ class OutlineExplorer extends StatefulWidget {
} }
class _OutlineExplorerState extends State<OutlineExplorer> { class _OutlineExplorerState extends State<OutlineExplorer> {
final ScrollController _scrollController = ScrollController(); // ScrollController final ScrollController _scrollController = ScrollController();
final TextEditingController _searchController = TextEditingController();
String _searchQuery = '';
@override @override
void dispose() { void dispose() {
_scrollController.dispose(); // controller _scrollController.dispose();
_searchController.dispose();
super.dispose(); super.dispose();
} }
// //
double calculateTotalWidth(BuildContext context, OutlineProvider fileProvider) { double calculateTotalWidth(BuildContext context, OutlineProvider outlineProvider) {
final maxDepth = _getMaxDepth(fileProvider.fileNodes); final maxDepth = _getMaxDepth(outlineProvider.outlineNode);
return maxDepth * 60 + MediaQuery.of(context).size.width * 0.2; return maxDepth * 60 + MediaQuery.of(context).size.width * 0.2;
} }
@ -41,45 +46,155 @@ class _OutlineExplorerState extends State<OutlineExplorer> {
return maxDepth; return maxDepth;
} }
//
// -
List<OutlineNode> _filterNodes(List<OutlineNode> nodes) {
if (_searchQuery.isEmpty) {
return nodes;
}
//
List<OutlineNode> recursiveFilter(List<OutlineNode> nodesToFilter, int currentDepth) {
final List<OutlineNode> result = [];
for (final node in nodesToFilter) {
// (01)
if (currentDepth == 1 && node.name.toLowerCase().contains(_searchQuery.toLowerCase())) {
result.add(node);
}
//
if (node.isDirectory && node.children.isNotEmpty) {
result.addAll(recursiveFilter(node.children, currentDepth + 1));
}
}
return result;
}
// 0
return recursiveFilter(nodes, 0);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final fileProvider = Provider.of<OutlineProvider>(context); final outlineProvider = Provider.of<OutlineProvider>(context);
_loadLastOpenedFolder(outlineProvider);
return Scrollbar(
controller: _scrollController, // controller return Column(
thumbVisibility: true, children: [
child: SingleChildScrollView( //
controller: _scrollController, // 使controller Padding(
scrollDirection: Axis.horizontal, padding: const EdgeInsets.all(4.0),
child: Container(
color: Colors.white,
child: SizedBox( child: SizedBox(
width: calculateTotalWidth(context, fileProvider), height: 30, //
child: TreeView( child: TextField(
nodes: fileProvider.fileNodes, controller: _searchController,
config: const TreeViewConfig(showIcons: true, lazyLoad: true), style: const TextStyle(fontSize: 12), // 10
onNodeTap: (node) => _handleNodeTap(context, node as OutlineNode), decoration: InputDecoration(
onNodeDoubleTap: (node) => _handleNodeDoubleTap(node as OutlineNode), hintText: '搜索节点...',
hintStyle: const TextStyle(fontSize: 12), // 10
prefixIcon: const Icon(
Icons.search,
size: 14, // 10
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: Colors.grey, //
width: 1.0,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: Colors.grey, //
width: 1.0,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: Colors.grey, //
width: 1.0,
),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 4.0, //
horizontal: 6.0,
),
),
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
),
),
),
Expanded(
child: Scrollbar(
controller: _scrollController,
thumbVisibility: true,
child: Stack(
children: [
SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
child: Container(
color: Colors.white,
child: SizedBox(
width: 300,
child: TreeView(
nodes: _filterNodes(outlineProvider.outlineNode),
config: const TreeViewConfig(showIcons: true, lazyLoad: true),
onNodeTap: (node) => _handleNodeTap(context, node as OutlineNode),
onNodeDoubleTap: (node) => _handleNodeDoubleTap(node as OutlineNode),
),
),
),
),
if (outlineProvider.isLoading)
Positioned.fill(
child: Container(
color: Colors.black.withAlpha(3),
child: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 10),
Text("加载中...", style: TextStyle(color: Colors.blue)),
],
),
),
),
),
],
), ),
), ),
), ),
), ],
); );
} }
Future<void> _handleNodeTap(BuildContext context, OutlineNode node) async { Future<void> _handleNodeTap(BuildContext context, OutlineNode node) async {
final fileProvider = Provider.of<OutlineProvider>(context, listen: false); final outlineProvider = Provider.of<OutlineProvider>(context, listen: false);
if (node.isDirectory) { if (node.isDirectory) {
await fileProvider.loadDirectoryContents(node); await outlineProvider.loadDirectoryContents(node);
} }
} }
void _handleNodeDoubleTap(TreeNode node) { void _handleNodeDoubleTap(TreeNode node) {}
final fileNode = node as OutlineNode;
if (fileNode.isDirectory && widget.onFolderDoubleTap != null) { Future<void> _loadLastOpenedFolder(OutlineProvider outlineProvider) async {
widget.onFolderDoubleTap!(fileNode.path); if (outlineProvider.rootPath != null && outlineProvider.rootPath!.isNotEmpty) {
} else if (!fileNode.isDirectory && widget.onFileDoubleTap != null) { return;
widget.onFileDoubleTap!(fileNode.path); }
final String? lastOpenedFolder = await FilePathManager.getLastOpenedFolder();
if (lastOpenedFolder != null) {
await outlineProvider.setRootPath(lastOpenedFolder);
} }
} }
} }

28
win_text_editor/lib/modules/outline/widgets/outline_view.dart

@ -25,7 +25,7 @@ class _OutlineViewState extends State<OutlineView> {
super.initState(); super.initState();
_outlineProvider = OutlineProvider(); // OutlineProvider _outlineProvider = OutlineProvider(); // OutlineProvider
final controllerFromManager = tabManager.getController(widget.tabId); final controllerFromManager = tabManager.getController(widget.tabId);
if (controllerFromManager != null) { if (controllerFromManager != null) {
_controller = controllerFromManager; _controller = controllerFromManager;
@ -51,22 +51,22 @@ class _OutlineViewState extends State<OutlineView> {
return ChangeNotifierProvider<OutlineProvider>.value( return ChangeNotifierProvider<OutlineProvider>.value(
value: _outlineProvider, // OutlineProvider value: _outlineProvider, // OutlineProvider
child: Row( child: Row(
children: [ children: [
const VerticalDivider(width: 1), const VerticalDivider(width: 1),
SizedBox( SizedBox(
width: 300, width: 300,
child: OutlineExplorer( child: OutlineExplorer(
onFileDoubleTap: (path) { onFileDoubleTap: (path) {
// //
}, },
onFolderDoubleTap: (path) { onFolderDoubleTap: (path) {
// //
}, },
),
), ),
), const VerticalDivider(width: 1),
const VerticalDivider(width: 1), const Expanded(child: Center(child: Text('大纲'))),
const Expanded(child: Center(child: Text('demo'))), ],
],
), ),
); );
} }

74
win_text_editor/lib/process.dart

@ -1,74 +0,0 @@
import 'dart:io';
import 'package:xml/xml.dart';
void main2() {
const dictFilePath = "D:/PBHK/pbhk_trade/Sources/ServerUFT/UFT/metadata/dict.dict";
const fieldFilePath = "D:/PBHK/pbhk_trade/Sources/ServerUFT/UFT/metadata/stdfield.stdfield";
final dictFile = File(dictFilePath);
final fieldFile = File(fieldFilePath);
if (!dictFile.existsSync()) {
print('字典文件不存在: $dictFilePath');
return;
}
if (!fieldFile.existsSync()) {
print('字段文件不存在: $fieldFilePath');
return;
}
try {
// 1.
final dictDocument = XmlDocument.parse(dictFile.readAsStringSync());
// 2.
final fieldDocument = XmlDocument.parse(fieldFile.readAsStringSync());
// 3. xsi:type="metadata:DictionaryType" items节点
final dictionaryItems =
dictDocument.findAllElements('items').where((element) {
return element.getAttribute('xsi:type') == 'metadata:DictionaryType';
}).toList();
// 4.
for (final item in dictionaryItems) {
bool hasConstantName = false;
String? dictionaryName;
//
for (final child in item.childElements) {
final constantName = child.getAttribute('constantName');
if (constantName != null && constantName.isNotEmpty) {
hasConstantName = true;
break;
}
}
// constantName属性
if (!hasConstantName) {
dictionaryName = item.getAttribute('name');
if (dictionaryName != null && dictionaryName.isNotEmpty) {
// items节点的dictionaryType等于该name值
final matchingFieldItems = fieldDocument.findAllElements('items').where((element) {
return element.getAttribute('dictionaryType') == dictionaryName;
});
//
if (matchingFieldItems.isEmpty) {
item.parent?.children.remove(item);
}
} else {
// name属性
item.parent?.children.remove(item);
}
}
}
// 5.
final outputPath = dictFilePath.replaceAll('.dict', '_modified.xml');
File(outputPath).writeAsStringSync(dictDocument.toXmlString(pretty: true));
print('处理完成,结果已保存到: $outputPath');
} catch (e) {
print('处理XML文件时出错: $e');
}
}

8
win_text_editor/pubspec.lock

@ -232,6 +232,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.19.0" version: "0.19.0"
jieba_flutter:
dependency: "direct main"
description:
name: jieba_flutter
sha256: "01d645633a05c67e526d165b3d17343b99acf77afd2230f699cc587705247fd6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:

3
win_text_editor/pubspec.yaml

@ -28,7 +28,7 @@ dependencies:
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
yaml: ^3.1.1 yaml: ^3.1.1
pluto_grid: ^8.0.0 pluto_grid: ^8.0.0
jieba_flutter: ^0.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -38,4 +38,5 @@ dev_dependencies:
flutter: flutter:
uses-material-design: true uses-material-design: true
assets: assets:
- assets/
- assets/config/ - assets/config/

Loading…
Cancel
Save