import type { ExtensionContext } from "@mariozechner/pi-coding-agent"; import { normalizeToolPath } from "./paths.js"; import type { RubyLspClient } from "./lsp-client.js"; type MinimalContext = Pick; type PathToolParams = { path: string; }; type PositionToolParams = { path: string; line: number; character: number; }; type ApplyableToolParams = { apply?: boolean }; export type CodeActionsParams = { path: string; startLine?: number; startCharacter?: number; endLine?: number; endCharacter?: number; kind?: string; apply?: boolean; actionIndex?: number; }; type RubyLspOperationsDeps = { getClient(ctx: MinimalContext, inputPath?: string): Promise; stopClient(root?: string): Promise; doctorFallback(ctx: MinimalContext, error: unknown): Promise; runDiagnostics(ctx: MinimalContext, inputPath: string): Promise<{ path: string; diagnostics: Array<{ severity?: number }> }>; resolveRailsRoot(ctx: MinimalContext): Promise; runRailsRoutes(cwd: string, filter?: string): Promise; }; export function createRubyLspOperations(deps: RubyLspOperationsDeps) { async function refreshDiagnosticsWhenApplied(ctx: MinimalContext, path: string, applied?: boolean) { if (applied) void deps.runDiagnostics(ctx, path).catch(() => undefined); } return { async doctor(ctx: MinimalContext) { try { const client = await deps.getClient(ctx); return client.doctor(); } catch (error) { return deps.doctorFallback(ctx, error); } }, async restart(ctx: MinimalContext) { await deps.stopClient(); const client = await deps.getClient(ctx); return client.doctor(); }, async stop() { await deps.stopClient(); }, async diagnostics(ctx: MinimalContext, inputPath: string) { return deps.runDiagnostics(ctx, inputPath); }, async symbols(ctx: MinimalContext, params: PathToolParams) { const path = normalizeToolPath(ctx.cwd, params.path); const client = await deps.getClient(ctx, params.path); const symbols = await client.documentSymbols(path); return { path, symbols }; }, async definition(ctx: MinimalContext, params: PositionToolParams) { const path = normalizeToolPath(ctx.cwd, params.path); const client = await deps.getClient(ctx, params.path); const locations = await client.definition(path, params.line, params.character); return { path, locations }; }, async hover(ctx: MinimalContext, params: PositionToolParams) { const path = normalizeToolPath(ctx.cwd, params.path); const client = await deps.getClient(ctx, params.path); const hover = await client.hover(path, params.line, params.character); return { path, hover }; }, async references(ctx: MinimalContext, params: PositionToolParams & { includeDeclaration?: boolean }) { const path = normalizeToolPath(ctx.cwd, params.path); const client = await deps.getClient(ctx, params.path); const references = await client.references(path, params.line, params.character, params.includeDeclaration ?? true); return { path, references }; }, async format(ctx: MinimalContext, params: PathToolParams & ApplyableToolParams) { const path = normalizeToolPath(ctx.cwd, params.path); const client = await deps.getClient(ctx, params.path); const result = await client.format(path, params.apply ?? false); await refreshDiagnosticsWhenApplied(ctx, path, result.applied); return result; }, async rename(ctx: MinimalContext, params: PositionToolParams & { newName: string } & ApplyableToolParams) { const path = normalizeToolPath(ctx.cwd, params.path); const client = await deps.getClient(ctx, params.path); const result = await client.rename(path, params.line, params.character, params.newName, params.apply ?? false); await refreshDiagnosticsWhenApplied(ctx, path, result.applied); return result; }, async codeActions(ctx: MinimalContext, params: CodeActionsParams) { const path = normalizeToolPath(ctx.cwd, params.path); const client = await deps.getClient(ctx, params.path); const actions = await client.codeActions(path, params, params.kind); const preview = actions.map((action, index) => ({ index, title: action.title, kind: action.kind, applyable: Boolean(action.edit), diagnostics: action.diagnostics?.length ?? 0, disabled: action.disabled, hasCommand: Boolean(action.command), })); if (params.apply && (params.actionIndex === undefined || !actions[params.actionIndex])) throw new Error(`Invalid or missing action index: ${params.actionIndex}`); const applied = params.apply && params.actionIndex !== undefined ? await client.applyCodeAction(actions[params.actionIndex]) : undefined; await refreshDiagnosticsWhenApplied(ctx, path, applied?.applied); return { path, actions: preview, applied }; }, async railsRoutes(ctx: MinimalContext, filter?: string) { const root = await deps.resolveRailsRoot(ctx); return deps.runRailsRoutes(root, filter); }, }; }