diff --git a/src/core/search/match/coreplugin-match.ts b/src/core/search/match/coreplugin-match.ts index f57b0eb..b5aaf29 100644 --- a/src/core/search/match/coreplugin-match.ts +++ b/src/core/search/match/coreplugin-match.ts @@ -1,7 +1,8 @@ -import { App } from "obsidian"; +import { App, TFile } from "obsidian"; import { MAX_RESULTS, truncateLine, + findLineDetails, SearchResult, formatResults, } from '../search-common'; @@ -17,69 +18,69 @@ export async function searchFilesWithCorePlugin( query: string, app: App, ): Promise { - const searchPlugin = (app as any).internalPlugins.plugins['global-search']?.instance; - if (!searchPlugin) { - throw new Error("Core search plugin is not available."); - } + try { + const searchPlugin = (app as any).internalPlugins.plugins['global-search']?.instance; + if (!searchPlugin) { + throw new Error("Core search plugin is not available."); + } - // The core search function is not officially documented and may change. - // This is based on community findings and common usage in other plugins. - const searchResults = await new Promise((resolve) => { - const unregister = searchPlugin.on("search-results", (results: any) => { - unregister(); - resolve(results); - }); + // This function opens the search pane and executes the search. + // It does not return the results directly. searchPlugin.openGlobalSearch(query); - }); - const results: SearchResult[] = []; - const vault = app.vault; + // We must wait for the search to execute and the UI to update. + await new Promise(resolve => setTimeout(resolve, 500)); - for (const fileMatches of Object.values(searchResults) as any) { - if (results.length >= MAX_RESULTS) { - break; + const searchLeaf = app.workspace.getLeavesOfType('search')[0]; + if (!searchLeaf) { + throw new Error("No active search pane found after triggering search."); } - const file = vault.getAbstractFileByPath(fileMatches.file.path); - if (!file || !('read' in file)) { - continue; + // @ts-ignore + const searchResultsMap = (searchLeaf.view as any).dom.resultDomLookup; + if (!searchResultsMap || searchResultsMap.size === 0) { + console.error("No results found."); + return "No results found." } - const content = await vault.cachedRead(file as any); - const lines = content.split('\n'); + const results: SearchResult[] = []; + const vault = app.vault; - for (const match of fileMatches.result.content) { + for (const [file, fileMatches] of searchResultsMap.entries()) { if (results.length >= MAX_RESULTS) { break; } - const [matchText, startOffset] = match; - let charCount = 0; - let lineNumber = 0; - let column = 0; - let lineContent = ""; + const content = await vault.cachedRead(file as TFile); + const lines = content.split('\n'); - for (let i = 0; i < lines.length; i++) { - const lineLength = lines[i].length + 1; // +1 for the newline character - if (charCount + lineLength > startOffset) { - lineNumber = i + 1; - column = startOffset - charCount + 1; - lineContent = lines[i]; - break; - } - charCount += lineLength; + // `fileMatches.result.content` holds an array of matches for the file. + // Each match is an array: [matched_text, start_offset] + for (const match of fileMatches.result.content) { + if (results.length >= MAX_RESULTS) break; + + const startOffset = match[1]; + const { lineNumber, columnNumber, lineContent } = findLineDetails(lines, startOffset); + + if (lineNumber === -1) continue; + + results.push({ + file: file.path, + line: lineNumber + 1, // ripgrep is 1-based, so we adjust + column: columnNumber + 1, + match: truncateLine(lineContent.trimEnd()), + beforeContext: lineNumber > 0 ? [truncateLine(lines[lineNumber - 1].trimEnd())] : [], + afterContext: + lineNumber < lines.length - 1 + ? [truncateLine(lines[lineNumber + 1].trimEnd())] + : [], + }); } - - results.push({ - file: fileMatches.file.path, - line: lineNumber, - column: column, - match: truncateLine(lineContent.trimEnd()), - beforeContext: lineNumber > 1 ? [truncateLine(lines[lineNumber - 2].trimEnd())] : [], - afterContext: lineNumber < lines.length ? [truncateLine(lines[lineNumber].trimEnd())] : [], - }); } - } - return formatResults(results, ".\\"); + return formatResults(results, ".\\"); + } catch (error) { + console.error("Error during core plugin processing:", error); + return "An error occurred during the search."; + } } \ No newline at end of file diff --git a/src/core/search/match/omnisearch-match.ts b/src/core/search/match/omnisearch-match.ts index 5ab16f4..03f39b7 100644 --- a/src/core/search/match/omnisearch-match.ts +++ b/src/core/search/match/omnisearch-match.ts @@ -2,6 +2,7 @@ import { App } from "obsidian"; import { MAX_RESULTS, truncateLine, + findLineDetails, SearchResult, formatResults, } from '../search-common'; @@ -40,32 +41,6 @@ function isOmnisearchAvailable(): boolean { return window.omnisearch && typeof window.omnisearch.search === "function"; } -/** - * Finds the line number, column number, and content for a given character offset in a file. - * @param allLines All lines in the file. - * @param offset The character offset of the match. - * @returns An object with line number, column number, and the full line content. - */ -function findLineAndColumnFromOffset( - allLines: string[], - offset: number -): { lineNumber: number; columnNumber: number; lineContent: string } { - let charCount = 0; - for (let i = 0; i < allLines.length; i++) { - const line = allLines[i]; - // The line ending length (1 for \n, 2 for \r\n) can vary. - // A simple +1 is a reasonable approximation for this calculation. - const lineEndOffset = charCount + line.length + 1; - - if (offset < lineEndOffset) { - const columnNumber = offset - charCount; - return { lineNumber: i, columnNumber, lineContent: line }; - } - charCount = lineEndOffset; - } - return { lineNumber: -1, columnNumber: -1, lineContent: "" }; -} - /** * Searches using Omnisearch and builds context for each match. * @param query The search query for Omnisearch. Note: Omnisearch does not support full regex. @@ -87,7 +62,8 @@ export async function searchFilesWithOmnisearch( // The `query` will be treated as a keyword/fuzzy search by the plugin. const apiResults = await window.omnisearch.search(query); if (!apiResults || apiResults.length === 0) { - throw new Error("No results found."); + console.error("No results found."); + return "No results found." } const results: SearchResult[] = []; @@ -99,15 +75,15 @@ export async function searchFilesWithOmnisearch( if (!result.matches || result.matches.length === 0) continue; const fileContent = await app.vault.adapter.read(result.path); - const allLines = fileContent.split("\n"); + const lines = fileContent.split("\n"); for (const match of result.matches) { if (results.length >= MAX_RESULTS) { break; // Stop processing matches if we have enough results } - const { lineNumber, columnNumber, lineContent } = findLineAndColumnFromOffset( - allLines, + const { lineNumber, columnNumber, lineContent } = findLineDetails( + lines, match.offset ); @@ -118,10 +94,10 @@ export async function searchFilesWithOmnisearch( line: lineNumber + 1, // ripgrep is 1-based, so we adjust column: columnNumber + 1, match: truncateLine(lineContent.trimEnd()), - beforeContext: lineNumber > 0 ? [truncateLine(allLines[lineNumber - 1].trimEnd())] : [], + beforeContext: lineNumber > 0 ? [truncateLine(lines[lineNumber - 1].trimEnd())] : [], afterContext: - lineNumber < allLines.length - 1 - ? [truncateLine(allLines[lineNumber + 1].trimEnd())] + lineNumber < lines.length - 1 + ? [truncateLine(lines[lineNumber + 1].trimEnd())] : [], }; results.push(searchResult); diff --git a/src/core/search/regex/coreplugin-regex.ts b/src/core/search/regex/coreplugin-regex.ts index d29ad54..4e233f8 100644 --- a/src/core/search/regex/coreplugin-regex.ts +++ b/src/core/search/regex/coreplugin-regex.ts @@ -12,6 +12,6 @@ export async function regexSearchFilesWithCorePlugin( regex: string, app: App, ): Promise { - const query = "/" + regex + "/"; - return searchFilesWithCorePlugin(query, app); + const regexQuery = `/${regex}/`; + return searchFilesWithCorePlugin(regexQuery, app); } \ No newline at end of file diff --git a/src/core/search/regex/ripgrep-regex.ts b/src/core/search/regex/ripgrep-regex.ts index fa3bf39..3adf6c7 100644 --- a/src/core/search/regex/ripgrep-regex.ts +++ b/src/core/search/regex/ripgrep-regex.ts @@ -96,7 +96,7 @@ export async function regexSearchFilesWithRipgrep( output = await execRipgrep(rgPath, args) } catch (error) { console.error("Error executing ripgrep:", error) - return "No results found" + return "No results found." } const results: SearchResult[] = [] let currentResult: Partial | null = null diff --git a/src/core/search/search-common.ts b/src/core/search/search-common.ts index 0e5b3e7..e16c1b8 100644 --- a/src/core/search/search-common.ts +++ b/src/core/search/search-common.ts @@ -14,6 +14,32 @@ export function truncateLine(line: string, maxLength: number = MAX_LINE_LENGTH): return line.length > maxLength ? line.substring(0, maxLength) + " [truncated...]" : line } +/** + * Finds the line number and content for a given character offset within a file's content. + * @param lines All lines in the file. + * @param offset The character offset of the match. + * @returns An object with line number, column number, and the full line content. + */ +export function findLineDetails( + lines: string[], + offset: number +): { lineNumber: number; columnNumber: number; lineContent: string } { + let charCount = 0; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + // The line ending length (1 for \n, 2 for \r\n) can vary. + // A simple +1 is a reasonable approximation for this calculation. + const lineEndOffset = charCount + line.length + 1; + + if (offset < lineEndOffset) { + const columnNumber = offset - charCount; + return { lineNumber: i, columnNumber, lineContent: line }; + } + charCount = lineEndOffset; + } + return { lineNumber: -1, columnNumber: -1, lineContent: "" }; +} + export interface SearchResult { file: string line: number diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 3f2b8c5..0374b61 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -235,6 +235,7 @@ export default { matchBackendDescription: 'Choose the backend for match search method.', ripgrep: 'ripgrep', coreplugin: 'Core plugin', + omnisearch: 'Omnisearch', ripgrepPath: 'ripgrep path', ripgrepPathDescription: 'Path to the ripgrep binary. When using ripgrep regex search, this is required.', }, diff --git a/src/lang/locale/zh-cn.ts b/src/lang/locale/zh-cn.ts index 05c813d..99081ca 100644 --- a/src/lang/locale/zh-cn.ts +++ b/src/lang/locale/zh-cn.ts @@ -236,6 +236,7 @@ export default { matchBackendDescription: '选择匹配搜索的后端。', ripgrep: 'ripgrep', coreplugin: '核心插件', + omnisearch: 'Omnisearch', ripgrepPath: 'ripgrep 路径', ripgrepPathDescription: 'ripgrep 二进制文件的路径。使用 ripgrep 正则搜索时需要此项。', },