From a7d590941d259043a4fac579d2f15771c5a30cd0 Mon Sep 17 00:00:00 2001 From: Zhang Sheng Date: Mon, 13 Jul 2026 10:15:23 +0800 Subject: [PATCH] feat: add directory exclusion feature to search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added support for excluding specific directories from search results via new --exclude parameter. Users can now provide comma-separated paths to skip during searching. This improves search performance by avoiding unnecessary directories like /tmp or .cache folders. The implementation includes: 1. New CLI option --exclude in dfm-searcher 2. SemanticSearcher API extension for excluded paths 3. Integration with underlying search engines 4. Documentation updates with examples 5. Backward compatible with older Qt versions Log: Added --exclude option to skip specified directories during search Influence: 1. Test exclusion with various path formats (absolute, relative, with spaces) 2. Verify search performance in directories containing excluded paths 3. Check semantic search exclusion behavior 4. Test combinations with other filters (time, size) 5. Verify JSON output includes excluded paths information feat: 新增搜索目录排除功能 新增通过--exclude参数排除特定目录的功能。用户可通过逗号分隔的路径列表跳 过特定目录搜索,提高搜索性能(特别是跳过/tmp或.cache等无关目录) 实现包括: 1. dfm-searcher新增--exclude命令行选项 2. 扩展SemanticSearcher API支持排除路径 3. 底层搜索引擎集成 4. 文档更新并添加示例 5. 保持对旧版Qt的兼容性 Log: 新增--exclude选项可跳过指定目录的搜索 Influence: 1. 测试不同路径格式的排除功能(绝对路径、相对路径、含空格路径) 2. 验证包含排除路径的目录中的搜索性能 3. 检查语义搜索的排除行为 4. 测试与其他过滤器(时间、大小)的组合使用 5. 验证JSON输出是否包含排除路径信息 --- docs/dfm-searcher-guide.md | 119 +++++++++++------- .../dfm-search/dfm-search/semanticsearcher.h | 11 ++ .../dfm-search-client/cli_options.cpp | 18 +++ .../dfm-search-client/cli_options.h | 2 + src/dfm-search/dfm-search-client/main.cpp | 7 ++ .../semantic/semanticsearcher.cpp | 8 ++ .../semantic/semanticsearcher_p.h | 1 + 7 files changed, 118 insertions(+), 48 deletions(-) diff --git a/docs/dfm-searcher-guide.md b/docs/dfm-searcher-guide.md index 74c6a90d..64393baa 100644 --- a/docs/dfm-searcher-guide.md +++ b/docs/dfm-searcher-guide.md @@ -252,9 +252,31 @@ dfm-searcher --file-types=doc --file-extensions=docx,odt "report" /home/user --- -## 八、时间范围过滤 +## 八、排除目录 -### 20. 最近 N 天 +### 20. 排除指定目录 + +```bash +# 排除 /tmp 和 /var 目录 +dfm-searcher --exclude=/tmp,/var "报告" /home/user +``` + +搜索时跳过 `/tmp` 和 `/var` 目录及其子目录,不搜索其中的文件。用逗号分隔多个排除路径。 + +### 21. 排除缓存等无关目录 + +```bash +# 排除缓存和回收站目录,减少无关结果 +dfm-searcher --exclude=/home/user/.cache,/home/user/.local/share/Trash "报告" /home/user +``` + +排除缓存、临时文件等目录可以加快搜索速度、减少无关结果。该选项在搜索模式和语义模式下均可使用。 + +--- + +## 九、时间范围过滤 + +### 22. 最近 N 天 ```bash # 最近 3 天内修改的文件 @@ -275,7 +297,7 @@ dfm-searcher --time-last=2h "报告" /home/user | `M` | 月 | `--time-last=3M`(最近 3 个月) | | `y` | 年 | `--time-last=1y`(最近 1 年) | -### 21. 预设时间范围 +### 23. 预设时间范围 ```bash # 今天的文件 @@ -298,7 +320,7 @@ dfm-searcher --time-this-year "报告" /home/user `--time-today` / `--time-yesterday` / `--time-this-week` / `--time-last-week` / `--time-this-month` / `--time-last-month` / `--time-this-year` / `--time-last-year` -### 22. 自定义时间范围 +### 24. 自定义时间范围 ```bash # 按日期范围 @@ -308,7 +330,7 @@ dfm-searcher --time-range="2025-01-01,2025-12-31" "报告" /home/user dfm-searcher --time-range="2025-06-01 09:00,2025-06-30 18:00" "报告" /home/user ``` -### 23. 按创建时间 vs 修改时间 +### 25. 按创建时间 vs 修改时间 ```bash # 默认按修改时间(modify) @@ -318,7 +340,7 @@ dfm-searcher --time-last=7d "报告" /home/user dfm-searcher --time-field=birth --time-last=7d "报告" /home/user ``` -### 24. 时间 + 实时搜索组合 +### 26. 时间 + 实时搜索组合 ```bash dfm-searcher --method=realtime --time-last=7d "报告" /home/user @@ -326,9 +348,9 @@ dfm-searcher --method=realtime --time-last=7d "报告" /home/user --- -## 九、文件大小过滤 +## 十、文件大小过滤 -### 25. 按大小范围过滤 +### 27. 按大小范围过滤 ```bash # 1MB 到 100MB 之间的文件 @@ -345,7 +367,7 @@ dfm-searcher --size-min=1M --size-max=100M "视频" /home/user | `G` | GB | `--size-max=1G`(最多 1GB) | | `T` | TB | `--size-max=1T`(最多 1TB) | -### 26. 只限最小或最大 +### 28. 只限最小或最大 ```bash # 至少 10MB @@ -357,9 +379,9 @@ dfm-searcher --size-max=1K "日志" /home/user --- -## 十、语义搜索 +## 十一、语义搜索 -### 27. 用自然语言搜索 +### 29. 用自然语言搜索 ```bash dfm-searcher -s "最近3天的图片" /home/user @@ -367,7 +389,7 @@ dfm-searcher -s "最近3天的图片" /home/user 语义搜索会自动解析你的自然语言描述,提取时间、类型、关键词等维度,转化为搜索条件。 -### 28. 语义搜索 + JSON +### 30. 语义搜索 + JSON ```bash dfm-searcher -s -j "最近3天的图片" /home/user @@ -375,7 +397,7 @@ dfm-searcher -s -j "最近3天的图片" /home/user JSON 输出会包含解析出的意图(intent)信息。 -### 29. 语义搜索不要求路径 +### 31. 语义搜索不要求路径 ```bash dfm-searcher -s "包含会议纪要的文档" @@ -383,7 +405,7 @@ dfm-searcher -s "包含会议纪要的文档" 语义模式可以不指定搜索路径。 -### 30. 语义搜索示例集 +### 32. 语义搜索示例集 ```bash # 查找最近图片 @@ -404,9 +426,9 @@ dfm-searcher -s -v "最近3天的图片" /home/user --- -## 十一、输出格式 +## 十二、输出格式 -### 31. JSON 输出 +### 33. JSON 输出 ```bash dfm-searcher -j "报告" /home/user @@ -433,7 +455,7 @@ dfm-searcher -j "报告" /home/user } ``` -### 32. 详细输出(verbose) +### 34. 详细输出(verbose) ```bash dfm-searcher -v --type=content "会议" /home/user/Documents @@ -441,7 +463,7 @@ dfm-searcher -v --type=content "会议" /home/user/Documents `-v` 启用详细模式,结果中会包含文件大小、修改时间、高亮内容预览等额外信息。 -### 33. JSON + verbose 组合 +### 35. JSON + verbose 组合 ```bash dfm-searcher -j -v --type=content "会议" /home/user/Documents @@ -449,9 +471,9 @@ dfm-searcher -j -v --type=content "会议" /home/user/Documents --- -## 十二、组合搜索 +## 十三、组合搜索 -### 34. 文件名 + 文件类型 + 时间 +### 36. 文件名 + 文件类型 + 时间 ```bash dfm-searcher --file-types=doc --time-last=7d "报告" /home/user @@ -459,7 +481,7 @@ dfm-searcher --file-types=doc --time-last=7d "报告" /home/user 最近 7 天内的文档类型文件,文件名包含"报告"。 -### 35. 内容搜索 + 文件名过滤 + 大小过滤 +### 37. 内容搜索 + 文件名过滤 + 大小过滤 ```bash dfm-searcher --type=content --filename="季度" --size-min=1M "财务" /home/user @@ -467,7 +489,7 @@ dfm-searcher --type=content --filename="季度" --size-min=1M "财务" /home/use 在内容包含"财务"且文件名包含"季度"、大小至少 1MB 的文件中搜索。 -### 36. 实时搜索 + 时间 + 大小 + 类型 +### 38. 实时搜索 + 时间 + 大小 + 类型 ```bash dfm-searcher --method=realtime --time-last=30d --size-min=10M --file-types=video "监控" /home/user @@ -479,9 +501,9 @@ dfm-searcher --method=realtime --time-last=30d --size-min=10M --file-types=video `preview` 子命令用来**直接读取**指定文件的内容片段,不需要运行完整搜索。 -## 十三、基本用法 +## 十四、基本用法 -### 37. 读取文件全部内容 +### 39. 读取文件全部内容 ```bash dfm-searcher preview /home/user/notes.txt @@ -495,7 +517,7 @@ dfm-searcher preview /home/user/notes.txt 今天重点完成代码评审部分,明天开始写自动化测试… ``` -### 38. 读取文件内容(JSON 格式) +### 40. 读取文件内容(JSON 格式) ```bash dfm-searcher preview /home/user/notes.txt -j @@ -523,11 +545,11 @@ dfm-searcher preview /home/user/notes.txt -j --- -## 十四、用 --offset 和 --max-preview 控制读取范围 +## 十五、用 --offset 和 --max-preview 控制读取范围 无 keyword 时,默认输出全文。加 `--offset` 可以从指定位置开始读,加 `--max-preview` 可以限制读取长度。 -### 39. 从第 100 个字符开始读(不限长度) +### 41. 从第 100 个字符开始读(不限长度) ```bash dfm-searcher preview /home/user/notes.txt --offset=100 @@ -535,19 +557,19 @@ dfm-searcher preview /home/user/notes.txt --offset=100 从第 100 个字符开始,读到文件末尾。 -### 40. 只读 50 个字符 +### 42. 只读 50 个字符 ```bash dfm-searcher preview /home/user/notes.txt --max-preview=50 ``` -### 41. 从第 100 个字符开始,只读 50 个字符 +### 43. 从第 100 个字符开始,只读 50 个字符 ```bash dfm-searcher preview /home/user/notes.txt --offset=100 --max-preview=50 ``` -### 42. 翻页式读取 +### 44. 翻页式读取 第一次读取: @@ -569,7 +591,7 @@ dfm-searcher preview /home/user/notes.txt --offset=200 --max-preview=100 -j 以此类推,每次读 100 个字符。 -### 43. offset 超过文件长度 → 返回空 +### 45. offset 超过文件长度 → 返回空 ```bash dfm-searcher preview /home/user/notes.txt --offset=99999 -j @@ -590,9 +612,9 @@ dfm-searcher preview /home/user/notes.txt --offset=99999 -j --- -## 十五、用 keyword 搜索并预览 +## 十六、用 keyword 搜索并预览 -### 44. 搜索关键词并预览 +### 46. 搜索关键词并预览 ```bash dfm-searcher preview "bug" /home/user/notes.txt @@ -605,7 +627,7 @@ dfm-searcher preview "bug" /home/user/notes.txt 内容从关键词所在位置开始,往后最多 200 个字符。 -### 45. 搜索关键词 + JSON +### 47. 搜索关键词 + JSON ```bash dfm-searcher preview "bug" /home/user/notes.txt -j @@ -629,7 +651,7 @@ dfm-searcher preview "bug" /home/user/notes.txt -j `keywordOffset: 156` 表示 "bug" 在全文第 156 个字符处。 -### 46. 从指定位置开始搜索关键词 +### 48. 从指定位置开始搜索关键词 如果 "bug" 在文件里出现多次,用 `--offset` 跳过前面的出现: @@ -638,7 +660,7 @@ dfm-searcher preview "bug" /home/user/notes.txt -j dfm-searcher preview "bug" /home/user/notes.txt --offset=200 ``` -### 47. offset 后面没有关键词 → 返回空 +### 49. offset 后面没有关键词 → 返回空 ```bash dfm-searcher preview "bug" /home/user/notes.txt --offset=500 -j @@ -655,7 +677,7 @@ dfm-searcher preview "bug" /home/user/notes.txt --offset=500 -j } ``` -### 48. 搭配搜索结果使用 +### 50. 搭配搜索结果使用 搜索返回 `keywordOffset`,可以用它作为 preview 的 `--offset`: @@ -666,9 +688,9 @@ dfm-searcher preview "关键词" /home/user/doc.txt --offset=320 --max-preview=2 --- -## 十六、批量预览多个文件 +## 十七、批量预览多个文件 -### 49. 同时预览多个文件 +### 51. 同时预览多个文件 ```bash dfm-searcher preview "报告" doc1.txt doc2.txt doc3.txt @@ -683,7 +705,7 @@ dfm-searcher preview "报告" doc1.txt doc2.txt doc3.txt (no content) ``` -### 50. 批量预览 + JSON +### 52. 批量预览 + JSON ```bash dfm-searcher preview "报告" doc1.txt doc2.txt doc3.txt -j @@ -718,15 +740,15 @@ dfm-searcher preview "报告" doc1.txt doc2.txt doc3.txt -j --- -## 十七、搜索类型 +## 十八、搜索类型 -### 51. 自动检测(默认) +### 53. 自动检测(默认) 不给 `--type`,程序根据文件扩展名自动判断: - `.txt` `.doc` `.md` 等 → 用内容索引(content) - `.png` `.jpg` 等 → 用 OCR 索引(ocr) -### 52. 强制指定搜索类型 +### 54. 强制指定搜索类型 ```bash # 强制用 OCR 索引 @@ -736,7 +758,7 @@ dfm-searcher preview --type=ocr "截图" photo.png dfm-searcher preview --type=content "会议" doc.txt ``` -### 53. 预览 OCR 图片中的文字 +### 55. 预览 OCR 图片中的文字 ```bash dfm-searcher preview "登录" screenshot.png -j @@ -759,21 +781,21 @@ dfm-searcher preview "登录" screenshot.png -j --- -## 十八、无关键词,纯读取内容 +## 十九、无关键词,纯读取内容 -### 54. 读取文件前 200 个字符 +### 56. 读取文件前 200 个字符 ```bash dfm-searcher preview /home/user/doc.txt ``` -### 55. 从第 500 个字符读取 100 个字符 +### 57. 从第 500 个字符读取 100 个字符 ```bash dfm-searcher preview /home/user/doc.txt --offset=500 --max-preview=100 ``` -### 56. 无关键词 + JSON +### 58. 无关键词 + JSON ```bash dfm-searcher preview /home/user/doc.txt --offset=500 --max-preview=100 -j @@ -815,6 +837,7 @@ dfm-searcher preview /home/user/doc.txt --offset=500 --max-preview=100 -j | `--pinyin-acronym` | 关 | 启用拼音首字母搜索 | | `--file-types=` | 无 | 按文件类型过滤 | | `--file-extensions=` | 无 | 按扩展名过滤 | +| `--exclude=` | 无 | 排除指定目录,逗号分隔 | | `--max-results=` | `0`(不限) | 最大结果数 | | `--max-preview=` | `200` | 内容预览长度 | | `--filename=` | 无 | 在内容/OCR 搜索中按文件名过滤 | diff --git a/include/dfm-search/dfm-search/semanticsearcher.h b/include/dfm-search/dfm-search/semanticsearcher.h index 9672113e..2ec6e9f2 100644 --- a/include/dfm-search/dfm-search/semanticsearcher.h +++ b/include/dfm-search/dfm-search/semanticsearcher.h @@ -192,6 +192,17 @@ class SemanticSearcher : public QObject */ int maxResults() const; + /** + * @brief Set directories to exclude from search + * + * When non-empty, these paths are forwarded to each sub-engine's + * SearchOptions so that files under the excluded directories are + * skipped during search. Must be called before search(). + * + * @param paths List of directory paths to exclude + */ + void setSearchExcludedPaths(const QStringList &paths); + Q_SIGNALS: /** * @brief Emitted after the natural language input is parsed into an intent diff --git a/src/dfm-search/dfm-search-client/cli_options.cpp b/src/dfm-search/dfm-search-client/cli_options.cpp index 7a3aa33b..0a415660 100644 --- a/src/dfm-search/dfm-search-client/cli_options.cpp +++ b/src/dfm-search/dfm-search-client/cli_options.cpp @@ -25,6 +25,7 @@ CliOptions::CliOptions() m_pinyinAcronymOption(QStringList() << "pinyin-acronym", "Enable pinyin acronym search (for filename search)"), m_fileTypesOption(QStringList() << "file-types", "Filter by file types, comma separated", "types"), m_fileExtensionsOption(QStringList() << "file-extensions", "Filter by file extensions, comma separated", "extensions"), + m_excludeOption(QStringList() << "exclude", "Exclude directories from search, comma separated", "paths"), m_maxResultsOption(QStringList() << "max-results", "Maximum number of results (0 for unlimited)", "number", "0"), m_maxPreviewOption(QStringList() << "max-preview", "Max content preview length", "length", "200"), m_offsetOption(QStringList() << "offset", "Content offset: start reading from the n-th character (preview only)", "n", "0"), @@ -73,6 +74,7 @@ void CliOptions::setupOptions() m_parser.addOption(m_pinyinAcronymOption); m_parser.addOption(m_fileTypesOption); m_parser.addOption(m_fileExtensionsOption); + m_parser.addOption(m_excludeOption); m_parser.addOption(m_maxResultsOption); m_parser.addOption(m_maxPreviewOption); m_parser.addOption(m_offsetOption); @@ -129,6 +131,7 @@ void CliOptions::printHelp() const std::cout << " --pinyin-acronym Enable pinyin acronym search (for filename search)" << std::endl; std::cout << " --file-types= Filter by file types, comma separated" << std::endl; std::cout << " --file-extensions= Filter by file extensions, comma separated" << std::endl; + std::cout << " --exclude= Exclude directories from search, comma separated" << std::endl; std::cout << " --max-results= Maximum number of results (0 for unlimited)" << std::endl; std::cout << " --max-preview= Max content preview length (for content/ocr search)" << std::endl; std::cout << " --offset= Content offset: start reading from the n-th character (preview only, default 0)" << std::endl; @@ -345,6 +348,13 @@ bool CliOptions::parse(QCoreApplication &app, SearchCliConfig &config) config.maxResults = maxResults; } } + if (m_parser.isSet(m_excludeOption)) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + config.excludedPaths = m_parser.value(m_excludeOption).split(',', Qt::SkipEmptyParts); +#else + config.excludedPaths = m_parser.value(m_excludeOption).split(',', QString::SkipEmptyParts); +#endif + } return true; } @@ -403,6 +413,14 @@ bool CliOptions::parse(QCoreApplication &app, SearchCliConfig &config) #endif } + if (m_parser.isSet(m_excludeOption)) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + config.excludedPaths = m_parser.value(m_excludeOption).split(',', Qt::SkipEmptyParts); +#else + config.excludedPaths = m_parser.value(m_excludeOption).split(',', QString::SkipEmptyParts); +#endif + } + // 解析文件名搜索选项(仅对 content/ocr 搜索有效) if (m_parser.isSet(m_filenameOption)) { config.filenameKeyword = m_parser.value(m_filenameOption); diff --git a/src/dfm-search/dfm-search-client/cli_options.h b/src/dfm-search/dfm-search-client/cli_options.h index 206a1e38..f783b45f 100644 --- a/src/dfm-search/dfm-search-client/cli_options.h +++ b/src/dfm-search/dfm-search-client/cli_options.h @@ -45,6 +45,7 @@ struct SearchCliConfig // 过滤选项 QStringList fileTypes; QStringList fileExtensions; + QStringList excludedPaths; // 排除的搜索路径 int maxResults = 0; // 0 表示不限制 int maxPreviewLength = 200; int offset = 0; // 内容偏移值,preview 子命令使用 @@ -107,6 +108,7 @@ class CliOptions QCommandLineOption m_pinyinAcronymOption; QCommandLineOption m_fileTypesOption; QCommandLineOption m_fileExtensionsOption; + QCommandLineOption m_excludeOption; QCommandLineOption m_maxResultsOption; QCommandLineOption m_maxPreviewOption; QCommandLineOption m_offsetOption; diff --git a/src/dfm-search/dfm-search-client/main.cpp b/src/dfm-search/dfm-search-client/main.cpp index 66bbec25..e50778c6 100644 --- a/src/dfm-search/dfm-search-client/main.cpp +++ b/src/dfm-search/dfm-search-client/main.cpp @@ -43,6 +43,10 @@ static void configureSearchOptions(SearchOptions &options, const SearchCliConfig options.setMaxResults(config.maxResults); options.setDetailedResultsEnabled(config.verbose); // 使用 verbose 选项控制详细输出 + if (!config.excludedPaths.isEmpty()) { + options.setSearchExcludedPaths(config.excludedPaths); + } + if (config.searchMethod == SearchMethod::Realtime) { options.setResultFoundEnabled(true); } @@ -259,6 +263,9 @@ int main(int argc, char *argv[]) if (config.maxResults > 0) { semanticSearcher->setMaxResults(config.maxResults); } + if (!config.excludedPaths.isEmpty()) { + semanticSearcher->setSearchExcludedPaths(config.excludedPaths); + } OutputFormatter *formatter = createOutputFormatter(config, &app); diff --git a/src/dfm-search/dfm-search-lib/semantic/semanticsearcher.cpp b/src/dfm-search/dfm-search-lib/semantic/semanticsearcher.cpp index 75e9c54c..df511a5e 100644 --- a/src/dfm-search/dfm-search-lib/semantic/semanticsearcher.cpp +++ b/src/dfm-search/dfm-search-lib/semantic/semanticsearcher.cpp @@ -232,6 +232,9 @@ void SemanticSearcherData::doSearch(const QString &naturalLanguage, const QStrin if (maxResults > 0) { opts.setMaxResults(maxResults); } + if (!excludedPaths.isEmpty()) { + opts.setSearchExcludedPaths(excludedPaths); + } }; // Step 8: Launch engines based on plan @@ -415,6 +418,11 @@ int SemanticSearcher::maxResults() const return d_ptr->maxResults; } +void SemanticSearcher::setSearchExcludedPaths(const QStringList &paths) +{ + d_ptr->excludedPaths = paths; +} + SearchResultExpected SemanticSearcher::searchSync(const QString &naturalLanguage) { return searchSync(naturalLanguage, {}); diff --git a/src/dfm-search/dfm-search-lib/semantic/semanticsearcher_p.h b/src/dfm-search/dfm-search-lib/semantic/semanticsearcher_p.h index 275bf2be..facdd74b 100644 --- a/src/dfm-search/dfm-search-lib/semantic/semanticsearcher_p.h +++ b/src/dfm-search/dfm-search-lib/semantic/semanticsearcher_p.h @@ -88,6 +88,7 @@ class SemanticSearcherData // Options forwarded from caller bool detailedResultsEnabled = false; int maxResults = 0; // 0 = unlimited + QStringList excludedPaths; // directories to exclude from search }; DFM_SEARCH_END_NS