14 Commits

Author SHA1 Message Date
7cde4fa138 refactor: 优化代码格式和用户提示
- 统一代码格式化(Prettier)
- 将 iverilog 相关错误提示改为 'IC Coder编译器'
- 优化后端服务错误提示为 '当前访问人数过多,请稍后重试'
- 修复代码风格一致性问题
2026-03-09 11:10:56 +08:00
1b7259d1c1 feat:排除打包项目中的waveform_trace文件中的无关文档 2026-03-09 10:41:17 +08:00
09ff812562 feat:修复 Windows vvp 解析问题
- 修复 iverilog 生成的 .vvp 文件 shebang 导致 Windows 解析失败
2026-03-07 18:41:42 +08:00
e7c631d532 feat: 优化文档结构
- 将文档移至 docs/ 目录统一管理
   - 更新 .vscodeignore 排除规则
2026-03-07 18:41:14 +08:00
06573e37d7 feat: 优化 webpack 打包配置
- 添加自动模式切换(开发/生产)
   - 启用 Tree Shaking 移除未使用代码
   - 加快编译速度(transpileOnly)
   - 添加打包体积监控
   - 自动清理旧文件
   - 添加打包优化文档
2026-03-06 18:27:56 +08:00
d740f4da44 feat: 支持文件路径标签带行号点击跳转
- 前端解析 file.v:1-2 格式,提取文件名和行号
   - 新增 openFilePathTag 命令,支持智能文件查找
   - 修复模板字符串中正则表达式转义问题
   - 不影响现有 openFile 和 diff 功能
2026-03-06 16:24:21 +08:00
f24bd38ec7 feat: 优化上下文项显示和识别逻辑
- 支持显示所有类型的上下文项(文件和代码片段)
   - 增强路径识别,支持代码片段格式(文件名:行号-行号)
2026-03-06 15:40:59 +08:00
45934baf0a feat: 添加上下文项点击功能
- 文件类型可点击打开文件
   - 代码片段可点击打开文件并选中对应代码
   - 文件夹类型不可点击
2026-03-06 10:13:27 +08:00
4384ee53c5 fix: 修复关闭面板后快捷键无法自动打开面板的问题
- 通过 try-catch 检测 webview 是否真正可用
   - 修复 panel._isDisposed 检测不准确的问题
   - 增加异常捕获防止发送消息时崩溃
   - 延长消息发送延迟至 500ms 确保面板加载完成
2026-03-06 10:05:52 +08:00
d89c326be5 Merge branch 'feat/DeleteConfirmation' into feat/codeToChat 2026-03-06 09:16:12 +08:00
2dccb4f871 update:changelog.md 2026-03-06 09:11:33 +08:00
a9ddf3074e 1.0.12 2026-03-06 09:10:51 +08:00
db087bb184 update:更新changelog.md 2026-03-06 09:10:09 +08:00
5e9083041f fix: 修复多选问题提交后选中项不显示高亮的问题 2026-03-06 09:08:38 +08:00
16 changed files with 505 additions and 193 deletions

View File

