import * as vscode from 'vscode'; import { Orchestrator } from '../orchestrator/orchestrator'; import { runAIReview } from '../ai/engine'; import { loadActiveRules } from '../rules/yaml-parser'; import { filterAndSummarize } from '../rules/rule-filter'; import { mergeResults, MergedReport } from '../merger/merger'; import { reportToMarkdown } from '../utils/report'; import { getApiKey } from '../config'; import { ReviewPanel } from '../panel/webview'; import { t } from '../i18n/messages'; let currentReport: MergedReport | null = null; export function registerCommands( context: vscode.ExtensionContext, orchestrator: Orchestrator, ): void { context.subscriptions.push( vscode.commands.registerCommand('codeReviewer.review', async () => { const editor = vscode.window.activeTextEditor; if (!editor) { vscode.window.showWarningMessage(t('review.noEditor')); return; } const document = editor.document; const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ''; const workingDir = workspaceRoot || vscode.Uri.joinPath(document.uri, '..').fsPath; await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: t('review.running'), cancellable: false, }, async (progress) => { progress.report({ message: t('review.staticAnalysis') }); const startTime = Date.now(); const staticResult = await orchestrator.runStaticAnalysis(document, workingDir); progress.report({ message: t('review.aiReview') }); const allRules = loadActiveRules(workspaceRoot); const filterResult = filterAndSummarize(allRules, document); const code = document.getText(); const aiResult = await runAIReview(context, code, staticResult.diagnostics, filterResult.relevant); currentReport = mergeResults({ staticDiagnostics: staticResult.diagnostics, customRuleResults: aiResult.customRuleResults, translatedDiagnostics: aiResult.translatedDiagnostics, aiFindings: aiResult.findings, errors: [...staticResult.errors, ...(aiResult.error ? [aiResult.error] : [])], degraded: aiResult.degraded, startTime, filePath: document.uri.fsPath, language: document.languageId, adapterIds: staticResult.adapterIds, customRuleFilterInfo: { totalActive: allRules.length, injected: filterResult.relevant.length, filteredOut: filterResult.filteredOut.length, skippedRequestA: filterResult.skippedRequestA, }, }); const panel = ReviewPanel.createOrShow(context.extensionUri); panel.update(currentReport); }); }) ); context.subscriptions.push( vscode.commands.registerCommand('codeReviewer.reviewSelection', async () => { const editor = vscode.window.activeTextEditor; if (!editor) { return; } const selection = editor.selection; if (selection.isEmpty) { vscode.window.showWarningMessage(t('review.noSelection')); return; } const code = editor.document.getText(selection); const apiKey = await getApiKey(context); if (!apiKey) { vscode.window.showWarningMessage(t('review.needApiKey')); return; } const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ''; const customRules = loadActiveRules(workspaceRoot); await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: t('review.reviewingSelection'), cancellable: false, }, async () => { const aiResult = await runAIReview(context, code, [], customRules); vscode.window.showInformationMessage( t('review.selectionComplete', { 0: String(aiResult.customRuleResults.length + aiResult.findings.length) }) ); }); }) ); context.subscriptions.push( vscode.commands.registerCommand('codeReviewer.openPanel', () => { ReviewPanel.createOrShow(context.extensionUri); if (currentReport) { const panel = ReviewPanel.createOrShow(context.extensionUri); panel.update(currentReport); } }) ); context.subscriptions.push( vscode.commands.registerCommand('codeReviewer.exportReport', async () => { if (!currentReport) { vscode.window.showWarningMessage(t('export.needRunFirst')); return; } const markdown = reportToMarkdown(currentReport); const pick = await vscode.window.showQuickPick([ { label: t('export.copyToClipboard'), description: t('export.copyDescription') }, { label: t('export.downloadMarkdown'), description: t('export.saveDescription') }, ], { placeHolder: t('export.selectMethod') }); if (!pick) { return; } if (pick.label === t('export.copyToClipboard')) { await vscode.env.clipboard.writeText(markdown); vscode.window.showInformationMessage(t('export.copied')); } else { const fileName = currentReport.filePath.split(/[/\\]/).pop()?.replace(/\.[^.]+$/, '') ?? 'review-report'; const defaultUri = vscode.workspace.workspaceFolders?.[0] ? vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, `${fileName}-review.md`) : undefined; const uri = await vscode.window.showSaveDialog({ defaultUri, filters: { 'Markdown': ['md'] }, title: t('export.saveDialogTitle'), }); if (!uri) { return; } await vscode.workspace.fs.writeFile(uri, Buffer.from(markdown, 'utf-8')); vscode.window.showInformationMessage(t('export.saved', { 0: uri.fsPath })); } }) ); context.subscriptions.push( vscode.commands.registerCommand('codeReviewer.addCustomRule', () => { const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; if (!workspaceRoot) { vscode.window.showWarningMessage(t('setup.noWorkspace')); return; } vscode.window.showInformationMessage(t('setup.manageRulesHint')); }) ); context.subscriptions.push( vscode.commands.registerCommand('codeReviewer.fixIssue', () => { vscode.window.showInformationMessage(t('review.fixNotAvailable')); }) ); context.subscriptions.push( vscode.commands.registerCommand('codeReviewer.fixAll', () => { vscode.window.showInformationMessage(t('review.fixAllNotAvailable')); }) ); context.subscriptions.push( vscode.commands.registerCommand('codeReviewer.openSetup', async () => { try { await vscode.commands.executeCommand('workbench.view.extension.code-reviewer'); } catch { const action = await vscode.window.showErrorMessage( t('setup.openSetupFail'), t('setup.openSettingsJson') ); if (action === t('setup.openSettingsJson')) { await vscode.commands.executeCommand('workbench.action.openSettingsJson'); } } }) ); }