9c245d9f8d
* docs: map existing codebase * feat(grep): add gitignore toggle support Thread gitignore option through coding-agent schema, TS native bindings, and Rust grep scan paths. Add native and tool tests for default behavior and gitignore:false behavior. Fixes #267 * chore: remove tracked .planning artifacts * style(rust): format grep scan call * feat(grep): add gitignore toggle for ignored files Fixes #267 --------- Co-authored-by: Vũ Anh Nguyễn <vu.anh.nguyen@mgm-tp.com> Co-authored-by: can1357 <me@can.ac>
161 lines
4.4 KiB
TypeScript
161 lines
4.4 KiB
TypeScript
/**
|
|
* Grep CLI command handlers.
|
|
*
|
|
* Handles `omp grep` subcommand for testing grep tool on Windows.
|
|
*/
|
|
import * as path from "node:path";
|
|
import { grep } from "@oh-my-pi/pi-natives";
|
|
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
import chalk from "chalk";
|
|
|
|
export interface GrepCommandArgs {
|
|
pattern: string;
|
|
path: string;
|
|
glob?: string;
|
|
limit: number;
|
|
context: number;
|
|
mode: "content" | "filesWithMatches" | "count";
|
|
gitignore: boolean;
|
|
}
|
|
|
|
/**
|
|
* Parse grep subcommand arguments.
|
|
* Returns undefined if not a grep command.
|
|
*/
|
|
export function parseGrepArgs(args: string[]): GrepCommandArgs | undefined {
|
|
if (args.length === 0 || args[0] !== "grep") {
|
|
return undefined;
|
|
}
|
|
|
|
const result: GrepCommandArgs = {
|
|
pattern: "",
|
|
path: ".",
|
|
limit: 20,
|
|
context: 2,
|
|
mode: "content",
|
|
gitignore: true,
|
|
};
|
|
|
|
const positional: string[] = [];
|
|
|
|
for (let i = 1; i < args.length; i++) {
|
|
const arg = args[i];
|
|
if (arg === "--glob" || arg === "-g") {
|
|
result.glob = args[++i];
|
|
} else if (arg === "--limit" || arg === "-l") {
|
|
result.limit = parseInt(args[++i], 10);
|
|
} else if (arg === "--context" || arg === "-C") {
|
|
result.context = parseInt(args[++i], 10);
|
|
} else if (arg === "--files" || arg === "-f") {
|
|
result.mode = "filesWithMatches";
|
|
} else if (arg === "--count" || arg === "-c") {
|
|
result.mode = "count";
|
|
} else if (arg === "--no-gitignore") {
|
|
result.gitignore = false;
|
|
} else if (!arg.startsWith("-")) {
|
|
positional.push(arg);
|
|
}
|
|
}
|
|
|
|
if (positional.length >= 1) {
|
|
result.pattern = positional[0];
|
|
}
|
|
if (positional.length >= 2) {
|
|
result.path = positional[1];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export async function runGrepCommand(cmd: GrepCommandArgs): Promise<void> {
|
|
if (!cmd.pattern) {
|
|
console.error(chalk.red("Error: Pattern is required"));
|
|
process.exit(1);
|
|
}
|
|
|
|
const searchPath = path.resolve(cmd.path);
|
|
console.log(chalk.dim(`Searching in: ${searchPath}`));
|
|
console.log(chalk.dim(`Pattern: ${cmd.pattern}`));
|
|
console.log(
|
|
chalk.dim(`Mode: ${cmd.mode}, Limit: ${cmd.limit}, Context: ${cmd.context}, Gitignore: ${cmd.gitignore}`),
|
|
);
|
|
|
|
console.log("");
|
|
|
|
try {
|
|
const result = await grep({
|
|
pattern: cmd.pattern,
|
|
path: searchPath,
|
|
glob: cmd.glob,
|
|
mode: cmd.mode,
|
|
maxCount: cmd.limit,
|
|
context: cmd.mode === "content" ? cmd.context : undefined,
|
|
hidden: true,
|
|
gitignore: cmd.gitignore,
|
|
});
|
|
|
|
console.log(chalk.green(`Total matches: ${result.totalMatches}`));
|
|
console.log(chalk.green(`Files with matches: ${result.filesWithMatches}`));
|
|
console.log(chalk.green(`Files searched: ${result.filesSearched}`));
|
|
if (result.limitReached) {
|
|
console.log(chalk.yellow(`Limit reached: true`));
|
|
}
|
|
console.log("");
|
|
|
|
for (const match of result.matches) {
|
|
const displayPath = match.path.replace(/\\/g, "/");
|
|
|
|
if (cmd.mode === "content") {
|
|
if (match.contextBefore) {
|
|
for (const ctx of match.contextBefore) {
|
|
console.log(chalk.dim(`${displayPath}-${ctx.lineNumber}- ${ctx.line}`));
|
|
}
|
|
}
|
|
console.log(`${chalk.cyan(displayPath)}:${chalk.yellow(String(match.lineNumber))}: ${match.line}`);
|
|
if (match.contextAfter) {
|
|
for (const ctx of match.contextAfter) {
|
|
console.log(chalk.dim(`${displayPath}-${ctx.lineNumber}- ${ctx.line}`));
|
|
}
|
|
}
|
|
console.log("");
|
|
} else if (cmd.mode === "count") {
|
|
console.log(`${chalk.cyan(displayPath)}: ${match.matchCount ?? 0} matches`);
|
|
} else {
|
|
console.log(chalk.cyan(displayPath));
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
export function printGrepHelp(): void {
|
|
console.log(`${chalk.bold(`${APP_NAME} grep`)} - Test grep tool
|
|
|
|
${chalk.bold("Usage:")}
|
|
${APP_NAME} grep <pattern> [path] [options]
|
|
|
|
${chalk.bold("Arguments:")}
|
|
pattern Regex pattern to search for
|
|
path Directory or file to search (default: .)
|
|
|
|
${chalk.bold("Options:")}
|
|
-g, --glob <pattern> Filter files by glob pattern
|
|
-l, --limit <n> Max matches (default: 20)
|
|
-C, --context <n> Context lines (default: 2)
|
|
-f, --files Output file names only
|
|
-c, --count Output match counts per file
|
|
-h, --help Show this help
|
|
--no-gitignore Include files excluded by .gitignore
|
|
|
|
${chalk.bold("Environment:")}
|
|
PI_GREP_WORKERS=0 Disable worker pool (use single-threaded mode)
|
|
|
|
${chalk.bold("Examples:")}
|
|
${APP_NAME} grep "import" src/
|
|
${APP_NAME} grep "TODO" . --glob "*.ts"
|
|
${APP_NAME} grep "function" --files
|
|
`);
|
|
}
|