diff --git a/autotests/dfm-search-tests/tst_content_retriever.cpp b/autotests/dfm-search-tests/tst_content_retriever.cpp index 638c55f..480821c 100644 --- a/autotests/dfm-search-tests/tst_content_retriever.cpp +++ b/autotests/dfm-search-tests/tst_content_retriever.cpp @@ -97,6 +97,7 @@ private Q_SLOTS: void fetchPreview_withKeyword(); void fetchPreview_keywordNotFound(); void fetchPreview_offsetBeyondContent(); + void fetchPreview_unlimitedNoKeyword(); void previewSnippet_basic(); }; @@ -329,6 +330,47 @@ void tst_ContentRetriever::fetchPreview_offsetBeyondContent() QCOMPARE(result.keywordOffset(), -1); } +void tst_ContentRetriever::fetchPreview_unlimitedNoKeyword() +{ + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + const QString contentIndexDir = tempDir.path() + "/content-index"; + createIndex(contentIndexDir, SearchType::Content); + + ContentRetriever retriever; + retriever.setIndexDirectory(SearchType::Content, contentIndexDir); + + // No keyword + maxLength=0 (unlimited) → return all content from offset + // Content of doc-a.txt is "hello world from content index" + { + PreviewOptions options; + options.setKeyword(QString()); + options.setOffset(0); + options.setMaxLength(0); + + const PreviewResult result = retriever.fetchPreview("/tmp/doc-a.txt", + SearchType::Content, + options); + QCOMPARE(result.content(), QString("hello world from content index")); + QCOMPARE(result.keywordOffset(), -1); + } + + // With offset, unlimited still returns all remaining content + { + PreviewOptions options; + options.setKeyword(QString()); + options.setOffset(6); // skip "hello " + options.setMaxLength(0); + + const PreviewResult result = retriever.fetchPreview("/tmp/doc-a.txt", + SearchType::Content, + options); + QCOMPARE(result.content(), QString("world from content index")); + QCOMPARE(result.keywordOffset(), -1); + } +} + void tst_ContentRetriever::previewSnippet_basic() { using DFMSEARCH::ContentHighlighter::previewSnippet; @@ -353,6 +395,16 @@ void tst_ContentRetriever::previewSnippet_basic() // Negative offset → empty QVERIFY(previewSnippet(content, -1, 10, QString(), &kwOff).isEmpty()); + + // maxLength <= 0 means unlimited — returns all content from offset + QCOMPARE(previewSnippet(content, 6, 0, QString(), &kwOff), QString("world from content index")); + QCOMPARE(kwOff, -1); + QCOMPARE(previewSnippet(content, 0, 0, QString(), &kwOff), content); + QCOMPARE(kwOff, -1); + + // With keyword + maxLength=0 (unlimited) — returns from keyword match to end + QCOMPARE(previewSnippet(content, 0, 0, "world", &kwOff), QString("world from content index")); + QCOMPARE(kwOff, 6); } QObject *create_tst_ContentRetriever() diff --git a/docs/dfm-searcher-guide.md b/docs/dfm-searcher-guide.md index 196fe21..74c6a90 100644 --- a/docs/dfm-searcher-guide.md +++ b/docs/dfm-searcher-guide.md @@ -481,17 +481,18 @@ dfm-searcher --method=realtime --time-last=30d --size-min=10M --file-types=video ## 十三、基本用法 -### 37. 读取文件开头内容 +### 37. 读取文件全部内容 ```bash dfm-searcher preview /home/user/notes.txt ``` -默认读取前 200 个字符。 +无 keyword 且未设 `--max-preview` 时,输出文档**全部内容**。 ``` /home/user/notes.txt 2026年7月10日 工作计划:1.完成需求文档 2.代码评审 3.修复bug + 今天重点完成代码评审部分,明天开始写自动化测试… ``` ### 38. 读取文件内容(JSON 格式) @@ -506,28 +507,34 @@ dfm-searcher preview /home/user/notes.txt -j "searchType": "semantic", "keyword": "", "offset": 0, - "maxLength": 200, + "maxLength": 0, "totalResults": 1, "results": [ { "path": "/home/user/notes.txt", - "content": "2026年7月10日 工作计划:1.完成需求文档 2.代码评审 3.修复bug", + "content": "2026年7月10日 工作计划:1.完成需求文档 2.代码评审 3.修复bug\n今天重点完成代码评审部分,明天开始写自动化测试…", "keywordOffset": -1 } ] } ``` +> `maxLength: 0` 表示无限制,返回全文。 + --- ## 十四、用 --offset 和 --max-preview 控制读取范围 -### 39. 从第 100 个字符开始读 +无 keyword 时,默认输出全文。加 `--offset` 可以从指定位置开始读,加 `--max-preview` 可以限制读取长度。 + +### 39. 从第 100 个字符开始读(不限长度) ```bash dfm-searcher preview /home/user/notes.txt --offset=100 ``` +从第 100 个字符开始,读到文件末尾。 + ### 40. 只读 50 个字符 ```bash @@ -828,7 +835,7 @@ dfm-searcher preview /home/user/doc.txt --offset=500 --max-preview=100 -j | 选项 | 默认值 | 说明 | |------|--------|------| | `--offset=` | `0` | 从文档第几个字符开始阅读/搜索 | -| `--max-preview=` | `200` | 本次最多读取的字符数 | +| `--max-preview=` | 无限制(无keyword) / `200`(有keyword) | 本次最多读取的字符数。设为 0 表示无限制 | | `--type=` | 自动 | 强制指定搜索类型 | | `-j` / `--json` | 关 | JSON 输出 | diff --git a/src/dfm-search/dfm-search-client/cli_options.cpp b/src/dfm-search/dfm-search-client/cli_options.cpp index 45fba88..f71bb02 100644 --- a/src/dfm-search/dfm-search-client/cli_options.cpp +++ b/src/dfm-search/dfm-search-client/cli_options.cpp @@ -189,7 +189,7 @@ void CliOptions::printHelp() const std::cout << " dfm-searcher preview [] [path2 ...] [--type=] [--offset=] [--max-preview=] [-j]" << std::endl; std::cout << " Fetch content snippets for specific files without running a full search." << std::endl; std::cout << " With keyword: returns snippet from the keyword match position (search starts at --offset)." << std::endl; - std::cout << " Without keyword: returns exact content starting from --offset, up to --max-preview characters." << std::endl; + std::cout << " Without keyword: returns exact content starting from --offset (full content if --max-preview is omitted)." << std::endl; std::cout << " Search type is auto-detected by file extension; use --type to force a specific index." << std::endl; std::cout << std::endl; std::cout << " # Fetch highlighted snippet for a single file" << std::endl; @@ -281,6 +281,7 @@ bool CliOptions::parse(QCoreApplication &app, SearchCliConfig &config) if (ok && previewLength > 0) { config.maxPreviewLength = previewLength; } + config.maxPreviewSet = true; } if (m_parser.isSet(m_offsetOption)) { bool ok; diff --git a/src/dfm-search/dfm-search-client/cli_options.h b/src/dfm-search/dfm-search-client/cli_options.h index 39f5f1f..206a1e3 100644 --- a/src/dfm-search/dfm-search-client/cli_options.h +++ b/src/dfm-search/dfm-search-client/cli_options.h @@ -48,6 +48,7 @@ struct SearchCliConfig int maxResults = 0; // 0 表示不限制 int maxPreviewLength = 200; int offset = 0; // 内容偏移值,preview 子命令使用 + bool maxPreviewSet = false; // --max-preview 是否被显式设置(preview 子命令使用) // 文件名搜索选项 QString filenameKeyword; diff --git a/src/dfm-search/dfm-search-client/main.cpp b/src/dfm-search/dfm-search-client/main.cpp index 22069de..65eaf30 100644 --- a/src/dfm-search/dfm-search-client/main.cpp +++ b/src/dfm-search/dfm-search-client/main.cpp @@ -188,7 +188,13 @@ int main(int argc, char *argv[]) DFMSEARCH::ContentRetriever retriever; DFMSEARCH::PreviewOptions previewOptions; previewOptions.setOffset(config.offset); - previewOptions.setMaxLength(config.maxPreviewLength); + // 无 keyword 且未显式设置 --max-preview:maxLength=0 表示无限制(返回全文) + // 有 keyword 或显式设置了 --max-preview:使用 maxPreviewLength 值 + if (config.keyword.isEmpty() && !config.maxPreviewSet) { + previewOptions.setMaxLength(0); + } else { + previewOptions.setMaxLength(config.maxPreviewLength); + } previewOptions.setKeyword(config.keyword); // Paths are stored as comma-separated in config.searchPath @@ -206,7 +212,7 @@ int main(int argc, char *argv[]) : (config.searchType == SearchType::Ocr) ? "ocr" : "semantic"; root["keyword"] = config.keyword; root["offset"] = config.offset; - root["maxLength"] = config.maxPreviewLength; + root["maxLength"] = previewOptions.maxLength(); QJsonArray results; for (const QString &path : paths) { diff --git a/src/dfm-search/dfm-search-lib/utils/contenthighlighter.cpp b/src/dfm-search/dfm-search-lib/utils/contenthighlighter.cpp index 3b82ca7..43caf75 100644 --- a/src/dfm-search/dfm-search-lib/utils/contenthighlighter.cpp +++ b/src/dfm-search/dfm-search-lib/utils/contenthighlighter.cpp @@ -249,13 +249,13 @@ QString previewSnippet(const QString &content, int offset, int maxLength, *keywordOffset = -1; } - if (content.isEmpty() || maxLength <= 0 || offset < 0) { + if (content.isEmpty() || offset < 0) { return {}; } if (keyword.isEmpty()) { - // 无 keyword:直接从 offset 截取 maxLength - return content.mid(offset, maxLength); + // 无 keyword:直接从 offset 截取 + return maxLength > 0 ? content.mid(offset, maxLength) : content.mid(offset); } // 有 keyword:从 offset 开始搜索,找到后从匹配位置截取 @@ -268,7 +268,7 @@ QString previewSnippet(const QString &content, int offset, int maxLength, *keywordOffset = pos; } - return content.mid(pos, maxLength); + return maxLength > 0 ? content.mid(pos, maxLength) : content.mid(pos); } } // namespace ContentHighlighter diff --git a/src/dfm-search/dfm-search-lib/utils/contenthighlighter.h b/src/dfm-search/dfm-search-lib/utils/contenthighlighter.h index 0595805..89ef5d7 100644 --- a/src/dfm-search/dfm-search-lib/utils/contenthighlighter.h +++ b/src/dfm-search/dfm-search-lib/utils/contenthighlighter.h @@ -47,9 +47,11 @@ QString customHighlight(const QStringList &keywords, const QString &content, int * 有 keyword: content.indexOf(keyword, offset) → 从匹配位置截取 maxLength * 不做 simplified,不加省略号,不高亮 * + * maxLength <= 0 表示无限制,返回从 offset(或 keyword 匹配位置)开始的全部剩余内容。 + * * @param content 原始文档内容 * @param offset 内容偏移值(默认0) - * @param maxLength 最大截取长度 + * @param maxLength 最大截取长度,<= 0 表示无限制 * @param keyword 搜索关键词(可选) * @param keywordOffset 输出 keyword 在全文中的匹配位置(-1 if 无 keyword 或未匹配) * @return 截取的内容片段,找不到 keyword 或 offset 越界时返回空字符串