Files
oh-my-pi/packages/coding-agent/src/cli/grep-cli.ts
T
daandden 9c245d9f8d feat(grep): add gitignore toggle support (#270)
* 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>
2026-03-03 14:44:58 +01:00

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
`);
}