Browse Source

增加进度条和停止搜索开关

master
hejl 2 months ago
parent
commit
73e6077d4b
  1. BIN
      documents/~$ UFT模块迁移方案.docx
  2. 40
      win_text_editor/lib/modules/content_search/controllers/content_search_controller.dart
  3. 104
      win_text_editor/lib/modules/content_search/services/content_search_service.dart
  4. 24
      win_text_editor/lib/modules/content_search/widgets/content_search_view.dart
  5. 30
      win_text_editor/lib/modules/content_search/widgets/search_settings.dart

BIN
documents/~$ UFT模块迁移方案.docx

Binary file not shown.

40
win_text_editor/lib/modules/content_search/controllers/content_search_controller.dart

@ -4,6 +4,7 @@ import 'dart:async'; @@ -4,6 +4,7 @@ import 'dart:async';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:win_text_editor/framework/controllers/logger.dart';
import 'package:win_text_editor/modules/content_search/models/search_mode.dart';
import 'package:win_text_editor/modules/content_search/models/search_result.dart';
@ -20,8 +21,10 @@ class ContentSearchController extends BaseContentController { @@ -20,8 +21,10 @@ class ContentSearchController extends BaseContentController {
bool _customRule = false;
SearchMode _searchMode = SearchMode.locate;
final List<SearchResult> _results = [];
List<SearchResult> _allResults = [];
final List<SearchResult> _allResults = [];
bool _includeZeroCounts = false;
bool _isSearching = false;
bool _shouldStop = false;
// Getters
String get searchQuery => _searchQuery;
@ -35,6 +38,24 @@ class ContentSearchController extends BaseContentController { @@ -35,6 +38,24 @@ class ContentSearchController extends BaseContentController {
List<SearchResult> get results => _results;
bool get includeZeroCounts => _includeZeroCounts;
double _progress = 0;
double get progress => _progress;
bool get isSearching => _isSearching;
//
void _updateProgress(double value) {
_progress = value.clamp(0.0, 100.0);
notifyListeners();
}
//
void stopSearch() {
_shouldStop = true;
_isSearching = false;
notifyListeners();
}
set customRule(bool value) {
_customRule = value;
notifyListeners();
@ -111,8 +132,12 @@ class ContentSearchController extends BaseContentController { @@ -111,8 +132,12 @@ class ContentSearchController extends BaseContentController {
}
Future<void> startSearch() async {
_shouldStop = false;
_isSearching = true;
_progress = 0;
_results.clear();
_allResults.clear();
notifyListeners();
//
if (searchQuery.isEmpty) {
@ -127,6 +152,7 @@ class ContentSearchController extends BaseContentController { @@ -127,6 +152,7 @@ class ContentSearchController extends BaseContentController {
return;
}
try {
if (customRule) {
final validationResult = ContentSearchService.validateJsRule(searchQuery);
if (validationResult.isError) {
@ -140,10 +166,11 @@ class ContentSearchController extends BaseContentController { @@ -140,10 +166,11 @@ class ContentSearchController extends BaseContentController {
fileType: fileType,
jsFunction: searchQuery,
searchMode: searchMode,
onProgress: _updateProgress,
shouldStop: () => _shouldStop,
),
);
} else {
try {
if (searchMode == SearchMode.locate) {
_allResults.addAll(
await ContentSearchService.performLocateSearch(
@ -153,6 +180,8 @@ class ContentSearchController extends BaseContentController { @@ -153,6 +180,8 @@ class ContentSearchController extends BaseContentController {
caseSensitive: caseSensitive,
wholeWord: wholeWord,
useRegex: useRegex,
onProgress: _updateProgress,
shouldStop: () => _shouldStop,
),
);
} else {
@ -163,6 +192,8 @@ class ContentSearchController extends BaseContentController { @@ -163,6 +192,8 @@ class ContentSearchController extends BaseContentController {
caseSensitive: caseSensitive,
wholeWord: wholeWord,
useRegex: useRegex,
onProgress: _updateProgress,
shouldStop: () => _shouldStop,
);
counts.forEach((keyword, count) {
@ -177,14 +208,17 @@ class ContentSearchController extends BaseContentController { @@ -177,14 +208,17 @@ class ContentSearchController extends BaseContentController {
);
});
}
}
} catch (e) {
Logger().error("搜索出错: $e");
} finally {
_isSearching = false;
_progress = 100;
_shouldStop = false;
_filterResults();
notifyListeners();
}
}
}
Future<void> pickDirectory() async {
final dir = await FilePicker.platform.getDirectoryPath();

104
win_text_editor/lib/modules/content_search/services/content_search_service.dart

@ -9,6 +9,8 @@ import 'package:win_text_editor/modules/content_search/models/match_result.dart' @@ -9,6 +9,8 @@ import 'package:win_text_editor/modules/content_search/models/match_result.dart'
import 'package:win_text_editor/modules/content_search/models/search_mode.dart';
import 'package:win_text_editor/modules/content_search/models/search_result.dart';
typedef ProgressCallback = void Function(double progress);
class ContentSearchService {
///
static Future<List<SearchResult>> performLocateSearch({
@ -18,29 +20,57 @@ class ContentSearchService { @@ -18,29 +20,57 @@ class ContentSearchService {
required bool caseSensitive,
required bool wholeWord,
required bool useRegex,
ProgressCallback? onProgress,
bool Function()? shouldStop,
}) async {
final results = <SearchResult>[];
final dir = Directory(directory);
final queries = _splitQuery(query); //
int speed = 0;
final queries = _splitQuery(query);
//
int totalFiles = 0;
await for (final entity in dir.list(recursive: true)) {
if (shouldStop?.call() == true) return results;
if (entity is File && _matchesFileType(entity.path, fileType)) {
totalFiles++;
}
}
onProgress?.call(1);
int words = 0;
int processedFiles = 0;
int oldProgress = 1;
for (final q in queries) {
if (shouldStop?.call() == true) return results;
final pattern = _buildSearchPattern(
query: q,
caseSensitive: caseSensitive,
wholeWord: wholeWord,
useRegex: useRegex,
);
Logger().info("搜索词 $q 开始[${++speed}/${queries.length}]");
int fileCount = 0;
Logger().info("搜索词 $q 开始[${++words}/${queries.length}]");
await for (final entity in dir.list(recursive: true)) {
if (shouldStop?.call() == true) return results;
if (entity is File && _matchesFileType(entity.path, fileType)) {
fileCount++;
await _searchInFile(entity, pattern, results, q); //
processedFiles++;
final progress = (processedFiles / (totalFiles * queries.length)) * 99 + 1;
if (progress - oldProgress >= 1) {
oldProgress = progress.floor();
onProgress?.call(progress);
}
await _searchInFile(entity, pattern, results, q);
}
Logger().info("搜索词 $q 结束[$speed/${queries.length}],搜索文件 $fileCount 个,");
}
Logger().info("搜索词 $q 结束[$words/${queries.length}],搜索文件 $totalFiles 个,");
}
onProgress?.call(100);
return results;
}
@ -52,12 +82,29 @@ class ContentSearchService { @@ -52,12 +82,29 @@ class ContentSearchService {
required bool caseSensitive,
required bool wholeWord,
required bool useRegex,
ProgressCallback? onProgress,
bool Function()? shouldStop,
}) async {
final counts = <String, int>{};
final dir = Directory(directory);
final queries = _splitQuery(query); //
int speed = 0;
int totalFiles = 0;
await for (final entity in dir.list(recursive: true)) {
if (shouldStop?.call() == true) return counts;
if (entity is File && _matchesFileType(entity.path, fileType)) {
totalFiles++;
}
}
onProgress?.call(1);
int words = 0;
int processedFiles = 0;
int oldProgress = 1;
for (final q in queries) {
if (shouldStop?.call() == true) return counts;
final pattern = _buildSearchPattern(
query: q,
caseSensitive: caseSensitive,
@ -65,17 +112,25 @@ class ContentSearchService { @@ -65,17 +112,25 @@ class ContentSearchService {
useRegex: useRegex,
);
Logger().info("搜索词 $q 开始[${++speed}/${queries.length}]");
Logger().info("搜索词 $q 开始[${++words}/${queries.length}]");
counts[q] = 0;
int fileCount = 0;
await for (final entity in dir.list(recursive: true)) {
if (shouldStop?.call() == true) return counts;
if (entity is File && _matchesFileType(entity.path, fileType)) {
fileCount++;
processedFiles++;
final progress = (processedFiles / (totalFiles * queries.length)) * 99 + 1;
if (progress - oldProgress >= 1) {
oldProgress = progress.floor();
onProgress?.call(progress);
}
await _countInFile(entity, pattern, counts, q); //
}
}
Logger().info("搜索词 $q 结束[$speed/${queries.length}],搜索文件 $fileCount 个,");
Logger().info("搜索词 $q 结束[$words/${queries.length}],搜索文件 $totalFiles 个,");
}
return counts;
@ -87,6 +142,8 @@ class ContentSearchService { @@ -87,6 +142,8 @@ class ContentSearchService {
required String fileType,
required String jsFunction,
required SearchMode searchMode,
ProgressCallback? onProgress,
bool Function()? shouldStop,
}) async {
final results = <SearchResult>[];
int count = 0;
@ -99,11 +156,26 @@ class ContentSearchService { @@ -99,11 +156,26 @@ class ContentSearchService {
jsRuntime.evaluate(jsCode);
int totalFiles = 0;
await for (final entity in dir.list(recursive: true)) {
if (shouldStop?.call() == true) return results;
if (entity is File && _matchesFileType(entity.path, fileType)) {
totalFiles++;
}
}
onProgress?.call(1);
int processedFiles = 0;
int calledProcessedFiles = 1;
await for (final entity in dir.list(recursive: true)) {
if (shouldStop?.call() == true) return results;
if (entity is File && _matchesFileType(entity.path, fileType)) {
try {
final lines = await entity.readAsLines();
for (int i = 0; i < lines.length; i++) {
if (shouldStop?.call() == true) return results;
final line = lines[i].trim();
if (line.length < 3) continue; //
@ -131,6 +203,13 @@ class ContentSearchService { @@ -131,6 +203,13 @@ class ContentSearchService {
} catch (e) {
Logger().error('Error in file ${entity.path}: $e');
}
processedFiles++;
final progress = (processedFiles / totalFiles) * 99 + 1;
if (processedFiles - calledProcessedFiles >= 1) {
calledProcessedFiles = processedFiles;
onProgress?.call(progress);
}
}
}
@ -147,6 +226,7 @@ class ContentSearchService { @@ -147,6 +226,7 @@ class ContentSearchService {
);
}
} finally {
onProgress?.call(100);
jsRuntime.dispose();
}

24
win_text_editor/lib/modules/content_search/widgets/content_search_view.dart

@ -34,13 +34,27 @@ class ContentSearchViewState extends State<ContentSearchView> { @@ -34,13 +34,27 @@ class ContentSearchViewState extends State<ContentSearchView> {
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: _controller,
child: const Padding(
padding: EdgeInsets.all(4.0),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
children: [
DirectorySettings(), // controller
SearchSettings(),
Expanded(child: ResultsView()),
const DirectorySettings(), // controller
const SearchSettings(),
Consumer<ContentSearchController>(
builder: (context, controller, _) {
return Column(
children: [
LinearProgressIndicator(
value: controller.progress / 100,
backgroundColor: Colors.grey[200],
valueColor: const AlwaysStoppedAnimation<Color>(Colors.green),
minHeight: 8,
),
],
);
},
),
const Expanded(child: ResultsView()),
],
),
),

30
win_text_editor/lib/modules/content_search/widgets/search_settings.dart

@ -20,7 +20,7 @@ class SearchSettings extends StatelessWidget { @@ -20,7 +20,7 @@ class SearchSettings extends StatelessWidget {
// ()
SizedBox(
width: MediaQuery.of(context).size.width * 0.5,
height: 300,
height: 360,
child: TextEditor(
tabId: 'search_content_${controller.hashCode}',
title: '搜索内容[列表以半角逗号分隔]',
@ -34,7 +34,7 @@ class SearchSettings extends StatelessWidget { @@ -34,7 +34,7 @@ class SearchSettings extends StatelessWidget {
//
Expanded(
child: Container(
height: 300,
height: 360,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(4),
@ -212,16 +212,32 @@ class SearchSettings extends StatelessWidget { @@ -212,16 +212,32 @@ class SearchSettings extends StatelessWidget {
),
),
//
Align(
alignment: Alignment.bottomCenter,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.search, size: 20),
label: const Text('开始搜索'),
onPressed: () {
controller.startSearch();
},
onPressed:
controller.isSearching ? null : () => controller.startSearch(),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.stop, size: 20),
label: const Text('停止'),
onPressed:
controller.isSearching ? () => controller.stopSearch() : null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
),
),
],
),
],
),
),

Loading…
Cancel
Save