11 changed files with 626 additions and 94 deletions
@ -0,0 +1,25 @@ |
|||||||
|
{ |
||||||
|
// 使用 IntelliSense 了解相关属性。 |
||||||
|
// 悬停以查看现有属性的描述。 |
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 |
||||||
|
"version": "0.2.0", |
||||||
|
"configurations": [ |
||||||
|
{ |
||||||
|
"name": "win_text_editor", |
||||||
|
"request": "launch", |
||||||
|
"type": "dart" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "win_text_editor (profile mode)", |
||||||
|
"request": "launch", |
||||||
|
"type": "dart", |
||||||
|
"flutterMode": "profile" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "win_text_editor (release mode)", |
||||||
|
"request": "launch", |
||||||
|
"type": "dart", |
||||||
|
"flutterMode": "release" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,171 @@ |
|||||||
|
// lib/app/modules/content_search/content_search_service.dart |
||||||
|
|
||||||
|
import 'dart:io'; |
||||||
|
import 'package:path/path.dart' as path; |
||||||
|
|
||||||
|
class ContentSearchService { |
||||||
|
static Future<List<SearchResult>> performLocateSearch({ |
||||||
|
required String directory, |
||||||
|
required String query, |
||||||
|
required String fileType, |
||||||
|
required bool caseSensitive, |
||||||
|
required bool wholeWord, |
||||||
|
required bool useRegex, |
||||||
|
}) async { |
||||||
|
final results = <SearchResult>[]; |
||||||
|
final dir = Directory(directory); |
||||||
|
final queries = _splitQuery(query); // 分割查询字符串 |
||||||
|
|
||||||
|
for (final q in queries) { |
||||||
|
final pattern = _buildSearchPattern( |
||||||
|
query: q, |
||||||
|
caseSensitive: caseSensitive, |
||||||
|
wholeWord: wholeWord, |
||||||
|
useRegex: useRegex, |
||||||
|
); |
||||||
|
|
||||||
|
await for (final entity in dir.list(recursive: true)) { |
||||||
|
if (entity is File && _matchesFileType(entity.path, fileType)) { |
||||||
|
await _searchInFile(entity, pattern, results, q); // 传递当前查询项 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return results; |
||||||
|
} |
||||||
|
|
||||||
|
/// 计数搜索(返回每个关键词的匹配数) |
||||||
|
static Future<Map<String, int>> performCountSearch({ |
||||||
|
required String directory, |
||||||
|
required String query, |
||||||
|
required String fileType, |
||||||
|
required bool caseSensitive, |
||||||
|
required bool wholeWord, |
||||||
|
required bool useRegex, |
||||||
|
}) async { |
||||||
|
final counts = <String, int>{}; |
||||||
|
final dir = Directory(directory); |
||||||
|
final queries = _splitQuery(query); // 分割查询字符串 |
||||||
|
|
||||||
|
for (final q in queries) { |
||||||
|
final pattern = _buildSearchPattern( |
||||||
|
query: q, |
||||||
|
caseSensitive: caseSensitive, |
||||||
|
wholeWord: wholeWord, |
||||||
|
useRegex: useRegex, |
||||||
|
); |
||||||
|
|
||||||
|
await for (final entity in dir.list(recursive: true)) { |
||||||
|
if (entity is File && _matchesFileType(entity.path, fileType)) { |
||||||
|
await _countInFile(entity, pattern, counts, q); // 传递当前查询项 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return counts; |
||||||
|
} |
||||||
|
|
||||||
|
/// 分割查询字符串(按半角逗号分隔,并去除空格) |
||||||
|
static List<String> _splitQuery(String query) { |
||||||
|
return query.split(',').map((q) => q.trim()).where((q) => q.isNotEmpty).toList(); |
||||||
|
} |
||||||
|
|
||||||
|
/// 构建正则表达式(原逻辑不变) |
||||||
|
static RegExp _buildSearchPattern({ |
||||||
|
required String query, |
||||||
|
required bool caseSensitive, |
||||||
|
required bool wholeWord, |
||||||
|
required bool useRegex, |
||||||
|
}) { |
||||||
|
String pattern; |
||||||
|
if (useRegex) { |
||||||
|
pattern = query; |
||||||
|
} else { |
||||||
|
pattern = RegExp.escape(query); |
||||||
|
if (wholeWord) { |
||||||
|
pattern = '\\b$pattern\\b'; |
||||||
|
} |
||||||
|
} |
||||||
|
return RegExp(pattern, caseSensitive: caseSensitive, multiLine: true); |
||||||
|
} |
||||||
|
|
||||||
|
/// 检查文件类型(原逻辑不变) |
||||||
|
static bool _matchesFileType(String filePath, String fileType) { |
||||||
|
if (fileType == '*.*') return true; |
||||||
|
final ext = path.extension(filePath).toLowerCase(); |
||||||
|
return ext == fileType.toLowerCase(); |
||||||
|
} |
||||||
|
|
||||||
|
/// 在文件中搜索匹配项(增加 queryTerm 参数) |
||||||
|
static Future<void> _searchInFile( |
||||||
|
File file, |
||||||
|
RegExp pattern, |
||||||
|
List<SearchResult> results, |
||||||
|
String queryTerm, // 当前查询项 |
||||||
|
) async { |
||||||
|
try { |
||||||
|
final lines = await file.readAsLines(); |
||||||
|
for (int i = 0; i < lines.length; i++) { |
||||||
|
final line = lines[i]; |
||||||
|
final matches = pattern.allMatches(line); |
||||||
|
|
||||||
|
if (matches.isNotEmpty) { |
||||||
|
results.add( |
||||||
|
SearchResult( |
||||||
|
filePath: file.path, |
||||||
|
lineNumber: i + 1, |
||||||
|
lineContent: line, |
||||||
|
matches: matches.map((m) => MatchResult(start: m.start, end: m.end)).toList(), |
||||||
|
queryTerm: queryTerm, // 记录匹配的查询项 |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
print('Error reading file ${file.path}: $e'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// 在文件中计数匹配项(增加 queryTerm 参数) |
||||||
|
static Future<void> _countInFile( |
||||||
|
File file, |
||||||
|
RegExp pattern, |
||||||
|
Map<String, int> counts, |
||||||
|
String queryTerm, // 当前查询项 |
||||||
|
) async { |
||||||
|
try { |
||||||
|
final content = await file.readAsString(); |
||||||
|
final matches = pattern.allMatches(content); |
||||||
|
|
||||||
|
if (matches.isNotEmpty) { |
||||||
|
counts[queryTerm] = (counts[queryTerm] ?? 0) + matches.length; |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
print('Error reading file ${file.path}: $e'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// 搜索结果类(新增 queryTerm 字段) |
||||||
|
class SearchResult { |
||||||
|
final String filePath; |
||||||
|
final int lineNumber; |
||||||
|
final String lineContent; |
||||||
|
final List<MatchResult> matches; |
||||||
|
final String queryTerm; // 记录匹配的查询项 |
||||||
|
|
||||||
|
SearchResult({ |
||||||
|
required this.filePath, |
||||||
|
required this.lineNumber, |
||||||
|
required this.lineContent, |
||||||
|
required this.matches, |
||||||
|
required this.queryTerm, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
class MatchResult { |
||||||
|
final int start; |
||||||
|
final int end; |
||||||
|
|
||||||
|
const MatchResult({required this.start, required this.end}); |
||||||
|
} |
Loading…
Reference in new issue