Files
2026Technology-Competition/src/activation/commands.ts
T
范智鹏 ff32ecf5c2 i18n 国际化全量接入 + 修复与 UI 增强
- 引入 i18n 系统,所有用户可见字符串替换为 t() 调用,支持中/EN/日三语
- 插件激活时读取 ai.outputLanguage 配置初始化语言,onDidChangeConfiguration 监听变更自动切换
- ReviewPanel 与 SetupViewProvider 注册 onLanguageChange 回调,语言切换时全量重渲染
- package.json description:"AI 输出语言" -> "插件语言"
- 修复 repairJsonEscapes 无差别解引号导致 JSON 解析失败
- 设置页规则名称空值时输入框红框 + 错误提示
- 规则导入流程添加 withProgress 加载提示
2026-07-26 15:44:26 +08:00

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');
}
}
})
);
}