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 @@ @@ -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 @@ @@ -1,19 +1,16 @@
import 'dart:io';
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/modules/outline/models/outline_node.dart';
import 'package:win_text_editor/modules/outline/services/outline_service.dart';
class OutlineProvider with ChangeNotifier {
List<OutlineNode> _fileNodes = [];
bool _isLoading = false;
List<OutlineNode> _outlineNode = [];
bool _isLoading = true;
String _searchQuery = '';
String? _currentRootPath; //
bool get isLoading => _isLoading;
bool get hasRoot => _fileNodes.isNotEmpty && _fileNodes[0].isRoot;
bool get hasRoot => _outlineNode.isNotEmpty && _outlineNode[0].isRoot;
// _initOutlineTree调用
OutlineProvider();
@ -26,8 +23,10 @@ class OutlineProvider with ChangeNotifier { @@ -26,8 +23,10 @@ class OutlineProvider with ChangeNotifier {
await _loadRootDirectory();
}
List<OutlineNode> get fileNodes =>
_searchQuery.isEmpty ? _fileNodes : _fileNodes.where((node) => _filterNode(node)).toList();
List<OutlineNode> get outlineNode =>
_searchQuery.isEmpty
? _outlineNode
: _outlineNode.where((node) => _filterNode(node)).toList();
bool _filterNode(OutlineNode node) {
if (node.name.toLowerCase().contains(_searchQuery.toLowerCase())) {
@ -46,38 +45,15 @@ class OutlineProvider with ChangeNotifier { @@ -46,38 +45,15 @@ class OutlineProvider with ChangeNotifier {
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 {
_isLoading = true;
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;
notifyListeners();
}
// outline_provider.dart _loadRootDirectory
Future<void> _loadRootDirectory() async {
if (_currentRootPath == null) return;
@ -85,48 +61,35 @@ class OutlineProvider with ChangeNotifier { @@ -85,48 +61,35 @@ class OutlineProvider with ChangeNotifier {
notifyListeners();
try {
final displayName = await OutlineService.getModuleDisplayName(_currentRootPath!);
_fileNodes = [
OutlineNode(
name: displayName ?? _currentRootPath!.split(Platform.pathSeparator).last,
path: _currentRootPath!,
isDirectory: true,
isRoot: true,
depth: 0, // 0
children: [], //
),
];
} catch (e) {
Logger().error('Error loading root directory: $e');
_fileNodes = [];
}
_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,
//
final isValid = await OutlineService.validateDirectoryStructure(_currentRootPath!);
if (!isValid) {
Logger().error('目录结构不完整,请检查必需的文件和目录');
_outlineNode = []; //
} else {
//
final wordNodes = await OutlineService.getWordNodes(_currentRootPath!);
Logger().info('获取到 ${wordNodes.length} 个分词结果');
// "所有"
final rootNode = OutlineNode(
name: '所有',
frequency: 0,
isDirectory: true,
isRoot: true,
children: [], //
),
];
isExpanded: true,
depth: 1,
children: wordNodes,
);
_outlineNode = [rootNode];
}
} catch (e) {
Logger().error('Error loading root: $e');
_fileNodes = [];
Logger().error('加载根目录时出错: $e');
_outlineNode = [];
} finally {
_isLoading = false;
notifyListeners();
}
_isLoading = false;
notifyListeners();
}
Future<void> toggleDirectory(OutlineNode dirNode) async {
@ -135,14 +98,6 @@ class OutlineProvider with ChangeNotifier { @@ -135,14 +98,6 @@ class OutlineProvider with ChangeNotifier {
_isLoading = true;
notifyListeners();
try {
dirNode.children = await OutlineService.listDirectory(dirNode.path);
dirNode.isExpanded = true;
} catch (e) {
Logger().error('Error loading directory: $e');
dirNode.children = [];
}
_isLoading = false;
notifyListeners();
} else {
@ -163,63 +118,14 @@ class OutlineProvider with ChangeNotifier { @@ -163,63 +118,14 @@ class OutlineProvider with ChangeNotifier {
_isLoading = true;
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;
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 {
_isLoading = true;
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;
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'; @@ -5,7 +5,6 @@ import 'package:win_text_editor/shared/components/tree_view.dart';
class OutlineNode implements TreeNode {
@override
final String name;
final String path;
@override
final bool isDirectory;
final bool isRoot;
@ -15,10 +14,11 @@ class OutlineNode implements TreeNode { @@ -15,10 +14,11 @@ class OutlineNode implements TreeNode {
List<OutlineNode> children;
@override
bool isExpanded;
final int frequency;
OutlineNode({
required this.name,
required this.path,
required this.frequency,
required this.isDirectory,
this.isRoot = false,
this.depth = 0,
@ -27,7 +27,7 @@ class OutlineNode implements TreeNode { @@ -27,7 +27,7 @@ class OutlineNode implements TreeNode {
}) : children = children ?? [];
@override
String get id => path;
String get id => '$depth-$name';
//
@override
@ -36,56 +36,7 @@ class OutlineNode implements TreeNode { @@ -36,56 +36,7 @@ class OutlineNode implements TreeNode {
return Icons.folder;
}
final ext = name.split('.').last.toLowerCase();
//
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;
}
return Icons.insert_drive_file;
}
//
@ -95,7 +46,7 @@ class OutlineNode implements TreeNode { @@ -95,7 +46,7 @@ class OutlineNode implements TreeNode {
OutlineNode copyWith({
String? name,
String? path,
int? frequency,
bool? isDirectory,
bool? isExpanded,
bool? isRoot,
@ -104,7 +55,7 @@ class OutlineNode implements TreeNode { @@ -104,7 +55,7 @@ class OutlineNode implements TreeNode {
}) {
return OutlineNode(
name: name ?? this.name,
path: path ?? this.path,
frequency: frequency ?? this.frequency,
isDirectory: isDirectory ?? this.isDirectory,
isExpanded: isExpanded ?? this.isExpanded,
isRoot: isRoot ?? this.isRoot,
@ -118,7 +69,7 @@ class OutlineNode implements TreeNode { @@ -118,7 +69,7 @@ class OutlineNode implements TreeNode {
if (identical(this, other)) return true;
return other is OutlineNode &&
other.name == name &&
other.path == path &&
other.frequency == frequency &&
other.isDirectory == isDirectory &&
other.isRoot == isRoot &&
listEquals(other.children, children) &&
@ -128,7 +79,7 @@ class OutlineNode implements TreeNode { @@ -128,7 +79,7 @@ class OutlineNode implements TreeNode {
@override
int get hashCode {
return name.hashCode ^
path.hashCode ^
frequency.hashCode ^
isDirectory.hashCode ^
isRoot.hashCode ^
children.hashCode ^

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

@ -1,128 +1,151 @@ @@ -1,128 +1,151 @@
// outline_service.dart
import 'dart:io';
import 'package:win_text_editor/framework/controllers/logger.dart';
import 'package:win_text_editor/framework/services/fast_xml_parser.dart';
import 'package:flutter/services.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:xml/xml.dart';
import 'package:yaml/yaml.dart';
import 'package:win_text_editor/framework/controllers/logger.dart';
class OutlineService {
static const _specialExtensions = [
'.uftfunction',
'.uftservice',
'.uftatomfunction',
'.uftatomservice',
'.uftfactorfunction',
'.uftfactorservice',
];
static const Map<String, String> _uftFloders = {
'.settings': '项目设置',
'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 List<String> _blackList = [];
static bool _isJiebaInitialized = false; //
static JiebaSegmenter? _segmenter; //
//
static Future<void> _initJieba() async {
if (!_isJiebaInitialized) {
await JiebaSegmenter.init(); // 使
_segmenter = JiebaSegmenter(); //
_isJiebaInitialized = true;
}
}
//
// -
static Future<void> _initBlackList() async {
try {
final result = await FastXmlParser.parse(filePath);
return ('[${result['objectId']}]${result['chineseName']}');
} catch (e) {
Logger().debug('Error reading special file: $e');
}
return null;
}
final yamlString = await rootBundle.loadString('assets/config/words_classes.yaml');
final yamlMap = loadYaml(yamlString);
///
static Future<List<OutlineNode>> listDirectory(String path, {int parentDepth = 0}) async {
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,
),
);
}
//
final blackListItems = (yamlMap['outline_name_black_list'] as List?)?.cast<String>() ?? [];
// 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 {
final floderName = dirPath.split(Platform.pathSeparator).last;
if (_uftFloders.containsKey(floderName)) return _uftFloders[floderName];
final moduleFile = File('$dirPath${Platform.pathSeparator}module.xml');
if (await moduleFile.exists()) {
final content = await moduleFile.readAsString();
final xmlDoc = XmlDocument.parse(content);
final infoNode = xmlDoc.findAllElements('info').firstOrNull;
return infoNode?.getAttribute('cname');
}
final file = File(filePath);
final content = await file.readAsString();
final document = XmlDocument.parse(content);
return document
.findAllElements('items')
.map((e) => e.getAttribute('chineseName') ?? '')
.where((name) => name.isNotEmpty)
.toList();
} catch (e) {
Logger().debug('Error reading module.xml: $e');
Logger().error('解析stdfield文件失败: $e');
return [];
}
return null;
}
///
static Future<List<OutlineNode>> buildOutlineTree(String rootPath) async {
final rootDirectory = Directory(rootPath);
final List<OutlineNode> nodes = [];
if (await rootDirectory.exists()) {
final entities = rootDirectory.listSync();
for (final entity in entities) {
final pathName = entity.path.split(Platform.pathSeparator).last;
if (_hiddenFiles.contains(pathName)) continue;
final node = OutlineNode(
name: pathName,
path: entity.path,
isDirectory: entity is Directory,
);
if (entity is Directory) {
node.children.addAll(await buildOutlineTree(entity.path));
//
static Future<Map<String, int>> _analyzeWords(List<String> chineseNames) async {
await _initJieba(); //
final wordFrequency = <String, int>{};
Logger().info('开始分词,共有 ${chineseNames.length} 个中文名称');
for (final name in chineseNames) {
List<SegToken> tokens = _segmenter!.process(name, SegMode.SEARCH); // 使
for (final token in tokens) {
final word = token.word.trim();
if (word.length > 1 && !_blackList.contains(word)) {
wordFrequency[word] = (wordFrequency[word] ?? 0) + 1;
}
nodes.add(node);
}
}
return nodes;
Logger().info('分词完成,共找到 ${wordFrequency.length} 个有效词语');
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'; @@ -2,6 +2,8 @@ import 'dart:math';
import 'package:flutter/material.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/models/outline_node.dart';
import 'package:win_text_editor/shared/components/tree_view.dart';
@ -17,17 +19,20 @@ class OutlineExplorer extends StatefulWidget { @@ -17,17 +19,20 @@ class OutlineExplorer extends StatefulWidget {
}
class _OutlineExplorerState extends State<OutlineExplorer> {
final ScrollController _scrollController = ScrollController(); // ScrollController
final ScrollController _scrollController = ScrollController();
final TextEditingController _searchController = TextEditingController();
String _searchQuery = '';
@override
void dispose() {
_scrollController.dispose(); // controller
_scrollController.dispose();
_searchController.dispose();
super.dispose();
}
//
double calculateTotalWidth(BuildContext context, OutlineProvider fileProvider) {
final maxDepth = _getMaxDepth(fileProvider.fileNodes);
double calculateTotalWidth(BuildContext context, OutlineProvider outlineProvider) {
final maxDepth = _getMaxDepth(outlineProvider.outlineNode);
return maxDepth * 60 + MediaQuery.of(context).size.width * 0.2;
}
@ -41,45 +46,155 @@ class _OutlineExplorerState extends State<OutlineExplorer> { @@ -41,45 +46,155 @@ class _OutlineExplorerState extends State<OutlineExplorer> {
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
Widget build(BuildContext context) {
final fileProvider = Provider.of<OutlineProvider>(context);
return Scrollbar(
controller: _scrollController, // controller
thumbVisibility: true,
child: SingleChildScrollView(
controller: _scrollController, // 使controller
scrollDirection: Axis.horizontal,
child: Container(
color: Colors.white,
final outlineProvider = Provider.of<OutlineProvider>(context);
_loadLastOpenedFolder(outlineProvider);
return Column(
children: [
//
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
width: calculateTotalWidth(context, fileProvider),
child: TreeView(
nodes: fileProvider.fileNodes,
config: const TreeViewConfig(showIcons: true, lazyLoad: true),
onNodeTap: (node) => _handleNodeTap(context, node as OutlineNode),
onNodeDoubleTap: (node) => _handleNodeDoubleTap(node as OutlineNode),
height: 30, //
child: TextField(
controller: _searchController,
style: const TextStyle(fontSize: 12), // 10
decoration: InputDecoration(
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 {
final fileProvider = Provider.of<OutlineProvider>(context, listen: false);
final outlineProvider = Provider.of<OutlineProvider>(context, listen: false);
if (node.isDirectory) {
await fileProvider.loadDirectoryContents(node);
await outlineProvider.loadDirectoryContents(node);
}
}
void _handleNodeDoubleTap(TreeNode node) {
final fileNode = node as OutlineNode;
if (fileNode.isDirectory && widget.onFolderDoubleTap != null) {
widget.onFolderDoubleTap!(fileNode.path);
} else if (!fileNode.isDirectory && widget.onFileDoubleTap != null) {
widget.onFileDoubleTap!(fileNode.path);
void _handleNodeDoubleTap(TreeNode node) {}
Future<void> _loadLastOpenedFolder(OutlineProvider outlineProvider) async {
if (outlineProvider.rootPath != null && outlineProvider.rootPath!.isNotEmpty) {
return;
}
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> { @@ -25,7 +25,7 @@ class _OutlineViewState extends State<OutlineView> {
super.initState();
_outlineProvider = OutlineProvider(); // OutlineProvider
final controllerFromManager = tabManager.getController(widget.tabId);
if (controllerFromManager != null) {
_controller = controllerFromManager;
@ -51,22 +51,22 @@ class _OutlineViewState extends State<OutlineView> { @@ -51,22 +51,22 @@ class _OutlineViewState extends State<OutlineView> {
return ChangeNotifierProvider<OutlineProvider>.value(
value: _outlineProvider, // OutlineProvider
child: Row(
children: [
const VerticalDivider(width: 1),
SizedBox(
width: 300,
child: OutlineExplorer(
onFileDoubleTap: (path) {
children: [
const VerticalDivider(width: 1),
SizedBox(
width: 300,
child: OutlineExplorer(
onFileDoubleTap: (path) {
//
},
onFolderDoubleTap: (path) {
},
onFolderDoubleTap: (path) {
//
},
},
),
),
),
const VerticalDivider(width: 1),
const Expanded(child: Center(child: Text('demo'))),
],
const VerticalDivider(width: 1),
const Expanded(child: Center(child: Text('大纲'))),
],
),
);
}

74
win_text_editor/lib/process.dart

@ -1,74 +0,0 @@ @@ -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: @@ -232,6 +232,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
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:
dependency: transitive
description:

3
win_text_editor/pubspec.yaml

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

Loading…
Cancel
Save