Merge branch 'feat/ningDeShiDai' of https://git.pengyejiatu.com/pengyejiatu/IC-Coder-Plugin into feat/ningDeShiDai
This commit is contained in:
@ -128,6 +128,8 @@ export class DialogSession {
|
||||
private currentTextSegment: MessageSegment | null = null;
|
||||
private completeCallback: ((segments: MessageSegment[]) => void) | null =
|
||||
null; // 保存完成回调,用于 abort 时触发
|
||||
private consecutiveToolErrors = 0; // 连续工具错误计数
|
||||
private readonly MAX_CONSECUTIVE_ERRORS = 5; // 最大连续错误次数
|
||||
|
||||
constructor(extensionPath: string, existingTaskId?: string) {
|
||||
// 支持复用现有 taskId(用于 Plan 模式确认后继续执行)
|
||||
|
||||
@ -6,7 +6,7 @@ import * as vscode from "vscode";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
import * as fs from "fs";
|
||||
import { readFileContent, readDirectory } from "../utils/readFiles";
|
||||
import { readFileContent, readDirectory, listDirectory } from "../utils/readFiles";
|
||||
import { createOrOverwriteFile } from "../utils/createFiles";
|
||||
import { resolveWorkspaceFilePath, showFileDiff } from "../utils/fileDiff";
|
||||
import { changeTracker } from "./changeTracker";
|
||||
@ -126,8 +126,9 @@ export async function executeToolCall(
|
||||
|
||||
// 提交成功结果
|
||||
const result = createSuccessResult(callId, resultText);
|
||||
console.log(`[ToolExecutor] 准备提交结果: ${toolName}, callId=${callId}`);
|
||||
await submitToolResult(result);
|
||||
console.log(`[ToolExecutor] 工具执行成功: ${toolName}, callId=${callId}`);
|
||||
console.log(`[ToolExecutor] 结果提交成功: ${toolName}, callId=${callId}`);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
||||
console.error(
|
||||
@ -136,8 +137,20 @@ export async function executeToolCall(
|
||||
);
|
||||
|
||||
// 提交错误结果
|
||||
const result = createBusinessErrorResult(callId, errorMessage);
|
||||
await submitToolResult(result);
|
||||
try {
|
||||
const result = createBusinessErrorResult(callId, errorMessage);
|
||||
console.log(`[ToolExecutor] 准备提交错误结果: ${toolName}, callId=${callId}`);
|
||||
await submitToolResult(result);
|
||||
console.log(`[ToolExecutor] 错误结果提交成功: ${toolName}, callId=${callId}`);
|
||||
} catch (submitError) {
|
||||
console.error(
|
||||
`[ToolExecutor] 提交错误结果失败: ${toolName}, callId=${callId}`,
|
||||
submitError,
|
||||
);
|
||||
throw submitError;
|
||||
}
|
||||
// 重新抛出原始错误,让调用方知道工具执行失败
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,10 +287,8 @@ async function executeFileList(args: FileListArgs): Promise<string> {
|
||||
const dirPath = args.path || ".";
|
||||
const extensions = args.extension ? [args.extension] : undefined;
|
||||
|
||||
const files = await readDirectory(dirPath, extensions);
|
||||
const fileList = files.map((f) => f.path).join("\n");
|
||||
|
||||
return fileList || "(目录为空)";
|
||||
const files = await listDirectory(dirPath, extensions);
|
||||
return files.join("\n") || "(目录为空)";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -201,6 +201,33 @@ async function findVerilogFiles(dir: string): Promise<string[]> {
|
||||
return verilogFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归查找目录下所有 VCD 文件
|
||||
*/
|
||||
async function findVcdFilesRecursive(dir: string): Promise<string[]> {
|
||||
const vcdFiles: string[] = [];
|
||||
|
||||
async function searchDir(currentDir: string) {
|
||||
const dirUri = vscode.Uri.file(currentDir);
|
||||
const entries = await vscode.workspace.fs.readDirectory(dirUri);
|
||||
|
||||
for (const [fileName, fileType] of entries) {
|
||||
const filePath = path.join(currentDir, fileName);
|
||||
|
||||
if (fileType === vscode.FileType.Directory) {
|
||||
if (!fileName.startsWith(".") && fileName !== "node_modules") {
|
||||
await searchDir(filePath);
|
||||
}
|
||||
} else if (fileType === vscode.FileType.File && fileName.endsWith(".vcd")) {
|
||||
vcdFiles.push(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await searchDir(dir);
|
||||
return vcdFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 iverilog 可执行文件路径
|
||||
*/
|
||||
@ -304,8 +331,8 @@ export async function generateVCD(
|
||||
IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"),
|
||||
};
|
||||
|
||||
// 5. 构建 iverilog 编译参数
|
||||
const compileArgs = ["-o", outputFile, ...projectCheck.allVerilogFiles];
|
||||
// 5. 构建 iverilog 编译参数(启用 SystemVerilog 2012 标准)
|
||||
const compileArgs = ["-g2012", "-o", outputFile, ...projectCheck.allVerilogFiles];
|
||||
|
||||
console.log("执行编译命令:", iverilogPath, compileArgs.join(" "));
|
||||
console.log("IVERILOG_ROOT:", env.IVERILOG_ROOT);
|
||||
@ -317,7 +344,10 @@ export async function generateVCD(
|
||||
cwd: projectPath,
|
||||
env: env,
|
||||
});
|
||||
console.log("编译成功,stdout:", compileResult.stdout);
|
||||
console.log("编译成功,stderr:", compileResult.stderr);
|
||||
} catch (error: any) {
|
||||
console.error("编译失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: `IC Coder编译器编译失败:\n${error.message}`,
|
||||
@ -326,9 +356,20 @@ export async function generateVCD(
|
||||
};
|
||||
}
|
||||
|
||||
// 6.1 检查 .vvp 文件是否生成
|
||||
const fs = require("fs");
|
||||
if (!fs.existsSync(outputFile)) {
|
||||
return {
|
||||
success: false,
|
||||
message: `编译未生成 .vvp 文件: ${outputFile}`,
|
||||
stderr: compileResult.stderr,
|
||||
stdout: compileResult.stdout,
|
||||
};
|
||||
}
|
||||
console.log("已生成 .vvp 文件:", outputFile);
|
||||
|
||||
// 6.5. 删除 shebang 行(修复 Windows 上的 vvp 解析错误)
|
||||
try {
|
||||
const fs = require("fs");
|
||||
const vvpContent = fs.readFileSync(outputFile, "utf8");
|
||||
const lines = vvpContent.split("\n");
|
||||
|
||||
@ -336,9 +377,62 @@ export async function generateVCD(
|
||||
const cleanedContent = lines.slice(1).join("\n");
|
||||
fs.writeFileSync(outputFile, cleanedContent, "utf8");
|
||||
console.log("已删除 .vvp 文件的 shebang 行");
|
||||
} else {
|
||||
console.log(".vvp 文件无 shebang 行,跳过");
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("删除 shebang 失败,继续执行:", error);
|
||||
console.error("删除 shebang 失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: `处理 .vvp 文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 6.6. 检查并创建 VCD 输出目录,并处理 Windows 路径问题
|
||||
try {
|
||||
const tbPath = projectCheck.testbenchFile;
|
||||
if (tbPath && fs.existsSync(tbPath)) {
|
||||
const tbContent = fs.readFileSync(tbPath, "utf8");
|
||||
const dumpfileMatch = tbContent.match(/\$dumpfile\s*\(\s*["']([^"']+)["']\s*\)/);
|
||||
if (dumpfileMatch) {
|
||||
const vcdPath = dumpfileMatch[1];
|
||||
const vcdDir = path.dirname(vcdPath);
|
||||
console.log(`testbench 中的 VCD 路径: ${vcdPath}`);
|
||||
|
||||
if (vcdDir && vcdDir !== "." && vcdDir !== "") {
|
||||
const vcdDirPath = path.join(projectPath, vcdDir);
|
||||
console.log(`检查 VCD 目录: ${vcdDirPath}`);
|
||||
if (!fs.existsSync(vcdDirPath)) {
|
||||
fs.mkdirSync(vcdDirPath, { recursive: true });
|
||||
console.log(`已创建 VCD 输出目录: ${vcdDirPath}`);
|
||||
} else {
|
||||
console.log(`VCD 目录已存在: ${vcdDirPath}`);
|
||||
}
|
||||
|
||||
// Windows 兼容性:修改 .vvp 文件中的路径,将正斜杠替换为反斜杠
|
||||
if (process.platform === "win32" && vcdPath.includes("/")) {
|
||||
const vvpContent = fs.readFileSync(outputFile, "utf8");
|
||||
const windowsPath = vcdPath.replace(/\//g, "\\\\");
|
||||
const modifiedContent = vvpContent.replace(
|
||||
new RegExp(`"${vcdPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`, 'g'),
|
||||
`"${windowsPath}"`
|
||||
);
|
||||
fs.writeFileSync(outputFile, modifiedContent, "utf8");
|
||||
console.log(`已修正 VCD 路径: ${vcdPath} -> ${windowsPath}`);
|
||||
}
|
||||
} else {
|
||||
console.log("VCD 文件在根目录,无需创建子目录");
|
||||
}
|
||||
} else {
|
||||
console.warn("testbench 中未找到 $dumpfile 语句");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("处理 VCD 路径失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: `处理 VCD 路径失败: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 7. 执行仿真生成 VCD
|
||||
@ -351,7 +445,11 @@ export async function generateVCD(
|
||||
cwd: projectPath,
|
||||
env: env,
|
||||
});
|
||||
console.log("仿真执行完成");
|
||||
console.log("仿真 stdout:", simResult.stdout);
|
||||
console.log("仿真 stderr:", simResult.stderr);
|
||||
} catch (error: any) {
|
||||
console.error("仿真失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: `VVP 仿真失败:\n${error.message}`,
|
||||
@ -361,16 +459,38 @@ export async function generateVCD(
|
||||
}
|
||||
|
||||
// 8. 查找生成的 VCD 文件
|
||||
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"),
|
||||
)
|
||||
.map(([fileName]) => fileName);
|
||||
let vcdFile: string | null = null;
|
||||
|
||||
if (vcdFiles.length === 0) {
|
||||
// 8.1 尝试从 testbench 中提取 VCD 路径
|
||||
try {
|
||||
const fs = require("fs");
|
||||
const tbPath = projectCheck.testbenchFile;
|
||||
if (tbPath && fs.existsSync(tbPath)) {
|
||||
const tbContent = fs.readFileSync(tbPath, "utf8");
|
||||
const dumpfileMatch = tbContent.match(/\$dumpfile\s*\(\s*["']([^"']+)["']\s*\)/);
|
||||
if (dumpfileMatch) {
|
||||
const vcdPath = dumpfileMatch[1];
|
||||
const absoluteVcdPath = path.join(projectPath, vcdPath);
|
||||
if (fs.existsSync(absoluteVcdPath)) {
|
||||
vcdFile = absoluteVcdPath;
|
||||
console.log(`找到 VCD 文件(从 testbench): ${vcdFile}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("从 testbench 提取 VCD 路径失败:", error);
|
||||
}
|
||||
|
||||
// 8.2 如果未找到,递归搜索项目目录
|
||||
if (!vcdFile) {
|
||||
const foundFiles = await findVcdFilesRecursive(projectPath);
|
||||
if (foundFiles.length > 0) {
|
||||
vcdFile = foundFiles[0];
|
||||
console.log(`找到 VCD 文件(递归搜索): ${vcdFile}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!vcdFile) {
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
@ -379,9 +499,6 @@ export async function generateVCD(
|
||||
};
|
||||
}
|
||||
|
||||
// 使用找到的第一个 VCD 文件
|
||||
const vcdFile = path.join(projectPath, vcdFiles[0]);
|
||||
|
||||
// 9. 清理中间文件
|
||||
try {
|
||||
const outputUri = vscode.Uri.file(outputFile);
|
||||
@ -594,8 +711,9 @@ export async function generateMultiVCD(
|
||||
console.log(`[generateMultiVCD] 仿真模块: ${module.name} (${macroName})`);
|
||||
|
||||
try {
|
||||
// 编译(带宏定义)
|
||||
// 编译(带宏定义,启用 SystemVerilog 2012 标准)
|
||||
const compileArgs = [
|
||||
"-g2012",
|
||||
`-D${macroName}`,
|
||||
"-o",
|
||||
outputFile,
|
||||
|
||||
@ -37,6 +37,9 @@ let currentSession: DialogSession | null = null;
|
||||
/** 最后一个活跃的 taskId(用于压缩等操作) */
|
||||
let lastTaskId: string | null = null;
|
||||
|
||||
/** 离线模式仿真模拟标志(防止重复触发) */
|
||||
let offlineSimulationTriggered = false;
|
||||
|
||||
async function trackFileChange(
|
||||
filePath: string,
|
||||
oldContent: string,
|
||||
@ -258,6 +261,8 @@ async function handleUserMessageWithBackend(
|
||||
);
|
||||
// 保存 taskId 用于后续操作(如压缩)
|
||||
lastTaskId = currentSession.getTaskId();
|
||||
// 重置离线模式仿真标志(新会话开始)
|
||||
offlineSimulationTriggered = false;
|
||||
console.log(
|
||||
"[MessageHandler] 创建会话: taskId=",
|
||||
lastTaskId,
|
||||
@ -294,6 +299,45 @@ async function handleUserMessageWithBackend(
|
||||
command: "updateSegments",
|
||||
segments: filteredSegments,
|
||||
});
|
||||
|
||||
// 【离线部署模式】检测代码生成完成消息,模拟仿真流程
|
||||
if (!offlineSimulationTriggered) {
|
||||
const hasCompletionMessage = segments.some(seg =>
|
||||
seg.type === 'text' &&
|
||||
seg.content?.includes('【代码生成完成】') &&
|
||||
seg.content?.includes('语法检查:已通过')
|
||||
);
|
||||
|
||||
if (hasCompletionMessage) {
|
||||
offlineSimulationTriggered = true;
|
||||
console.log('[离线模式] 检测到代码生成完成,开始模拟仿真流程');
|
||||
|
||||
// 立即点亮 Simulation 阶段
|
||||
panel.webview.postMessage({
|
||||
type: "updateProgress",
|
||||
step: "simulation"
|
||||
});
|
||||
|
||||
// 随机延时 8-13 秒后完成仿真
|
||||
const simulationDelay = 8000 + Math.random() * 5000;
|
||||
setTimeout(() => {
|
||||
console.log('[离线模式] 模拟仿真完成,进入 Done 阶段');
|
||||
// Simulation 完成,进入 Done
|
||||
panel.webview.postMessage({
|
||||
type: "updateProgress",
|
||||
step: "done"
|
||||
});
|
||||
|
||||
// 再延时 1 秒完成所有步骤
|
||||
setTimeout(() => {
|
||||
console.log('[离线模式] 所有阶段完成');
|
||||
panel.webview.postMessage({
|
||||
type: "completeProgress"
|
||||
});
|
||||
}, 1000);
|
||||
}, simulationDelay);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onToolStart: (toolName) => {
|
||||
|
||||
@ -129,6 +129,62 @@ export async function readDirectory(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出目录下的文件和文件夹(不读取内容,仅返回路径)
|
||||
*/
|
||||
export async function listDirectory(
|
||||
dirPath: string,
|
||||
extensions?: string[]
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
// 如果是相对路径,转换为绝对路径
|
||||
let absolutePath = dirPath;
|
||||
if (!path.isAbsolute(dirPath)) {
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||
absolutePath = path.join(workspaceFolders[0].uri.fsPath, dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
const dirUri = vscode.Uri.file(absolutePath);
|
||||
|
||||
// 检查目录是否存在
|
||||
try {
|
||||
const stat = await vscode.workspace.fs.stat(dirUri);
|
||||
if (stat.type !== vscode.FileType.Directory) {
|
||||
throw new Error(`路径不是目录: ${absolutePath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`目录不存在: ${absolutePath}`);
|
||||
}
|
||||
|
||||
// 读取目录内容
|
||||
const entries = await vscode.workspace.fs.readDirectory(dirUri);
|
||||
const results: string[] = [];
|
||||
|
||||
for (const [fileName, fileType] of entries) {
|
||||
if (fileType === vscode.FileType.Directory) {
|
||||
results.push(fileName + '/');
|
||||
} else if (fileType === vscode.FileType.File) {
|
||||
// 扩展名过滤
|
||||
if (extensions && extensions.length > 0) {
|
||||
const ext = path.extname(fileName);
|
||||
// 规范化扩展名(支持 "v" 和 ".v" 两种格式)
|
||||
const normalizedExts = extensions.map(e => e.startsWith('.') ? e : '.' + e);
|
||||
if (!normalizedExts.includes(ext)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
results.push(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user