mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-05-09 08:30:09 +00:00
feat: Enhance file search with core plugin and Omnisearch integration
- Introduces a new match_search_files tool for fuzzy/keyword search, integrating with Obsidian's core search plugin and updating Omnisearch integration for improved file search capabilities. - Adds settings for selecting search backends (core plugin, Omnisearch, ripgrep) for both regex and match searches. - Updates language files, prompts, and types to support the new functionality. - Restructures search-related files for better organization.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
const MatchSearchFilesInstructions = "\n- You can use match_search_files to perform fuzzy-based searches across files using keyword/phrase. This tool is ideal for finding similar texts in notes. It excels at finding similar contents with similar keywords and phrases quickly."
|
||||
|
||||
const RegexSearchFilesInstructions = "\n- You can use regex_search_files to perform pattern-based searches across files using regular expressions. This tool is ideal for finding exact text matches, specific patterns (like tags, links, dates, URLs), or structural elements in notes. It excels at locating precise format patterns and is perfect for finding connections between notes, frontmatter elements, or specific Markdown formatting."
|
||||
|
||||
const SemanticSearchFilesInstructions = "\n- You can use semantic_search_files to find content based on meaning rather than exact text matches. Semantic search uses embedding vectors to understand concepts and ideas, finding relevant content even when keywords differ. This is especially powerful for discovering thematically related notes, answering conceptual questions about your knowledge base, or finding content when you don't know the exact wording used in the notes."
|
||||
@@ -6,12 +8,20 @@ function getObsidianCapabilitiesSection(
|
||||
cwd: string,
|
||||
searchFilesTool: string,
|
||||
): string {
|
||||
|
||||
const searchFilesInstructions = searchFilesTool === 'regex'
|
||||
? RegexSearchFilesInstructions
|
||||
: searchFilesTool === 'semantic'
|
||||
? SemanticSearchFilesInstructions
|
||||
: "";
|
||||
let searchFilesInstructions: string;
|
||||
switch (searchFilesTool) {
|
||||
case 'match':
|
||||
searchFilesInstructions = MatchSearchFilesInstructions;
|
||||
break;
|
||||
case 'regex':
|
||||
searchFilesInstructions = RegexSearchFilesInstructions;
|
||||
break;
|
||||
case 'semantic':
|
||||
searchFilesInstructions = SemanticSearchFilesInstructions;
|
||||
break;
|
||||
default:
|
||||
searchFilesInstructions = "";
|
||||
}
|
||||
|
||||
return `====
|
||||
|
||||
|
||||
@@ -57,7 +57,9 @@ function getEditingInstructions(diffStrategy?: DiffStrategy): string {
|
||||
}
|
||||
|
||||
function getSearchInstructions(searchTool: string): string {
|
||||
if (searchTool === 'regex') {
|
||||
if (searchTool === 'match') {
|
||||
return `- When using the match_search_files tool, craft your keyword/phrase carefully to balance specificity and flexibility. Based on the user's task, you may use it to find specific content, notes, headings, connections between notes, tags, or any text-based information across the Obsidian vault. The results include context, so analyze the surrounding text to better understand the matches. Leverage the match_search_files tool in combination with other tools for comprehensive analysis. For example, use it to find specific keywords or phrases, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.`
|
||||
} else if (searchTool === 'regex') {
|
||||
return `- When using the regex_search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task, you may use it to find specific content, notes, headings, connections between notes, tags, or any text-based information across the Obsidian vault. The results include context, so analyze the surrounding text to better understand the matches. Leverage the regex_search_files tool in combination with other tools for comprehensive analysis. For example, use it to find specific phrases or patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.`
|
||||
} else if (searchTool === 'semantic') {
|
||||
return `- When using the semantic_search_files tool, craft your natural language query to describe concepts and ideas rather than specific patterns. Based on the user's task, you may use it to find thematically related content, conceptually similar notes, or knowledge connections across the Obsidian vault, even when exact keywords aren't present. The results include context, so analyze the surrounding text to understand the conceptual relevance of each match. Leverage the semantic_search_files tool in combination with other tools for comprehensive analysis. For example, use it to find specific phrases or patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.`
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ToolArgs } from "./types"
|
||||
|
||||
export function getSearchFilesDescription(args: ToolArgs): string {
|
||||
if (args.searchTool === 'regex') {
|
||||
if (args.searchTool === 'match') {
|
||||
return getMatchSearchFilesDescription(args)
|
||||
} else if (args.searchTool === 'regex') {
|
||||
return getRegexSearchFilesDescription(args)
|
||||
} else if (args.searchTool === 'semantic') {
|
||||
return getSemanticSearchFilesDescription(args)
|
||||
@@ -10,6 +12,26 @@ export function getSearchFilesDescription(args: ToolArgs): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMatchSearchFilesDescription(args: ToolArgs): string {
|
||||
return `## match_search_files
|
||||
Description: Request to perform a match/fuzzy search across files in a specified directory, providing context-rich results. This tool searches for specific content across multiple files, displaying each match with encapsulating context.
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current working directory ${args.cwd}). This directory will be recursively searched.
|
||||
- query: (required) The keyword, phrase to search for. The system will find documents with similar keywords/phrases.
|
||||
|
||||
Usage:
|
||||
<match_search_files>
|
||||
<path>Directory path here</path>
|
||||
<query>Your keyword/phrase here</query>
|
||||
</match_search_files>
|
||||
|
||||
Example: Requesting to search for all Markdown files containing 'test' in the current directory
|
||||
<match_search_files>
|
||||
<path>.</path>
|
||||
<query>test</query>
|
||||
</match_search_files>`
|
||||
}
|
||||
|
||||
export function getRegexSearchFilesDescription(args: ToolArgs): string {
|
||||
return `## regex_search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
85
src/core/search/match/coreplugin-match.ts
Normal file
85
src/core/search/match/coreplugin-match.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { App } from "obsidian";
|
||||
import {
|
||||
MAX_RESULTS,
|
||||
truncateLine,
|
||||
SearchResult,
|
||||
formatResults,
|
||||
} from '../search-common';
|
||||
|
||||
/**
|
||||
* Searches using Obsidian's core search plugin and builds context for each match.
|
||||
*
|
||||
* @param app The Obsidian App instance.
|
||||
* @param query The query to search for.
|
||||
* @returns A promise that resolves to a formatted string of search results.
|
||||
*/
|
||||
export async function searchFilesWithCorePlugin(
|
||||
query: string,
|
||||
app: App,
|
||||
): Promise<string> {
|
||||
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<any[]>((resolve) => {
|
||||
const unregister = searchPlugin.on("search-results", (results: any) => {
|
||||
unregister();
|
||||
resolve(results);
|
||||
});
|
||||
searchPlugin.openGlobalSearch(query);
|
||||
});
|
||||
|
||||
const results: SearchResult[] = [];
|
||||
const vault = app.vault;
|
||||
|
||||
for (const fileMatches of Object.values(searchResults) as any) {
|
||||
if (results.length >= MAX_RESULTS) {
|
||||
break;
|
||||
}
|
||||
|
||||
const file = vault.getAbstractFileByPath(fileMatches.file.path);
|
||||
if (!file || !('read' in file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = await vault.cachedRead(file as any);
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (const match of fileMatches.result.content) {
|
||||
if (results.length >= MAX_RESULTS) {
|
||||
break;
|
||||
}
|
||||
|
||||
const [matchText, startOffset] = match;
|
||||
let charCount = 0;
|
||||
let lineNumber = 0;
|
||||
let column = 0;
|
||||
let lineContent = "";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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, ".\\");
|
||||
}
|
||||
@@ -4,9 +4,7 @@ import {
|
||||
truncateLine,
|
||||
SearchResult,
|
||||
formatResults,
|
||||
} from './regex-common';
|
||||
|
||||
// --- Omnisearch API and Helper Types ---
|
||||
} from '../search-common';
|
||||
|
||||
type SearchMatchApi = {
|
||||
match: string;
|
||||
@@ -69,14 +67,12 @@ function findLineAndColumnFromOffset(
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches using Omnisearch and builds context for each match to replicate ripgrep's output.
|
||||
* @param vaultPath The absolute path of the vault for making relative paths.
|
||||
* Searches using Omnisearch and builds context for each match.
|
||||
* @param query The search query for Omnisearch. Note: Omnisearch does not support full regex.
|
||||
* @param app The Obsidian App instance.
|
||||
* @returns A formatted string of search results.
|
||||
*/
|
||||
export async function regexSearchFilesWithOmnisearch(
|
||||
vaultPath: string,
|
||||
export async function searchFilesWithOmnisearch(
|
||||
query: string,
|
||||
app: App,
|
||||
): Promise<string> {
|
||||
@@ -87,8 +83,8 @@ export async function regexSearchFilesWithOmnisearch(
|
||||
);
|
||||
}
|
||||
|
||||
// Omnisearch is not a regex engine. The function name is kept for consistency
|
||||
// but the `query` will be treated as a keyword/fuzzy search by the plugin.
|
||||
// Omnisearch is not a regex engine.
|
||||
// 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.");
|
||||
@@ -132,7 +128,7 @@ export async function regexSearchFilesWithOmnisearch(
|
||||
}
|
||||
}
|
||||
|
||||
return formatResults(results, vaultPath);
|
||||
return formatResults(results, ".\\");
|
||||
} catch (error) {
|
||||
console.error("Error during Omnisearch processing:", error);
|
||||
return "An error occurred during the search.";
|
||||
17
src/core/search/regex/coreplugin-regex.ts
Normal file
17
src/core/search/regex/coreplugin-regex.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { App } from "obsidian";
|
||||
import { searchFilesWithCorePlugin } from '../match/coreplugin-match'
|
||||
|
||||
/**
|
||||
* Performs a regular expression search using Obsidian's core search plugin.
|
||||
*
|
||||
* @param app The Obsidian App instance.
|
||||
* @param regex The regular expression to search for.
|
||||
* @returns A promise that resolves to a formatted string of search results.
|
||||
*/
|
||||
export async function regexSearchFilesWithCorePlugin(
|
||||
regex: string,
|
||||
app: App,
|
||||
): Promise<string> {
|
||||
const query = "/" + regex + "/";
|
||||
return searchFilesWithCorePlugin(query, app);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
truncateLine,
|
||||
SearchResult,
|
||||
formatResults
|
||||
} from './regex-common'
|
||||
} from '../search-common'
|
||||
|
||||
const isWindows = /^win/.test(process.platform)
|
||||
const binName = isWindows ? "rg.exe" : "rg"
|
||||
Reference in New Issue
Block a user