Merge pull request #3016 from code-yeongyu/fix/issue-3003-ripgrep-autodownload

fix(grep): enable ripgrep auto-download when not found in PATH
This commit is contained in:
YeonGyu-Kim
2026-04-02 15:10:35 +09:00
committed by GitHub
3 changed files with 34 additions and 13 deletions
+15 -8
View File
@@ -1,6 +1,7 @@
import { spawn } from "bun" import { spawn } from "bun"
import { import {
resolveGrepCli, resolveGrepCli,
type ResolvedCli,
type GrepBackend, type GrepBackend,
DEFAULT_MAX_DEPTH, DEFAULT_MAX_DEPTH,
DEFAULT_MAX_FILESIZE, DEFAULT_MAX_FILESIZE,
@@ -148,17 +149,17 @@ function parseCountOutput(output: string): CountResult[] {
return results return results
} }
export async function runRg(options: GrepOptions): Promise<GrepResult> { export async function runRg(options: GrepOptions, resolvedCli?: ResolvedCli): Promise<GrepResult> {
await rgSemaphore.acquire() await rgSemaphore.acquire()
try { try {
return await runRgInternal(options) return await runRgInternal(options, resolvedCli)
} finally { } finally {
rgSemaphore.release() rgSemaphore.release()
} }
} }
async function runRgInternal(options: GrepOptions): Promise<GrepResult> { async function runRgInternal(options: GrepOptions, resolvedCli?: ResolvedCli): Promise<GrepResult> {
const cli = resolveGrepCli() const cli = resolvedCli ?? resolveGrepCli()
const args = buildArgs(options, cli.backend) const args = buildArgs(options, cli.backend)
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS) const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS)
@@ -224,17 +225,23 @@ async function runRgInternal(options: GrepOptions): Promise<GrepResult> {
} }
} }
export async function runRgCount(options: Omit<GrepOptions, "context">): Promise<CountResult[]> { export async function runRgCount(
options: Omit<GrepOptions, "context">,
resolvedCli?: ResolvedCli
): Promise<CountResult[]> {
await rgSemaphore.acquire() await rgSemaphore.acquire()
try { try {
return await runRgCountInternal(options) return await runRgCountInternal(options, resolvedCli)
} finally { } finally {
rgSemaphore.release() rgSemaphore.release()
} }
} }
async function runRgCountInternal(options: Omit<GrepOptions, "context">): Promise<CountResult[]> { async function runRgCountInternal(
const cli = resolveGrepCli() options: Omit<GrepOptions, "context">,
resolvedCli?: ResolvedCli
): Promise<CountResult[]> {
const cli = resolvedCli ?? resolveGrepCli()
const args = buildArgs({ ...options, context: 0 }, cli.backend) const args = buildArgs({ ...options, context: 0 }, cli.backend)
if (cli.backend === "rg") { if (cli.backend === "rg") {
+15 -3
View File
@@ -3,10 +3,11 @@ import { join, dirname } from "node:path"
import { spawnSync } from "node:child_process" import { spawnSync } from "node:child_process"
import { getInstalledRipgrepPath, downloadAndInstallRipgrep } from "./downloader" import { getInstalledRipgrepPath, downloadAndInstallRipgrep } from "./downloader"
import { getDataDir } from "../../shared/data-path" import { getDataDir } from "../../shared/data-path"
import { log } from "../../shared/logger"
export type GrepBackend = "rg" | "grep" export type GrepBackend = "rg" | "grep"
interface ResolvedCli { export interface ResolvedCli {
path: string path: string
backend: GrepBackend backend: GrepBackend
} }
@@ -89,7 +90,7 @@ export function resolveGrepCli(): ResolvedCli {
export async function resolveGrepCliWithAutoInstall(): Promise<ResolvedCli> { export async function resolveGrepCliWithAutoInstall(): Promise<ResolvedCli> {
const current = resolveGrepCli() const current = resolveGrepCli()
if (current.backend === "rg") { if (current.backend === "rg" && current.path !== "rg") {
return current return current
} }
@@ -103,7 +104,18 @@ export async function resolveGrepCliWithAutoInstall(): Promise<ResolvedCli> {
const rgPath = await downloadAndInstallRipgrep() const rgPath = await downloadAndInstallRipgrep()
cachedCli = { path: rgPath, backend: "rg" } cachedCli = { path: rgPath, backend: "rg" }
return cachedCli return cachedCli
} catch { } catch (error) {
if (current.backend === "grep") {
log("[oh-my-opencode] Failed to auto-install ripgrep. Falling back to GNU grep.", {
error: error instanceof Error ? error.message : String(error),
grep_path: current.path,
})
} else {
log("[oh-my-opencode] Failed to auto-install ripgrep and GNU grep was not found.", {
error: error instanceof Error ? error.message : String(error),
})
}
return current return current
} }
} }
+4 -2
View File
@@ -2,6 +2,7 @@ import { resolve } from "node:path"
import type { PluginInput } from "@opencode-ai/plugin" import type { PluginInput } from "@opencode-ai/plugin"
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool" import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
import { runRg, runRgCount } from "./cli" import { runRg, runRgCount } from "./cli"
import { resolveGrepCliWithAutoInstall } from "./constants"
import { formatGrepResult, formatCountResult } from "./result-formatter" import { formatGrepResult, formatCountResult } from "./result-formatter"
export function createGrepTools(ctx: PluginInput): Record<string, ToolDefinition> { export function createGrepTools(ctx: PluginInput): Record<string, ToolDefinition> {
@@ -42,13 +43,14 @@ export function createGrepTools(ctx: PluginInput): Record<string, ToolDefinition
const paths = [searchPath] const paths = [searchPath]
const outputMode = args.output_mode ?? "files_with_matches" const outputMode = args.output_mode ?? "files_with_matches"
const headLimit = args.head_limit ?? 0 const headLimit = args.head_limit ?? 0
const cli = await resolveGrepCliWithAutoInstall()
if (outputMode === "count") { if (outputMode === "count") {
const results = await runRgCount({ const results = await runRgCount({
pattern: args.pattern, pattern: args.pattern,
paths, paths,
globs, globs,
}) }, cli)
const limited = headLimit > 0 ? results.slice(0, headLimit) : results const limited = headLimit > 0 ? results.slice(0, headLimit) : results
return formatCountResult(limited) return formatCountResult(limited)
} }
@@ -60,7 +62,7 @@ export function createGrepTools(ctx: PluginInput): Record<string, ToolDefinition
context: 0, context: 0,
outputMode, outputMode,
headLimit, headLimit,
}) }, cli)
return formatGrepResult(result) return formatGrepResult(result)
} catch (e) { } catch (e) {