@ -18,10 +18,15 @@ node_modules/**
# 文档(避免中文文件名打包问题)
docs/**
PUBLISH.md
CLAUDE.md
# 只排除 waveform_trace 的 src/dist 目录
tools/waveform_trace/src/dist/**
tools/waveform_trace/src/**
tools/iverilog/examples/**
tools/iverilog/INSTALL.md
tools/iverilog/README.md
tools/iverilog/DOWNLOAD_INSTRUCTIONS.md
# Git 相关
.git/**

View File

@ -2,6 +2,12 @@
所有重要的项目变更都将记录在此文件中。
## [1.0.12] - 2026-03-06
### 新增
- 支持 AskUserQuestion 多问题和多选功能
## [1.0.9] - 2026-03-04
### 优化

View File

@ -0,0 +1,55 @@
# Webpack 打包优化说明
## 优化内容
### 1. 自动模式切换
- 开发模式:保持源码可读性
- 生产模式:自动压缩代码
### 2. 性能优化
- **Tree Shaking**:移除未使用的代码
- **transpileOnly**:跳过类型检查,加快编译速度
- **自动清理**:每次打包自动删除旧文件
### 3. 体积监控
- 单文件超过 2MB 会发出警告
- 帮助及时发现打包体积问题
## 使用方法
### 开发模式
```bash
# 编译(不压缩)
pnpm run compile
# 监听模式(自动重新编译)
pnpm run watch
```
### 生产模式
```bash
# Windows
set NODE_ENV=production && pnpm run package
# macOS/Linux
NODE_ENV=production pnpm run package
```
## 打包结果
- **输出目录**`dist/`
- **入口文件**`dist/extension.js`
- **静态资源**`dist/assets/`
## 性能对比
| 模式 | 体积 | 编译速度 | Source Map |
|------|------|----------|------------|
| 开发 | 较大 | 快 | 完整 |
| 生产 | 小 | 较慢 | 隐藏 |
## 注意事项
1. 开发时使用 `pnpm run watch`,修改代码自动重新编译
2. 发布前必须使用生产模式打包
3. 如果打包体积超过 2MB检查是否引入了不必要的依赖

View File

@ -2,7 +2,7 @@
"name": "iccoder",
"displayName": "IC Coder: Agentic Verilog Platform",
"description": "Agentic Verilog Coding Platform for Real-World FPGAs",
"version": "1.0.11",
"version": "1.0.12",
"publisher": "ICCoderAgenticVerilogPlatform",
"engines": {
"vscode": "^1.80.0"

View File

@ -286,8 +286,12 @@ export async function activate(context: vscode.ExtensionContext) {
const addCodeToChat = vscode.commands.registerCommand(
"ic-coder.addCodeToChat",
async () => {
console.log('[addCodeToChat] 命令触发');
const editor = vscode.window.activeTextEditor;
if (!editor) return;
if (!editor) {
console.log('[addCodeToChat] 没有活动编辑器');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
@ -303,24 +307,53 @@ export async function activate(context: vscode.ExtensionContext) {
// 检查是否已有打开的面板
let panel = (global as any).currentICHelperPanel;
if (!panel || panel._isDisposed) {
let needCreatePanel = false;
if (!panel) {
needCreatePanel = true;
} else {
// 尝试访问 webview如果抛出异常说明已销毁
try {
const _ = panel.webview;
} catch (e) {
needCreatePanel = true;
}
}
console.log('[addCodeToChat] 需要创建面板:', needCreatePanel);
if (needCreatePanel) {
console.log('[addCodeToChat] 正在打开面板...');
await showICHelperPanel(context);
panel = (global as any).currentICHelperPanel;
console.log('[addCodeToChat] 面板打开后状态:', panel ? '成功' : '失败');
// 如果面板仍未创建(如未登录),直接返回
if (!panel) {
console.log('[addCodeToChat] 面板创建失败,退出');
return;
}
}
// 发送代码上下文
console.log('[addCodeToChat] 准备发送代码到面板');
setTimeout(() => {
if (panel?.webview) {
panel.webview.postMessage({
command: 'addCodeContext',
fileName,
startLine,
endLine,
code: selectedText,
languageId: editor.document.languageId
});
try {
if (panel?.webview) {
console.log('[addCodeToChat] 发送 addCodeContext 消息');
panel.webview.postMessage({
command: 'addCodeContext',
fileName,
startLine,
endLine,
code: selectedText,
languageId: editor.document.languageId
});
}
} catch (e) {
console.log('[addCodeToChat] 发送消息失败:', e);
}
}, 300);
}, 500);
}
);

View File

@ -494,6 +494,78 @@ export async function showICHelperPanel(
// 退出登录
vscode.commands.executeCommand("ic-coder.logout");
break;
case "openFile":
// 打开文件
if (message.filePath) {
const path = require('path');
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const fullPath = path.isAbsolute(message.filePath) || !workspaceFolder
? message.filePath
: vscode.Uri.joinPath(workspaceFolder.uri, message.filePath).fsPath;
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc);
});
}
break;
case "openFileWithSelection":
// 打开文件并选中代码
if (message.filePath) {
const path = require('path');
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const fullPath = path.isAbsolute(message.filePath) || !workspaceFolder
? message.filePath
: vscode.Uri.joinPath(workspaceFolder.uri, message.filePath).fsPath;
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc).then(editor => {
const start = new vscode.Position(message.startLine - 1, 0);
const end = new vscode.Position(message.endLine - 1, doc.lineAt(message.endLine - 1).text.length);
editor.selection = new vscode.Selection(start, end);
editor.revealRange(new vscode.Range(start, end));
});
});
}
break;
case "openFilePathTag":
// 打开文件路径标签(智能查找)
if (message.filePath) {
const path = require('path');
const fs = require('fs');
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
let fullPath = message.filePath;
// 如果是相对路径且工作区存在
if (!path.isAbsolute(message.filePath) && workspaceFolder) {
const candidatePath = vscode.Uri.joinPath(workspaceFolder.uri, message.filePath).fsPath;
// 检查文件是否存在
if (fs.existsSync(candidatePath)) {
fullPath = candidatePath;
} else {
// 尝试在工作区中搜索该文件
const fileName = path.basename(message.filePath);
const files = await vscode.workspace.findFiles(`**/${fileName}`, '**/node_modules/**', 1);
if (files.length > 0) {
fullPath = files[0].fsPath;
}
}
}
if (message.startLine && message.endLine) {
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc).then(editor => {
const start = new vscode.Position(message.startLine - 1, 0);
const end = new vscode.Position(message.endLine - 1, doc.lineAt(message.endLine - 1).text.length);
editor.selection = new vscode.Selection(start, end);
editor.revealRange(new vscode.Range(start, end));
});
});
} else {
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc);
});
}
}
break;
case "acceptChange":
// 采纳变更
if (message.changeId) {

View File

@ -291,7 +291,7 @@ async function executeSyntaxCheck(
// 检查 iverilog 是否可用
const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
if (!iverilogCheck.available) {
throw new Error(`iverilog 不可用: ${iverilogCheck.message}`);
throw new Error(`IC Coder编译器不可用: ${iverilogCheck.message}`);
}
// 创建临时文件
@ -372,7 +372,7 @@ async function executeIverilog(
// 检查 iverilog 是否可用
const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
if (!iverilogCheck.available) {
throw new Error(`iverilog 不可用: ${iverilogCheck.message}`);
throw new Error(`IC Coder编译器不可用: ${iverilogCheck.message}`);
}
// 获取工作目录

View File

@ -7,7 +7,7 @@ import { promisify } from "util";
function execCommand(
command: string,
args: string[],
options: { cwd: string; env?: any }
options: { cwd: string; env?: any },
): Promise<{ stdout: string; stderr: string }> {
return new Promise((resolve, reject) => {
// 在 Windows 上,如果路径包含空格,不使用 shell直接用 spawn
@ -23,25 +23,25 @@ function execCommand(
let stderr = "";
// 在 Windows 上使用 GBK 编码解码输出
const encoding = process.platform === 'win32' ? 'gbk' : 'utf8';
const encoding = process.platform === "win32" ? "gbk" : "utf8";
child.stdout.on("data", (data) => {
try {
// 尝试使用 iconv-lite 解码(如果可用)
const iconv = require('iconv-lite');
const iconv = require("iconv-lite");
stdout += iconv.decode(data, encoding);
} catch {
// 如果 iconv-lite 不可用,使用默认解码
stdout += data.toString('utf8');
stdout += data.toString("utf8");
}
});
child.stderr.on("data", (data) => {
try {
const iconv = require('iconv-lite');
const iconv = require("iconv-lite");
stderr += iconv.decode(data, encoding);
} catch {
stderr += data.toString('utf8');
stderr += data.toString("utf8");
}
});
@ -93,7 +93,7 @@ export interface VCDGenerationResult {
* 检查项目中的 Verilog 文件完整性
*/
export async function checkVerilogProject(
projectPath: string
projectPath: string,
): Promise<VerilogProjectCheck> {
const result: VerilogProjectCheck = {
isComplete: false,
@ -164,7 +164,7 @@ export async function checkVerilogProject(
return result;
} catch (error) {
result.errors.push(
`检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}`
`检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}`,
);
return result;
}
@ -209,12 +209,30 @@ async function getIverilogPath(extensionPath: string): Promise<string> {
let iverilogBin = "";
if (platform === "win32") {
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog.exe");
iverilogBin = path.join(
extensionPath,
"tools",
"iverilog",
"bin",
"iverilog.exe",
);
} else if (platform === "darwin") {
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog");
iverilogBin = path.join(
extensionPath,
"tools",
"iverilog",
"bin",
"iverilog",
);
} else {
// Linux
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog");
iverilogBin = path.join(
extensionPath,
"tools",
"iverilog",
"bin",
"iverilog",
);
}
// 如果插件包中没有,尝试使用系统安装的 iverilog
@ -258,7 +276,7 @@ async function getVvpPath(extensionPath: string): Promise<string> {
*/
export async function generateVCD(
projectPath: string,
extensionPath: string
extensionPath: string,
): Promise<VCDGenerationResult> {
try {
// 1. 检查项目完整性
@ -302,12 +320,27 @@ export async function generateVCD(
} catch (error: any) {
return {
success: false,
message: `iverilog 编译失败:\n${error.message}`,
message: `IC Coder编译器编译失败:\n${error.message}`,
stderr: error.stderr,
stdout: error.stdout,
};
}
// 6.5. 删除 shebang 行(修复 Windows 上的 vvp 解析错误)
try {
const fs = require("fs");
const vvpContent = fs.readFileSync(outputFile, "utf8");
const lines = vvpContent.split("\n");
if (lines.length > 0 && lines[0].startsWith("#!")) {
const cleanedContent = lines.slice(1).join("\n");
fs.writeFileSync(outputFile, cleanedContent, "utf8");
console.log("已删除 .vvp 文件的 shebang 行");
}
} catch (error) {
console.warn("删除 shebang 失败,继续执行:", error);
}
// 7. 执行仿真生成 VCD
const simArgs = [outputFile];
console.log("执行仿真命令:", vvpPath, simArgs.join(" "));
@ -331,13 +364,17 @@ export async function generateVCD(
const projectUri = vscode.Uri.file(projectPath);
const entries = await vscode.workspace.fs.readDirectory(projectUri);
const vcdFiles = entries
.filter(([fileName, fileType]) => fileType === vscode.FileType.File && fileName.endsWith('.vcd'))
.filter(
([fileName, fileType]) =>
fileType === vscode.FileType.File && fileName.endsWith(".vcd"),
)
.map(([fileName]) => fileName);
if (vcdFiles.length === 0) {
return {
success: false,
message: "VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。",
message:
"VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。",
stdout: simResult.stdout,
};
}
@ -373,7 +410,7 @@ export async function generateVCD(
* 检查 iverilog 是否可用
*/
export async function checkIverilogAvailable(
extensionPath: string
extensionPath: string,
): Promise<{ available: boolean; version?: string; message: string }> {
try {
const iverilogPath = await getIverilogPath(extensionPath);
@ -385,7 +422,7 @@ export async function checkIverilogAvailable(
} catch (error) {
return {
available: false,
message: `iverilog 不可用。未找到文件: ${iverilogPath}`,
message: `IC Coder编译器不可用。未找到文件: ${iverilogPath}`,
};
}
@ -404,12 +441,12 @@ export async function checkIverilogAvailable(
return {
available: true,
version: version,
message: `iverilog 可用: ${version}`,
message: `IC Coder编译器可用: ${version}`,
};
} catch (error: any) {
return {
available: false,
message: `iverilog 执行失败: ${error.message}\n${error.stderr || ""}`,
message: `IC Coder编译器执行失败: ${error.message}\n${error.stderr || ""}`,
};
}
}
@ -418,8 +455,8 @@ export async function checkIverilogAvailable(
* 要 dump 的模块定义
*/
export interface DumpModule {
name: string; // 模块名(用于 VCD 文件名和宏名)
path: string; // 实例路径(如 dut.u_tx
name: string; // 模块名(用于 VCD 文件名和宏名)
path: string; // 实例路径(如 dut.u_tx
}
/**
@ -444,10 +481,11 @@ export interface MultiVCDResult {
function injectConditionalDump(
tbContent: string,
dumpModules: DumpModule[],
vcdDir: string
vcdDir: string,
): string {
// 匹配 $dumpfile 和 $dumpvars 语句(可能跨多行)
const dumpPattern = /(\$dumpfile\s*\([^)]+\)\s*;[\s\S]*?\$dumpvars\s*\([^)]+\)\s*;)/g;
const dumpPattern =
/(\$dumpfile\s*\([^)]+\)\s*;[\s\S]*?\$dumpvars\s*\([^)]+\)\s*;)/g;
// 生成条件编译代码
const conditionalCode = generateConditionalDumpCode(dumpModules, vcdDir);
@ -469,7 +507,7 @@ function injectConditionalDump(
*/
function generateConditionalDumpCode(
dumpModules: DumpModule[],
vcdDir: string
vcdDir: string,
): string {
if (dumpModules.length === 0) {
return '$dumpfile("output.vcd");\n $dumpvars(0, dut);';
@ -480,7 +518,7 @@ function generateConditionalDumpCode(
dumpModules.forEach((module, index) => {
const macroName = `DUMP_${module.name.toUpperCase()}`;
const vcdPath = `${vcdDir}/${module.name}.vcd`;
const directive = index === 0 ? '`ifdef' : '`elsif';
const directive = index === 0 ? "`ifdef" : "`elsif";
lines.push(`${directive} ${macroName}`);
lines.push(` $dumpfile("${vcdPath}");`);
@ -488,12 +526,12 @@ function generateConditionalDumpCode(
});
// 添加默认分支(使用第一个模块)
lines.push('`else');
lines.push("`else");
lines.push(` $dumpfile("${vcdDir}/${dumpModules[0].name}.vcd");`);
lines.push(` $dumpvars(1, ${dumpModules[0].path});`);
lines.push('`endif');
lines.push("`endif");
return lines.join('\n');
return lines.join("\n");
}
/**
@ -504,10 +542,10 @@ export async function generateMultiVCD(
extensionPath: string,
tbPath: string,
dumpModules: DumpModule[],
vcdDir: string = 'vcd'
vcdDir: string = "vcd",
): Promise<MultiVCDResult> {
const results: MultiVCDResult['vcdFiles'] = [];
let allStdout = '';
const results: MultiVCDResult["vcdFiles"] = [];
let allStdout = "";
try {
// 1. 创建 vcd 目录
@ -520,16 +558,21 @@ export async function generateMultiVCD(
}
// 2. 读取原始 testbench
const tbFullPath = path.isAbsolute(tbPath) ? tbPath : path.join(projectPath, tbPath);
const tbFullPath = path.isAbsolute(tbPath)
? tbPath
: path.join(projectPath, tbPath);
const tbUri = vscode.Uri.file(tbFullPath);
const tbBytes = await vscode.workspace.fs.readFile(tbUri);
const originalTb = Buffer.from(tbBytes).toString('utf-8');
const originalTb = Buffer.from(tbBytes).toString("utf-8");
// 3. 注入条件编译代码
const modifiedTb = injectConditionalDump(originalTb, dumpModules, vcdDir);
await vscode.workspace.fs.writeFile(tbUri, Buffer.from(modifiedTb, 'utf-8'));
await vscode.workspace.fs.writeFile(
tbUri,
Buffer.from(modifiedTb, "utf-8"),
);
console.log('[generateMultiVCD] Testbench 已修改,开始多次仿真...');
console.log("[generateMultiVCD] Testbench 已修改,开始多次仿真...");
// 4. 获取工具路径
const iverilogPath = await getIverilogPath(extensionPath);
@ -554,27 +597,34 @@ export async function generateMultiVCD(
// 编译(带宏定义)
const compileArgs = [
`-D${macroName}`,
"-o", outputFile,
...projectCheck.allVerilogFiles
"-o",
outputFile,
...projectCheck.allVerilogFiles,
];
await execCommand(iverilogPath, compileArgs, { cwd: projectPath, env });
// 仿真
const simResult = await execCommand(vvpPath, [outputFile], { cwd: projectPath, env });
const simResult = await execCommand(vvpPath, [outputFile], {
cwd: projectPath,
env,
});
allStdout += `\n[${module.name}] ${simResult.stdout}`;
results.push({
moduleName: module.name,
vcdPath: vcdPath,
success: true
success: true,
});
} catch (error: any) {
console.error(`[generateMultiVCD] 模块 ${module.name} 仿真失败:`, error.message);
console.error(
`[generateMultiVCD] 模块 ${module.name} 仿真失败:`,
error.message,
);
results.push({
moduleName: module.name,
vcdPath: vcdPath,
success: false,
error: error.message
error: error.message,
});
// 继续执行其他模块
}
@ -587,19 +637,18 @@ export async function generateMultiVCD(
// 忽略
}
const successCount = results.filter(r => r.success).length;
const successCount = results.filter((r) => r.success).length;
return {
success: successCount > 0,
vcdFiles: results,
message: `生成完成:${successCount}/${dumpModules.length} 个 VCD 文件成功`,
stdout: allStdout
stdout: allStdout,
};
} catch (error) {
return {
success: false,
vcdFiles: results,
message: `生成多 VCD 文件失败: ${error instanceof Error ? error.message : '未知错误'}`
message: `生成多 VCD 文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
};
}
}

View File

@ -41,7 +41,11 @@ let currentSession: DialogSession | null = null;
/** 最后一个活跃的 taskId用于压缩等操作 */
let lastTaskId: string | null = null;
async function trackFileChange(filePath: string, oldContent: string, newContent: string): Promise<void> {
async function trackFileChange(
filePath: string,
oldContent: string,
newContent: string,
): Promise<void> {
try {
changeTracker.trackChange(filePath, oldContent, newContent);
} catch (error) {
@ -58,7 +62,7 @@ export async function handleUserMessage(
extensionPath?: string,
mode?: RunMode,
serviceTier?: ServiceTier, // 服务等级参数
contextItems?: Array<{ id: number; type: string; path: string }> // 上下文项参数
contextItems?: Array<{ id: number; type: string; path: string }>, // 上下文项参数
) {
console.log("收到用户消息:", text);
@ -68,7 +72,9 @@ export async function handleUserMessage(
// 从 session 中获取 token
let token: string | undefined;
try {
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
const session = await vscode.authentication.getSession("iccoder", [], {
createIfNone: false,
});
token = session?.accessToken;
} catch (error) {
console.warn("[MessageHandler] 获取 session 失败:", error);
@ -78,20 +84,20 @@ export async function handleUserMessage(
console.warn("[MessageHandler] 未登录,阻止发送");
// 保存待发送的消息
await context.globalState.update('pendingMessage', {
await context.globalState.update("pendingMessage", {
text,
mode,
serviceTier,
timestamp: Date.now()
timestamp: Date.now(),
});
// 显示弹窗提示
const action = await vscode.window.showWarningMessage(
'请先登录后再发送消息',
'立即登录'
"请先登录后再发送消息",
"立即登录",
);
if (action === '立即登录') {
if (action === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true,
});
@ -110,24 +116,24 @@ export async function handleUserMessage(
console.warn("[MessageHandler] Token 已过期,阻止发送");
// 保存待发送的消息
await context.globalState.update('pendingMessage', {
await context.globalState.update("pendingMessage", {
text,
mode,
serviceTier,
timestamp: Date.now()
timestamp: Date.now(),
});
// 清除过期的 session
await context.globalState.update('icCoderSessions', []);
await context.globalState.update('icCoderUserInfo', undefined);
await context.globalState.update("icCoderSessions", []);
await context.globalState.update("icCoderUserInfo", undefined);
// 显示弹窗提示
const action = await vscode.window.showWarningMessage(
'登录已过期,请重新登录',
'立即登录'
"登录已过期,请重新登录",
"立即登录",
);
if (action === '立即登录') {
if (action === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true,
});
@ -190,11 +196,11 @@ export async function handleUserMessage(
// 显示错误提示
const selection = await vscode.window.showWarningMessage(
balanceCheck.message || "资源点余额不足",
"去充值"
"去充值",
);
if (selection === "去充值") {
vscode.env.openExternal(
vscode.Uri.parse("https://iccoder.com/memberCenter")
vscode.Uri.parse("https://iccoder.com/memberCenter"),
);
}
// 恢复输入状态
@ -216,14 +222,14 @@ export async function handleUserMessage(
mode,
undefined,
serviceTier,
contextItems
contextItems,
);
return;
} catch (error) {
console.error("后端服务不可用:", error);
console.error("当前访问人数过多,请稍后重试:", error);
panel.webview.postMessage({
command: "updateStatus",
text: "后端服务不可用",
text: "当前访问人数过多,请稍后重试",
type: "error",
});
// 恢复输入状态
@ -254,7 +260,7 @@ async function handleUserMessageWithBackend(
mode?: RunMode,
reuseTaskId?: string, // 可选,复用现有 taskId用于 Plan 模式确认后继续执行)
serviceTier?: ServiceTier, // 服务等级参数
contextItems?: Array<{ id: number; type: string; path: string }> // 上下文项参数
contextItems?: Array<{ id: number; type: string; path: string }>, // 上下文项参数
): Promise<void> {
const historyManager = ChatHistoryManager.getInstance();
@ -262,7 +268,7 @@ async function handleUserMessageWithBackend(
let enhancedText = text;
if (contextItems && contextItems.length > 0) {
console.log("[MessageHandler] 处理上下文项:", contextItems.length);
const paths = contextItems.map(item => item.path).join('\n');
const paths = contextItems.map((item) => item.path).join("\n");
enhancedText = `${paths}\n\n${text}`;
}
@ -273,7 +279,7 @@ async function handleUserMessageWithBackend(
// 创建会话dialogManager 会自动处理旧会话的中止)
currentSession = dialogManager.createSession(
extensionPath,
taskIdToUse || undefined
taskIdToUse || undefined,
);
// 保存 taskId 用于后续操作(如压缩)
lastTaskId = currentSession.getTaskId();
@ -281,7 +287,7 @@ async function handleUserMessageWithBackend(
"[MessageHandler] 创建会话: taskId=",
lastTaskId,
"来源=",
taskIdToUse ? "historyManager" : "新生成"
taskIdToUse ? "historyManager" : "新生成",
);
// 显示状态栏
@ -324,7 +330,10 @@ async function handleUserMessageWithBackend(
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
},
onQuestion: (askId: string, questions: import("../types/api").QuestionItem[]) => {
onQuestion: (
askId: string,
questions: import("../types/api").QuestionItem[],
) => {
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
panel.webview.postMessage({
command: "updateStatus",
@ -379,18 +388,21 @@ async function handleUserMessageWithBackend(
// 发送系统通知 - AI 响应完成
const notificationService = NotificationService.getInstance();
notificationService.success(
'IC Coder - AI 响应完成',
'您的问题已得到回复,点击查看详情',
"IC Coder - AI 响应完成",
"您的问题已得到回复,点击查看详情",
() => {
// 点击通知时聚焦到面板
panel.reveal();
}
},
);
// 发送代码变更到前端
sendChangesToWebview(panel);
} catch (error) {
console.warn("[MessageHandler] 更新面板失败(面板可能已关闭):", error);
console.warn(
"[MessageHandler] 更新面板失败(面板可能已关闭):",
error,
);
}
resolve();
@ -458,7 +470,7 @@ async function handleUserMessageWithBackend(
},
},
mode,
serviceTier // 传递服务等级
serviceTier, // 传递服务等级
);
});
}
@ -470,7 +482,7 @@ export async function handleUserAnswer(
askId: string,
selected?: string[],
customInput?: string,
answers?: { [questionIndex: string]: string[] }
answers?: { [questionIndex: string]: string[] },
): Promise<void> {
if (currentSession) {
await currentSession.submitAnswer(askId, selected, customInput, answers);
@ -540,7 +552,7 @@ export async function handlePlanAction(
action: string,
planTitle: string,
extensionPath: string,
serviceTier?: ServiceTier
serviceTier?: ServiceTier,
): Promise<void> {
console.log(
"[handlePlanAction] action:",
@ -548,7 +560,7 @@ export async function handlePlanAction(
"planTitle:",
planTitle,
"serviceTier:",
serviceTier
serviceTier,
);
switch (action) {
@ -564,7 +576,7 @@ export async function handlePlanAction(
`请按照刚才的计划执行:${planTitle}`,
extensionPath,
"agent",
serviceTier
serviceTier,
);
break;
@ -581,7 +593,7 @@ export async function handlePlanAction(
`请根据以下建议修改计划:${modification}`,
extensionPath,
"plan",
serviceTier
serviceTier,
);
}
break;
@ -636,7 +648,7 @@ function parseFileOperation(text: string): {
// 匹配重命名文件:将 xxx.ts 重命名为 yyy.ts 或 把 xxx.ts 改名为 yyy.ts优先匹配避免被修改匹配
const renameMatch = lowerText.match(
/(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/
/(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/,
);
if (renameMatch) {
const oldPath = renameMatch[1].trim();
@ -653,7 +665,7 @@ function parseFileOperation(text: string): {
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
// 格式3: 将 xxx.ts 文件 'aaa' 替换为 'bbb'
const replaceMatch1 = lowerText.match(
/在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/
/在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/,
);
if (replaceMatch1) {
const filePath = replaceMatch1[1].trim();
@ -669,7 +681,7 @@ function parseFileOperation(text: string): {
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
const replaceMatch2 = lowerText.match(
/(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/
/(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/,
);
if (replaceMatch2) {
const filePath = replaceMatch2[1].trim();
@ -718,7 +730,7 @@ async function handleFileOperation(
newPath?: string;
searchText?: string;
replaceText?: string;
}
},
) {
const historyManager = ChatHistoryManager.getInstance();
@ -734,7 +746,7 @@ async function handleFileOperation(
text: responseText,
});
vscode.window.showInformationMessage(
`文件创建成功: ${operation.filePath}`
`文件创建成功: ${operation.filePath}`,
);
await historyManager.addAiMessage(responseText);
break;
@ -747,7 +759,7 @@ async function handleFileOperation(
text: responseText,
});
vscode.window.showInformationMessage(
`文件删除成功: ${operation.filePath}`
`文件删除成功: ${operation.filePath}`,
);
await historyManager.addAiMessage(responseText);
break;
@ -783,7 +795,7 @@ async function handleFileOperation(
text: responseText,
});
vscode.window.showInformationMessage(
`文件重命名成功: ${operation.filePath}${operation.newPath}`
`文件重命名成功: ${operation.filePath}${operation.newPath}`,
);
await historyManager.addAiMessage(responseText);
break;
@ -792,21 +804,29 @@ async function handleFileOperation(
if (!operation.searchText || !operation.replaceText) {
throw new Error("缺少替换内容");
}
const oldContentBeforeReplace = await readFileContent(operation.filePath);
const oldContentBeforeReplace = await readFileContent(
operation.filePath,
);
await replaceFile(
operation.filePath,
operation.searchText,
operation.replaceText
operation.replaceText,
);
const newContentAfterReplace = await readFileContent(
operation.filePath,
);
await trackFileChange(
operation.filePath,
oldContentBeforeReplace,
newContentAfterReplace,
);
const newContentAfterReplace = await readFileContent(operation.filePath);
await trackFileChange(operation.filePath, oldContentBeforeReplace, newContentAfterReplace);
responseText = `✅ 文件内容替换成功: ${operation.filePath}`;
panel.webview.postMessage({
command: "receiveMessage",
text: responseText,
});
vscode.window.showInformationMessage(
`文件内容替换成功: ${operation.filePath}`
`文件内容替换成功: ${operation.filePath}`,
);
await historyManager.addAiMessage(responseText);
break;
@ -866,7 +886,7 @@ function getDefaultContent(filePath: string): string {
*/
export async function handleReadFile(
panel: vscode.WebviewPanel,
filePath: string
filePath: string,
) {
try {
const content = await readFileContent(filePath);
@ -890,7 +910,7 @@ export async function handleCreateFile(
panel: vscode.WebviewPanel,
filePath: string,
content: string,
overwrite: boolean = false //是否覆盖
overwrite: boolean = false, //是否覆盖
) {
try {
if (overwrite) {
@ -909,11 +929,14 @@ export async function handleCreateFile(
// 发送系统通知
const notificationService = NotificationService.getInstance();
notificationService.success(
'IC Coder - 文件创建',
"IC Coder - 文件创建",
`文件已创建: ${path.basename(filePath)}`,
() => {
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath));
}
vscode.commands.executeCommand(
"vscode.open",
vscode.Uri.file(filePath),
);
},
);
} catch (error) {
panel.webview.postMessage({
@ -921,7 +944,7 @@ export async function handleCreateFile(
error: error instanceof Error ? error.message : "创建文件失败",
});
vscode.window.showErrorMessage(
`创建文件失败: ${error instanceof Error ? error.message : "未知错误"}`
`创建文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
);
}
}
@ -932,7 +955,7 @@ export async function handleCreateFile(
export async function handleUpdateFile(
panel: vscode.WebviewPanel,
filePath: string,
content: string
content: string,
) {
try {
const oldContent = await readFileContent(filePath);
@ -948,8 +971,8 @@ export async function handleUpdateFile(
// 发送系统通知
const notificationService = NotificationService.getInstance();
notificationService.info(
'IC Coder - 文件更新',
`文件已更新: ${path.basename(filePath)}`
"IC Coder - 文件更新",
`文件已更新: ${path.basename(filePath)}`,
);
} catch (error) {
panel.webview.postMessage({
@ -957,7 +980,7 @@ export async function handleUpdateFile(
error: error instanceof Error ? error.message : "更新文件失败",
});
vscode.window.showErrorMessage(
`更新文件失败: ${error instanceof Error ? error.message : "未知错误"}`
`更新文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
);
}
}
@ -968,7 +991,7 @@ export async function handleUpdateFile(
export async function handleRenameFile(
panel: vscode.WebviewPanel,
oldPath: string,
newPath: string
newPath: string,
) {
try {
await renameFile(oldPath, newPath);
@ -979,7 +1002,7 @@ export async function handleRenameFile(
message: "文件重命名成功",
});
vscode.window.showInformationMessage(
`文件重命名成功: ${oldPath}${newPath}`
`文件重命名成功: ${oldPath}${newPath}`,
);
} catch (error) {
panel.webview.postMessage({
@ -987,7 +1010,7 @@ export async function handleRenameFile(
error: error instanceof Error ? error.message : "重命名文件失败",
});
vscode.window.showErrorMessage(
`重命名文件失败: ${error instanceof Error ? error.message : "未知错误"}`
`重命名文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
);
}
}
@ -999,7 +1022,7 @@ export async function handleReplaceInFile(
panel: vscode.WebviewPanel,
filePath: string,
searchText: string,
replaceText: string
replaceText: string,
) {
try {
const oldContent = await readFileContent(filePath);
@ -1018,7 +1041,7 @@ export async function handleReplaceInFile(
error: error instanceof Error ? error.message : "替换文件内容失败",
});
vscode.window.showErrorMessage(
`替换文件内容失败: ${error instanceof Error ? error.message : "未知错误"}`
`替换文件内容失败: ${error instanceof Error ? error.message : "未知错误"}`,
);
}
}
@ -1063,7 +1086,7 @@ function isVCDGenerationCommand(text: string): boolean {
*/
async function handleVCDGeneration(
panel: vscode.WebviewPanel,
extensionPath: string
extensionPath: string,
) {
try {
// 获取当前工作区路径
@ -1090,7 +1113,7 @@ async function handleVCDGeneration(
if (!iverilogCheck.available) {
panel.webview.postMessage({
command: "receiveMessage",
text: `${iverilogCheck.message}\n\n请参考插件文档安装 iverilog 工具`,
text: `${iverilogCheck.message}`,
});
vscode.window.showErrorMessage(iverilogCheck.message);
return;
@ -1165,12 +1188,15 @@ async function handleVCDGeneration(
// 发送系统通知
const notificationService = NotificationService.getInstance();
notificationService.success(
'IC Coder - 仿真完成',
"IC Coder - 仿真完成",
`VCD 文件已生成: ${fileName}`,
() => {
// 点击通知时打开 VCD 查看器
vscode.commands.executeCommand('ic-coder.openVCDViewer', result.vcdFilePath);
}
vscode.commands.executeCommand(
"ic-coder.openVCDViewer",
result.vcdFilePath,
);
},
);
} else {
panel.webview.postMessage({
@ -1199,12 +1225,12 @@ async function handleVCDGeneration(
// 发送系统通知
const notificationService = NotificationService.getInstance();
notificationService.error(
'IC Coder - 仿真失败',
'VCD 文件生成失败,请查看错误信息',
"IC Coder - 仿真失败",
"VCD 文件生成失败,请查看错误信息",
() => {
// 点击通知时聚焦到面板
panel.reveal();
}
},
);
}
} catch (error) {
@ -1222,11 +1248,11 @@ async function handleVCDGeneration(
// 发送系统通知
const notificationService = NotificationService.getInstance();
notificationService.error(
'IC Coder - 仿真错误',
error instanceof Error ? error.message : '生成 VCD 文件时出错',
"IC Coder - 仿真错误",
error instanceof Error ? error.message : "生成 VCD 文件时出错",
() => {
panel.reveal();
}
},
);
}
}
@ -1236,7 +1262,7 @@ async function handleVCDGeneration(
*/
export async function handleOptimizePrompt(
panel: vscode.WebviewPanel,
prompt: string
prompt: string,
): Promise<void> {
console.log("[MessageHandler] ========== 收到提示词优化请求 ==========");
console.log("[MessageHandler] prompt:", prompt);
@ -1268,7 +1294,7 @@ export async function handleOptimizePrompt(
*/
export async function handleAcceptChange(
panel: vscode.WebviewPanel,
changeId: string
changeId: string,
) {
try {
const success = await changeTracker.acceptChange(changeId);
@ -1276,14 +1302,14 @@ export async function handleAcceptChange(
panel.webview.postMessage({
command: "changeAccepted",
changeId: changeId,
success: true
success: true,
});
} else {
panel.webview.postMessage({
command: "changeAccepted",
changeId: changeId,
success: false,
error: "采纳变更失败"
error: "采纳变更失败",
});
}
} catch (error) {
@ -1292,7 +1318,7 @@ export async function handleAcceptChange(
command: "changeAccepted",
changeId: changeId,
success: false,
error: String(error)
error: String(error),
});
}
}
@ -1302,7 +1328,7 @@ export async function handleAcceptChange(
*/
export async function handleRejectChange(
panel: vscode.WebviewPanel,
changeId: string
changeId: string,
) {
try {
const success = await changeTracker.rejectChange(changeId);
@ -1310,14 +1336,14 @@ export async function handleRejectChange(
panel.webview.postMessage({
command: "changeRejected",
changeId: changeId,
success: true
success: true,
});
} else {
panel.webview.postMessage({
command: "changeRejected",
changeId: changeId,
success: false,
error: "拒绝变更失败"
error: "拒绝变更失败",
});
}
} catch (error) {
@ -1326,7 +1352,7 @@ export async function handleRejectChange(
command: "changeRejected",
changeId: changeId,
success: false,
error: String(error)
error: String(error),
});
}
}
@ -1337,18 +1363,18 @@ export async function handleRejectChange(
export function sendChangesToWebview(panel: vscode.WebviewPanel) {
const session = changeTracker.endSession();
if (session && session.changes.length > 0) {
const changesWithDiff = session.changes.map(change => {
const changesWithDiff = session.changes.map((change) => {
const diffLines = generateDiff(change.oldContent, change.newContent);
const diffHtml = renderDiffHtml(diffLines);
return {
...change,
diffHtml
diffHtml,
};
});
panel.webview.postMessage({
command: "showChanges",
changes: changesWithDiff
changes: changesWithDiff,
});
}
}
@ -1365,62 +1391,67 @@ export function startChangeSession(sessionId: string) {
*/
export async function handleOpenFileDiff(
panel: vscode.WebviewPanel,
changeId: string
changeId: string,
) {
try {
const session = changeTracker.getCurrentSession();
if (!session) {
vscode.window.showErrorMessage('没有找到变更会话');
vscode.window.showErrorMessage("没有找到变更会话");
return;
}
const change = session.changes.find(c => c.changeId === changeId);
const change = session.changes.find((c) => c.changeId === changeId);
if (!change) {
vscode.window.showErrorMessage('没有找到该变更');
vscode.window.showErrorMessage("没有找到该变更");
return;
}
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
vscode.window.showErrorMessage('没有打开的工作区');
vscode.window.showErrorMessage("没有打开的工作区");
return;
}
// 创建临时文件用于对比
const filePath = change.filePath;
const absolutePath = vscode.Uri.file(
path.join(workspaceFolder.uri.fsPath, filePath)
path.join(workspaceFolder.uri.fsPath, filePath),
);
// 创建虚拟文档显示旧内容
const oldUri = vscode.Uri.parse(
`ic-coder-diff:${filePath}.old?${changeId}`
).with({ scheme: 'ic-coder-diff' });
`ic-coder-diff:${filePath}.old?${changeId}`,
).with({ scheme: "ic-coder-diff" });
// 注册文档内容提供者(如果还没注册)
if (!(global as any).__diffProviderRegistered) {
const provider = new (class implements vscode.TextDocumentContentProvider {
const provider = new (class
implements vscode.TextDocumentContentProvider
{
provideTextDocumentContent(uri: vscode.Uri): string {
const changeId = uri.query;
const session = changeTracker.getCurrentSession();
const change = session?.changes.find(c => c.changeId === changeId);
return change?.oldContent || '';
const change = session?.changes.find((c) => c.changeId === changeId);
return change?.oldContent || "";
}
})();
vscode.workspace.registerTextDocumentContentProvider('ic-coder-diff', provider);
vscode.workspace.registerTextDocumentContentProvider(
"ic-coder-diff",
provider,
);
(global as any).__diffProviderRegistered = true;
}
// 打开 diff 编辑器
await vscode.commands.executeCommand(
'vscode.diff',
"vscode.diff",
oldUri,
absolutePath,
`${filePath} (变更对比)`
`${filePath} (变更对比)`,
);
} catch (error) {
console.error('[MessageHandler] 打开 diff 失败:', error);
console.error("[MessageHandler] 打开 diff 失败:", error);
vscode.window.showErrorMessage(`打开 diff 失败: ${error}`);
}
}

View File

@ -51,7 +51,11 @@ export function getContextDisplayStyles(): string {
transition: all 0.2s ease;
}
.context-item:hover {
.context-item.clickable {
cursor: pointer;
}
.context-item.clickable:hover {
background: var(--vscode-list-hoverBackground);
}
@ -180,11 +184,14 @@ export function getContextDisplayScript(): string {
case 'code': icon = getCodeIcon(); break;
}
const clickable = item.type !== 'folder' ? 'clickable' : '';
const onclick = item.type !== 'folder' ? \`onclick="window.handleContextItemClick(\${item.id})"\` : '';
return \`
<div class="context-item" title="\${item.path || item.displayPath}">
<div class="context-item \${clickable}" title="\${item.path || item.displayPath}" \${onclick}>
\${icon}
<span class="context-item-name">\${item.displayPath || getFileName(item.path)}</span>
<span class="context-item-remove" onclick="removeContextItem(\${item.id})">
<span class="context-item-remove" onclick="event.stopPropagation(); removeContextItem(\${item.id})">
\${getRemoveIcon()}
</span>
</div>
@ -192,6 +199,27 @@ export function getContextDisplayScript(): string {
}).join('');
}
// 全局访问函数
window.handleContextItemClick = function(id) {
const item = contextItems.find(i => i.id === id);
if (!item || item.type === 'folder') return;
if (item.type === 'code') {
const codeData = JSON.parse(item.path);
vscode.postMessage({
command: 'openFileWithSelection',
filePath: codeData.fileName,
startLine: codeData.startLine,
endLine: codeData.endLine
});
} else {
vscode.postMessage({
command: 'openFile',
filePath: item.path
});
}
};
// 处理后端返回的文件选择结果
window.addEventListener('message', event => {
const message = event.data;

View File

@ -46,10 +46,21 @@ export function getFilePathTagScript(): string {
return `
// 处理文件路径标签点击
function handleFilePathClick(filePath) {
vscode.postMessage({
command: 'openFile',
filePath: filePath
});
// 解析文件路径,支持 file.v:5-8 格式
const match = filePath.match(/^(.+?):(\\d+)-(\\d+)$/);
if (match) {
vscode.postMessage({
command: 'openFilePathTag',
filePath: match[1],
startLine: parseInt(match[2]),
endLine: parseInt(match[3])
});
} else {
vscode.postMessage({
command: 'openFilePathTag',
filePath: filePath
});
}
}
// 创建文件路径标签

View File

@ -432,15 +432,14 @@ export function getInputAreaScript(): string {
// 获取上下文项
const contextItems = window.getContextItems ? window.getContextItems() : [];
// 构建显示消息:如果有上下文文件,添加文件路径前缀
// 构建显示消息:如果有上下文,添加路径前缀
let displayText = text;
if (contextItems.length > 0) {
const filePaths = contextItems
.filter(item => item.type === 'file')
const contextPaths = contextItems
.map(item => item.displayPath || item.path)
.join(' ');
if (filePaths) {
displayText = filePaths + ' ' + text;
if (contextPaths) {
displayText = contextPaths + ' ' + text;
}
}

View File

@ -863,8 +863,8 @@ export function getMessageAreaScript(): string {
const textParts = [];
parts.forEach(part => {
// 判断是否为文件路径:包含路径分隔符文件扩展名
if (part.includes('/') || part.includes('\\\\') || /\\.[a-zA-Z0-9]+$/.test(part)) {
// 判断是否为文件路径或代码片段:包含路径分隔符文件扩展名或代码片段格式(文件名:行号-行号)
if (part.includes('/') || part.includes('\\\\') || /\\.[a-zA-Z0-9]+$/.test(part) || /:[0-9]+-[0-9]+$/.test(part)) {
filePaths.push(part);
} else {
textParts.push(part);
@ -1211,7 +1211,7 @@ export function getMessageAreaScript(): string {
const optionsHtml = q.options.map(opt => {
const isSelected = selectedAnswers.includes(opt);
return \`<label style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 0;">
return \`<label class="question-option\${isSelected ? ' selected' : ''}" style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 0;">
<input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}>
<span>\${opt}</span>
</label>\`;
@ -1729,9 +1729,18 @@ export function getMessageAreaScript(): string {
// 标记问题已回答
segmentDiv.classList.add('answered');
// 禁用所有输入
// 禁用所有输入并保持选中状态的高亮
const inputs = segmentDiv.querySelectorAll('input');
inputs.forEach(input => input.disabled = true);
inputs.forEach(input => {
input.disabled = true;
// 确保选中的选项保持高亮
if (input.checked) {
const label = input.closest('.question-option');
if (label) {
label.classList.add('selected');
}
}
});
// 隐藏提交按钮
const submitBtn = segmentDiv.querySelector('.custom-submit');

View File

@ -10,24 +10,23 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
/** @type WebpackConfig */
const extensionConfig = {
target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
target: 'node',
mode: process.env.NODE_ENV === 'production' ? 'production' : 'none',
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
entry: './src/extension.ts',
output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
libraryTarget: 'commonjs2'
libraryTarget: 'commonjs2',
clean: true // 自动清理旧文件
},
externals: {
vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
'node-notifier': 'commonjs node-notifier' // node-notifier 依赖原生模块,必须排除
// modules added here also need to be added in the .vscodeignore file
vscode: 'commonjs vscode',
'node-notifier': 'commonjs node-notifier'
},
resolve: {
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
extensions: ['.ts', '.js']
extensions: ['.ts', '.js'],
mainFields: ['module', 'main']
},
module: {
rules: [
@ -36,15 +35,21 @@ const extensionConfig = {
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
loader: 'ts-loader',
options: {
transpileOnly: true, // 加快编译速度
compilerOptions: {
sourceMap: true
}
}
}
]
}
]
},
devtool: 'nosources-source-map',
devtool: process.env.NODE_ENV === 'production' ? 'hidden-source-map' : 'nosources-source-map',
infrastructureLogging: {
level: "log", // enables logging required for problem matchers
level: "log",
},
plugins: [
new CopyWebpackPlugin({
@ -52,6 +57,15 @@ const extensionConfig = {
{ from: 'src/assets', to: 'assets' }
]
})
]
],
optimization: {
minimize: process.env.NODE_ENV === 'production',
usedExports: true // Tree Shaking
},
performance: {
hints: 'warning',
maxAssetSize: 2 * 1024 * 1024, // 2MB
maxEntrypointSize: 2 * 1024 * 1024
}
};
module.exports = [ extensionConfig ];