fix: Improve search result processing and error handling
- Refactor core plugin and Omnisearch result processing to use a shared helper function findLineDetails. - Update error handling in core plugin and Omnisearch search functions to return a "No results found" string instead of throwing an error when no results are found.
This commit is contained in:
parent
9984527e85
commit
c2dfb48e22
@ -1,7 +1,8 @@
|
|||||||
import { App } from "obsidian";
|
import { App, TFile } from "obsidian";
|
||||||
import {
|
import {
|
||||||
MAX_RESULTS,
|
MAX_RESULTS,
|
||||||
truncateLine,
|
truncateLine,
|
||||||
|
findLineDetails,
|
||||||
SearchResult,
|
SearchResult,
|
||||||
formatResults,
|
formatResults,
|
||||||
} from '../search-common';
|
} from '../search-common';
|
||||||
@ -17,69 +18,69 @@ export async function searchFilesWithCorePlugin(
|
|||||||
query: string,
|
query: string,
|
||||||
app: App,
|
app: App,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const searchPlugin = (app as any).internalPlugins.plugins['global-search']?.instance;
|
try {
|
||||||
if (!searchPlugin) {
|
const searchPlugin = (app as any).internalPlugins.plugins['global-search']?.instance;
|
||||||
throw new Error("Core search plugin is not available.");
|
if (!searchPlugin) {
|
||||||
}
|
throw new Error("Core search plugin is not available.");
|
||||||
|
}
|
||||||
|
|
||||||
// The core search function is not officially documented and may change.
|
// This function opens the search pane and executes the search.
|
||||||
// This is based on community findings and common usage in other plugins.
|
// It does not return the results directly.
|
||||||
const searchResults = await new Promise<any[]>((resolve) => {
|
|
||||||
const unregister = searchPlugin.on("search-results", (results: any) => {
|
|
||||||
unregister();
|
|
||||||
resolve(results);
|
|
||||||
});
|
|
||||||
searchPlugin.openGlobalSearch(query);
|
searchPlugin.openGlobalSearch(query);
|
||||||
});
|
|
||||||
|
|
||||||
const results: SearchResult[] = [];
|
// We must wait for the search to execute and the UI to update.
|
||||||
const vault = app.vault;
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
for (const fileMatches of Object.values(searchResults) as any) {
|
const searchLeaf = app.workspace.getLeavesOfType('search')[0];
|
||||||
if (results.length >= MAX_RESULTS) {
|
if (!searchLeaf) {
|
||||||
break;
|
throw new Error("No active search pane found after triggering search.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = vault.getAbstractFileByPath(fileMatches.file.path);
|
// @ts-ignore
|
||||||
if (!file || !('read' in file)) {
|
const searchResultsMap = (searchLeaf.view as any).dom.resultDomLookup;
|
||||||
continue;
|
if (!searchResultsMap || searchResultsMap.size === 0) {
|
||||||
|
console.error("No results found.");
|
||||||
|
return "No results found."
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await vault.cachedRead(file as any);
|
const results: SearchResult[] = [];
|
||||||
const lines = content.split('\n');
|
const vault = app.vault;
|
||||||
|
|
||||||
for (const match of fileMatches.result.content) {
|
for (const [file, fileMatches] of searchResultsMap.entries()) {
|
||||||
if (results.length >= MAX_RESULTS) {
|
if (results.length >= MAX_RESULTS) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [matchText, startOffset] = match;
|
const content = await vault.cachedRead(file as TFile);
|
||||||
let charCount = 0;
|
const lines = content.split('\n');
|
||||||
let lineNumber = 0;
|
|
||||||
let column = 0;
|
|
||||||
let lineContent = "";
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
// `fileMatches.result.content` holds an array of matches for the file.
|
||||||
const lineLength = lines[i].length + 1; // +1 for the newline character
|
// Each match is an array: [matched_text, start_offset]
|
||||||
if (charCount + lineLength > startOffset) {
|
for (const match of fileMatches.result.content) {
|
||||||
lineNumber = i + 1;
|
if (results.length >= MAX_RESULTS) break;
|
||||||
column = startOffset - charCount + 1;
|
|
||||||
lineContent = lines[i];
|
const startOffset = match[1];
|
||||||
break;
|
const { lineNumber, columnNumber, lineContent } = findLineDetails(lines, startOffset);
|
||||||
}
|
|
||||||
charCount += lineLength;
|
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.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,6 +2,7 @@ import { App } from "obsidian";
|
|||||||
import {
|
import {
|
||||||
MAX_RESULTS,
|
MAX_RESULTS,
|
||||||
truncateLine,
|
truncateLine,
|
||||||
|
findLineDetails,
|
||||||
SearchResult,
|
SearchResult,
|
||||||
formatResults,
|
formatResults,
|
||||||
} from '../search-common';
|
} from '../search-common';
|
||||||
@ -40,32 +41,6 @@ function isOmnisearchAvailable(): boolean {
|
|||||||
return window.omnisearch && typeof window.omnisearch.search === "function";
|
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.
|
* Searches using Omnisearch and builds context for each match.
|
||||||
* @param query The search query for Omnisearch. Note: Omnisearch does not support full regex.
|
* @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.
|
// The `query` will be treated as a keyword/fuzzy search by the plugin.
|
||||||
const apiResults = await window.omnisearch.search(query);
|
const apiResults = await window.omnisearch.search(query);
|
||||||
if (!apiResults || apiResults.length === 0) {
|
if (!apiResults || apiResults.length === 0) {
|
||||||
throw new Error("No results found.");
|
console.error("No results found.");
|
||||||
|
return "No results found."
|
||||||
}
|
}
|
||||||
|
|
||||||
const results: SearchResult[] = [];
|
const results: SearchResult[] = [];
|
||||||
@ -99,15 +75,15 @@ export async function searchFilesWithOmnisearch(
|
|||||||
if (!result.matches || result.matches.length === 0) continue;
|
if (!result.matches || result.matches.length === 0) continue;
|
||||||
|
|
||||||
const fileContent = await app.vault.adapter.read(result.path);
|
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) {
|
for (const match of result.matches) {
|
||||||
if (results.length >= MAX_RESULTS) {
|
if (results.length >= MAX_RESULTS) {
|
||||||
break; // Stop processing matches if we have enough results
|
break; // Stop processing matches if we have enough results
|
||||||
}
|
}
|
||||||
|
|
||||||
const { lineNumber, columnNumber, lineContent } = findLineAndColumnFromOffset(
|
const { lineNumber, columnNumber, lineContent } = findLineDetails(
|
||||||
allLines,
|
lines,
|
||||||
match.offset
|
match.offset
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -118,10 +94,10 @@ export async function searchFilesWithOmnisearch(
|
|||||||
line: lineNumber + 1, // ripgrep is 1-based, so we adjust
|
line: lineNumber + 1, // ripgrep is 1-based, so we adjust
|
||||||
column: columnNumber + 1,
|
column: columnNumber + 1,
|
||||||
match: truncateLine(lineContent.trimEnd()),
|
match: truncateLine(lineContent.trimEnd()),
|
||||||
beforeContext: lineNumber > 0 ? [truncateLine(allLines[lineNumber - 1].trimEnd())] : [],
|
beforeContext: lineNumber > 0 ? [truncateLine(lines[lineNumber - 1].trimEnd())] : [],
|
||||||
afterContext:
|
afterContext:
|
||||||
lineNumber < allLines.length - 1
|
lineNumber < lines.length - 1
|
||||||
? [truncateLine(allLines[lineNumber + 1].trimEnd())]
|
? [truncateLine(lines[lineNumber + 1].trimEnd())]
|
||||||
: [],
|
: [],
|
||||||
};
|
};
|
||||||
results.push(searchResult);
|
results.push(searchResult);
|
||||||
|
|||||||
@ -12,6 +12,6 @@ export async function regexSearchFilesWithCorePlugin(
|
|||||||
regex: string,
|
regex: string,
|
||||||
app: App,
|
app: App,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const query = "/" + regex + "/";
|
const regexQuery = `/${regex}/`;
|
||||||
return searchFilesWithCorePlugin(query, app);
|
return searchFilesWithCorePlugin(regexQuery, app);
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ export async function regexSearchFilesWithRipgrep(
|
|||||||
output = await execRipgrep(rgPath, args)
|
output = await execRipgrep(rgPath, args)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error executing ripgrep:", error)
|
console.error("Error executing ripgrep:", error)
|
||||||
return "No results found"
|
return "No results found."
|
||||||
}
|
}
|
||||||
const results: SearchResult[] = []
|
const results: SearchResult[] = []
|
||||||
let currentResult: Partial<SearchResult> | null = null
|
let currentResult: Partial<SearchResult> | null = null
|
||||||
|
|||||||
@ -14,6 +14,32 @@ export function truncateLine(line: string, maxLength: number = MAX_LINE_LENGTH):
|
|||||||
return line.length > maxLength ? line.substring(0, maxLength) + " [truncated...]" : line
|
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 {
|
export interface SearchResult {
|
||||||
file: string
|
file: string
|
||||||
line: number
|
line: number
|
||||||
|
|||||||
@ -235,6 +235,7 @@ export default {
|
|||||||
matchBackendDescription: 'Choose the backend for match search method.',
|
matchBackendDescription: 'Choose the backend for match search method.',
|
||||||
ripgrep: 'ripgrep',
|
ripgrep: 'ripgrep',
|
||||||
coreplugin: 'Core plugin',
|
coreplugin: 'Core plugin',
|
||||||
|
omnisearch: 'Omnisearch',
|
||||||
ripgrepPath: 'ripgrep path',
|
ripgrepPath: 'ripgrep path',
|
||||||
ripgrepPathDescription: 'Path to the ripgrep binary. When using ripgrep regex search, this is required.',
|
ripgrepPathDescription: 'Path to the ripgrep binary. When using ripgrep regex search, this is required.',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -236,6 +236,7 @@ export default {
|
|||||||
matchBackendDescription: '选择匹配搜索的后端。',
|
matchBackendDescription: '选择匹配搜索的后端。',
|
||||||
ripgrep: 'ripgrep',
|
ripgrep: 'ripgrep',
|
||||||
coreplugin: '核心插件',
|
coreplugin: '核心插件',
|
||||||
|
omnisearch: 'Omnisearch',
|
||||||
ripgrepPath: 'ripgrep 路径',
|
ripgrepPath: 'ripgrep 路径',
|
||||||
ripgrepPathDescription: 'ripgrep 二进制文件的路径。使用 ripgrep 正则搜索时需要此项。',
|
ripgrepPathDescription: 'ripgrep 二进制文件的路径。使用 ripgrep 正则搜索时需要此项。',
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user