Skip to content

Commit f737649

Browse files
0xMinkedelauna
andcommitted
feat(ripgrep): add Show Ripgrep Diagnostic command (#281)
* feat: add Zoo Code: Show Ripgrep Diagnostic command Adds a user-triggerable diagnostic for the "Could not find ripgrep binary" error class. The command runs the same hybrid resolution that #248 tested — try require("@vscode/ripgrep"), then probe known appRoot-relative paths — and writes a verbose report to a dedicated output channel, also copying it to the clipboard. Motivated by #248: debugging that bug required a custom test build to inspect resolution state on the user's machine. With this command shipped, future users hitting weird ripgrep-resolution behavior can paste the diagnostic into a bug report without us needing to build instrumented VSIXs. Reintroduces the @vscode/ripgrep devDep and esbuild external entry that #248 dropped — the diagnostic needs to call require() to report what happens. The require attempt also serves as forward- compat: when VS Code completes the @vscode/ripgrep → @vscode/ ripgrep-universal package-rename migration, it will start succeeding broadly, and the diagnostic output will be the signal that we can revisit the require approach in getBinPath itself. - src/services/ripgrep/diagnostic.ts: new module (data fn + cmd wrapper) - src/services/ripgrep/internal/loadRipgrep.ts: testable require wrapper - zoo-code.showRipgrepDiagnostic command wired up in registerCommands.ts and contributed via package.json - Tests for the data function * fix: address review feedback on ripgrep diagnostic command - diagnostic.ts: fix .asar->.asar.unpacked regex (the \b boundary was matching inside node_modules.asar.unpacked too, producing .unpacked.unpacked). Replaced with a path-separator lookahead. - diagnostic.ts: create the OutputChannel once at registration and return a composite Disposable that disposes both the command and the channel; clear the channel before appending so repeated runs are readable. - diagnostic.ts: route the command ID through getCommand() instead of hardcoding 'zoo-code.showRipgrepDiagnostic', and add 'showRipgrepDiagnostic' to the CommandId union in @roo-code/types. Exclude it from getCommandsMap so the diagnostic's separate registration owns the OutputChannel lifecycle. - package.json + package.nls.*.json: switch the command title to a %command.showRipgrepDiagnostic.title% NLS key across all 18 locale files. - loadRipgrep.ts: preserve the require() error message in a loadError field instead of swallowing it; surface it in the diagnostic report. - Tests updated for the loadError case, the already-unpacked path guard, and widened the mock value type. * test(ripgrep diagnostic): add Windows-path coverage + appRoot validation Two polish items from review: - getRipgrepDiagnostic now early-returns an explanatory message when vscode.env.appRoot is empty, instead of silently producing a report with a meaningless path probe. Closes the input- validation gap surfaced in re-reading the diagnostic. - New test exercises the .asar->.asar.unpacked substitution on Windows-style backslash paths. The regex already handles both separators via [\\/], this just pins it. * refactor(registerCommands): tighten command map value type Replace the residual `any` in the command-callback map value with a `CommandCallback` alias of `(...args: any[]) => unknown`. Mirrors VS Code's own `commands.registerCommand` signature while narrowing the return to `unknown` so callers must inspect before use. Rest-args stay `any[]` intentionally: the callbacks in this map are heterogeneous (`importSettings` takes an optional `filePath?: string`, the rest take none), and VS Code dispatches positional args dynamically — a single tight per-arity type would be lossy without splitting the map. * feat(ripgrep): add Show Ripgrep Diagnostic command and translations --------- Co-authored-by: 0xMink <260166390+0xMink@users.noreply.github.com> Co-authored-by: edelauna <54631123+edelauna@users.noreply.github.com> Co-authored-by: Elliott de Launay <edelauna@gmail.com>
1 parent 8cc2a4a commit f737649

28 files changed

Lines changed: 612 additions & 14 deletions

knip.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@types/node-cache",
2222
"@types/vscode",
2323
"@vscode/codicons",
24+
"@vscode/ripgrep",
2425
"esbuild-wasm",
2526
"sambanova-ai-provider",
2627
"tree-sitter-wasms",

packages/types/src/vscode.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export const commandIds = [
4646
"acceptInput",
4747
"focusPanel",
4848
"toggleAutoApprove",
49+
50+
"showRipgrepDiagnostic",
4951
] as const
5052

5153
export type CommandId = (typeof commandIds)[number]

src/activate/__tests__/registerCommands.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ vi.mock("../../i18n", () => ({
8181
t: (key: string) => key,
8282
}))
8383

84+
vi.mock("../../services/ripgrep/diagnostic", () => ({
85+
registerRipgrepDiagnosticCommand: vi.fn().mockReturnValue({ dispose: vi.fn() }),
86+
}))
87+
8488
describe("getVisibleProviderOrLog", () => {
8589
let mockOutputChannel: vscode.OutputChannel
8690

@@ -172,6 +176,14 @@ describe("registerCommands handlers", () => {
172176
setPanel(undefined, "tab")
173177
})
174178

179+
it("registers the ripgrep diagnostic command and stores its disposable in context.subscriptions", async () => {
180+
const { registerRipgrepDiagnosticCommand } = await import("../../services/ripgrep/diagnostic")
181+
const mock = vi.mocked(registerRipgrepDiagnosticCommand)
182+
const disposable = mock.mock.results[0]?.value
183+
expect(mock).toHaveBeenCalled()
184+
expect(mockContext.subscriptions).toContain(disposable)
185+
})
186+
175187
it("settingsButtonClicked posts both settingsButtonClicked and didBecomeVisible actions", () => {
176188
handlers["zoo-code.settingsButtonClicked"]()
177189

src/activate/registerCommands.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { handleNewTask } from "./handleTask"
1313
import { CodeIndexManager } from "../services/code-index/manager"
1414
import { importSettingsWithFeedback } from "../core/config/importExport"
1515
import { MdmService } from "../services/mdm/MdmService"
16+
import { registerRipgrepDiagnosticCommand } from "../services/ripgrep/diagnostic"
1617
import { t } from "../i18n"
1718

1819
/**
@@ -68,9 +69,27 @@ export const registerCommands = (options: RegisterCommandOptions) => {
6869
const command = getCommand(id as CommandId)
6970
context.subscriptions.push(vscode.commands.registerCommand(command, callback))
7071
}
72+
73+
context.subscriptions.push(registerRipgrepDiagnosticCommand())
7174
}
7275

73-
const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions): Record<CommandId, any> => ({
76+
// `showRipgrepDiagnostic` is registered separately by
77+
// `registerRipgrepDiagnosticCommand` (above), which owns the OutputChannel
78+
// lifecycle alongside the command registration, so it's intentionally
79+
// excluded from this map.
80+
//
81+
// Callback shape mirrors VS Code's own `commands.registerCommand` signature
82+
// (`(...args: any[]) => any`), with the return narrowed to `unknown` so
83+
// callers must inspect before using. `any[]` for args is unavoidable: the
84+
// callbacks here are heterogeneous (`importSettings` takes an optional
85+
// `filePath?: string`, others take none) and VS Code dispatches positional
86+
// args dynamically.
87+
type CommandCallback = (...args: any[]) => unknown
88+
const getCommandsMap = ({
89+
context,
90+
outputChannel,
91+
provider,
92+
}: RegisterCommandOptions): Record<Exclude<CommandId, "showRipgrepDiagnostic">, CommandCallback> => ({
7493
activationCompleted: () => {},
7594
plusButtonClicked: async () => {
7695
const visibleProvider = getVisibleProviderOrLog(outputChannel)

src/esbuild.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ async function main() {
126126
// global-agent must be external because it dynamically patches Node.js http/https modules
127127
// which breaks when bundled. It needs access to the actual Node.js module instances.
128128
// undici must be bundled because our VSIX is packaged with `--no-dependencies`.
129-
external: ["vscode", "esbuild", "global-agent"],
129+
external: ["vscode", "esbuild", "global-agent", "@vscode/ripgrep"],
130130
}
131131

132132
/**

src/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@
160160
"title": "%command.acceptInput.title%",
161161
"category": "%configuration.title%"
162162
},
163+
{
164+
"command": "zoo-code.showRipgrepDiagnostic",
165+
"title": "%command.showRipgrepDiagnostic.title%",
166+
"category": "%configuration.title%"
167+
},
163168
{
164169
"command": "zoo-code.toggleAutoApprove",
165170
"title": "%command.toggleAutoApprove.title%",

src/package.nls.ca.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/package.nls.de.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/package.nls.es.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/package.nls.fr.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)