- 引入 i18n 系统,所有用户可见字符串替换为 t() 调用,支持中/EN/日三语 - 插件激活时读取 ai.outputLanguage 配置初始化语言,onDidChangeConfiguration 监听变更自动切换 - ReviewPanel 与 SetupViewProvider 注册 onLanguageChange 回调,语言切换时全量重渲染 - package.json description:"AI 输出语言" -> "插件语言" - 修复 repairJsonEscapes 无差别解引号导致 JSON 解析失败 - 设置页规则名称空值时输入框红框 + 错误提示 - 规则导入流程添加 withProgress 加载提示
188 lines
6.9 KiB
TypeScript
188 lines
6.9 KiB
TypeScript
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');
|
|
}
|
|
}
|
|
})
|
|
);
|
|
}
|