feat:实现删除文件确认功能
This commit is contained in:
@ -2,23 +2,31 @@
|
|||||||
* 工具执行器
|
* 工具执行器
|
||||||
* 接收后端的 tool_call 事件,执行本地工具,返回结果
|
* 接收后端的 tool_call 事件,执行本地工具,返回结果
|
||||||
*/
|
*/
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from "vscode";
|
||||||
import * as path from 'path';
|
import * as path from "path";
|
||||||
import * as os from 'os';
|
import * as os from "os";
|
||||||
import * as fs from 'fs';
|
import * as fs from "fs";
|
||||||
import { readFileContent, readDirectory } from '../utils/readFiles';
|
import { readFileContent, readDirectory } from "../utils/readFiles";
|
||||||
import { createOrOverwriteFile } from '../utils/createFiles';
|
import { createOrOverwriteFile } from "../utils/createFiles";
|
||||||
import { resolveWorkspaceFilePath, showFileDiff } from '../utils/fileDiff';
|
import { resolveWorkspaceFilePath, showFileDiff } from "../utils/fileDiff";
|
||||||
import { changeTracker } from './changeTracker';
|
import { changeTracker } from "./changeTracker";
|
||||||
import { generateVCD, checkIverilogAvailable, generateMultiVCD, DumpModule } from '../utils/iverilogRunner';
|
import {
|
||||||
import { analyzeVcdFile } from '../utils/vcdParser';
|
generateVCD,
|
||||||
import { executeWaveformTrace, WaveformTraceArgs } from '../utils/waveformTracer';
|
checkIverilogAvailable,
|
||||||
|
generateMultiVCD,
|
||||||
|
DumpModule,
|
||||||
|
} from "../utils/iverilogRunner";
|
||||||
|
import { analyzeVcdFile } from "../utils/vcdParser";
|
||||||
|
import {
|
||||||
|
executeWaveformTrace,
|
||||||
|
WaveformTraceArgs,
|
||||||
|
} from "../utils/waveformTracer";
|
||||||
import {
|
import {
|
||||||
submitToolResult,
|
submitToolResult,
|
||||||
createSuccessResult,
|
createSuccessResult,
|
||||||
createBusinessErrorResult,
|
createBusinessErrorResult,
|
||||||
createSystemErrorResult
|
createSystemErrorResult,
|
||||||
} from './apiClient';
|
} from "./apiClient";
|
||||||
import type {
|
import type {
|
||||||
ToolCallRequest,
|
ToolCallRequest,
|
||||||
ToolName,
|
ToolName,
|
||||||
@ -31,8 +39,8 @@ import type {
|
|||||||
SimulationArgs,
|
SimulationArgs,
|
||||||
WaveformSummaryArgs,
|
WaveformSummaryArgs,
|
||||||
KnowledgeSaveArgs,
|
KnowledgeSaveArgs,
|
||||||
KnowledgeLoadArgs
|
KnowledgeLoadArgs,
|
||||||
} from '../types/api';
|
} from "../types/api";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具执行器上下文
|
* 工具执行器上下文
|
||||||
@ -51,7 +59,7 @@ export interface ToolExecutorContext {
|
|||||||
*/
|
*/
|
||||||
export async function executeToolCall(
|
export async function executeToolCall(
|
||||||
request: ToolCallRequest,
|
request: ToolCallRequest,
|
||||||
context: ToolExecutorContext
|
context: ToolExecutorContext,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const toolName = request.params.name as ToolName;
|
const toolName = request.params.name as ToolName;
|
||||||
const args = request.params.arguments;
|
const args = request.params.arguments;
|
||||||
@ -63,37 +71,53 @@ export async function executeToolCall(
|
|||||||
let resultText: string;
|
let resultText: string;
|
||||||
|
|
||||||
switch (toolName) {
|
switch (toolName) {
|
||||||
case 'file_read':
|
case "file_read":
|
||||||
resultText = await executeFileRead(args as unknown as FileReadArgs);
|
resultText = await executeFileRead(args as unknown as FileReadArgs);
|
||||||
break;
|
break;
|
||||||
case 'file_write':
|
case "file_write":
|
||||||
resultText = await executeFileWrite(args as unknown as FileWriteArgs);
|
resultText = await executeFileWrite(args as unknown as FileWriteArgs);
|
||||||
break;
|
break;
|
||||||
case 'file_delete':
|
case "file_delete":
|
||||||
resultText = await executeFileDelete(args as unknown as FileDeleteArgs);
|
resultText = await executeFileDelete(args as unknown as FileDeleteArgs);
|
||||||
break;
|
break;
|
||||||
case 'file_list':
|
case "file_list":
|
||||||
resultText = await executeFileList(args as unknown as FileListArgs);
|
resultText = await executeFileList(args as unknown as FileListArgs);
|
||||||
break;
|
break;
|
||||||
case 'syntax_check':
|
case "syntax_check":
|
||||||
resultText = await executeSyntaxCheck(args as unknown as SyntaxCheckArgs, context);
|
resultText = await executeSyntaxCheck(
|
||||||
|
args as unknown as SyntaxCheckArgs,
|
||||||
|
context,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'iverilog':
|
case "iverilog":
|
||||||
resultText = await executeIverilog(args as unknown as IverilogArgs, context);
|
resultText = await executeIverilog(
|
||||||
|
args as unknown as IverilogArgs,
|
||||||
|
context,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'simulation':
|
case "simulation":
|
||||||
resultText = await executeSimulation(args as unknown as SimulationArgs, context);
|
resultText = await executeSimulation(
|
||||||
|
args as unknown as SimulationArgs,
|
||||||
|
context,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'waveform_summary':
|
case "waveform_summary":
|
||||||
resultText = await executeWaveformSummary(args as unknown as WaveformSummaryArgs);
|
resultText = await executeWaveformSummary(
|
||||||
|
args as unknown as WaveformSummaryArgs,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'waveform_trace':
|
case "waveform_trace":
|
||||||
resultText = await executeWaveformTrace(args as unknown as WaveformTraceArgs, context);
|
resultText = await executeWaveformTrace(
|
||||||
|
args as unknown as WaveformTraceArgs,
|
||||||
|
context,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'knowledge_save':
|
case "knowledge_save":
|
||||||
resultText = await executeKnowledgeSave(args as unknown as KnowledgeSaveArgs);
|
resultText = await executeKnowledgeSave(
|
||||||
|
args as unknown as KnowledgeSaveArgs,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'knowledge_load':
|
case "knowledge_load":
|
||||||
resultText = await executeKnowledgeLoad();
|
resultText = await executeKnowledgeLoad();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -104,10 +128,12 @@ export async function executeToolCall(
|
|||||||
const result = createSuccessResult(callId, resultText);
|
const result = createSuccessResult(callId, resultText);
|
||||||
await submitToolResult(result);
|
await submitToolResult(result);
|
||||||
console.log(`[ToolExecutor] 工具执行成功: ${toolName}, callId=${callId}`);
|
console.log(`[ToolExecutor] 工具执行成功: ${toolName}, callId=${callId}`);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : '未知错误';
|
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
||||||
console.error(`[ToolExecutor] 工具执行失败: ${toolName}, callId=${callId}`, error);
|
console.error(
|
||||||
|
`[ToolExecutor] 工具执行失败: ${toolName}, callId=${callId}`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
|
||||||
// 提交错误结果
|
// 提交错误结果
|
||||||
const result = createBusinessErrorResult(callId, errorMessage);
|
const result = createBusinessErrorResult(callId, errorMessage);
|
||||||
@ -129,7 +155,7 @@ async function executeFileRead(args: FileReadArgs): Promise<string> {
|
|||||||
async function executeFileWrite(args: FileWriteArgs): Promise<string> {
|
async function executeFileWrite(args: FileWriteArgs): Promise<string> {
|
||||||
const absolutePath = resolveWorkspaceFilePath(args.path);
|
const absolutePath = resolveWorkspaceFilePath(args.path);
|
||||||
const existedBeforeWrite = fs.existsSync(absolutePath);
|
const existedBeforeWrite = fs.existsSync(absolutePath);
|
||||||
const oldContent = existedBeforeWrite ? await readFileContent(args.path) : '';
|
const oldContent = existedBeforeWrite ? await readFileContent(args.path) : "";
|
||||||
|
|
||||||
await createOrOverwriteFile(args.path, args.content);
|
await createOrOverwriteFile(args.path, args.content);
|
||||||
|
|
||||||
@ -137,11 +163,11 @@ async function executeFileWrite(args: FileWriteArgs): Promise<string> {
|
|||||||
try {
|
try {
|
||||||
changeTracker.trackChange(args.path, oldContent, args.content);
|
changeTracker.trackChange(args.path, oldContent, args.content);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[ToolExecutor] 记录文件变更失败:', error);
|
console.warn("[ToolExecutor] 记录文件变更失败:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verilog 文件添加知识图谱提示
|
// Verilog 文件添加知识图谱提示
|
||||||
const isVerilogFile = args.path.endsWith('.v') || args.path.endsWith('.sv');
|
const isVerilogFile = args.path.endsWith(".v") || args.path.endsWith(".sv");
|
||||||
if (isVerilogFile) {
|
if (isVerilogFile) {
|
||||||
return `文件已写入: ${args.path}\n\n[提示] 如有新信号或规则,请更新知识图谱`;
|
return `文件已写入: ${args.path}\n\n[提示] 如有新信号或规则,请更新知识图谱`;
|
||||||
}
|
}
|
||||||
@ -151,7 +177,7 @@ async function executeFileWrite(args: FileWriteArgs): Promise<string> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行 file_delete 工具
|
* 执行 file_delete 工具
|
||||||
* 删除指定路径的文件
|
* 删除指定路径的文件(带用户确认)
|
||||||
*/
|
*/
|
||||||
async function executeFileDelete(args: FileDeleteArgs): Promise<string> {
|
async function executeFileDelete(args: FileDeleteArgs): Promise<string> {
|
||||||
const filePath = args.path;
|
const filePath = args.path;
|
||||||
@ -159,7 +185,7 @@ async function executeFileDelete(args: FileDeleteArgs): Promise<string> {
|
|||||||
// 获取工作区路径
|
// 获取工作区路径
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||||
throw new Error('请先打开一个工作区');
|
throw new Error("请先打开一个工作区");
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspacePath = workspaceFolders[0].uri.fsPath;
|
const workspacePath = workspaceFolders[0].uri.fsPath;
|
||||||
@ -180,18 +206,60 @@ async function executeFileDelete(args: FileDeleteArgs): Promise<string> {
|
|||||||
throw new Error(`不能删除目录,请指定文件路径: ${filePath}`);
|
throw new Error(`不能删除目录,请指定文件路径: ${filePath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证文件路径在工作区内
|
||||||
|
const isInWorkspace = workspaceFolders.some((folder) =>
|
||||||
|
absolutePath.startsWith(folder.uri.fsPath),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isInWorkspace) {
|
||||||
|
throw new Error("只能删除工作区内的文件");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保护敏感文件
|
||||||
|
const protectedFiles = [
|
||||||
|
"package.json",
|
||||||
|
"tsconfig.json",
|
||||||
|
".git",
|
||||||
|
"node_modules",
|
||||||
|
];
|
||||||
|
|
||||||
|
const fileName = path.basename(absolutePath);
|
||||||
|
if (protectedFiles.includes(fileName)) {
|
||||||
|
throw new Error(`不允许删除系统文件: ${fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹出确认对话框
|
||||||
|
const confirmed = await vscode.window.showWarningMessage(
|
||||||
|
`确定要删除文件吗?\n\n📄 ${path.basename(filePath)}\n📁 ${path.dirname(filePath)}`,
|
||||||
|
{
|
||||||
|
modal: true, // 模态对话框,阻止其他操作
|
||||||
|
detail: "⚠️ 文件将被移到回收站,可以恢复",
|
||||||
|
},
|
||||||
|
"确定删除",
|
||||||
|
"取消",
|
||||||
|
);
|
||||||
|
|
||||||
|
// 用户取消或关闭对话框
|
||||||
|
if (confirmed !== "确定删除") {
|
||||||
|
throw new Error("用户取消了删除操作");
|
||||||
|
}
|
||||||
|
|
||||||
// 读取文件内容用于变更追踪
|
// 读取文件内容用于变更追踪
|
||||||
const oldContent = fs.readFileSync(absolutePath, 'utf-8');
|
const oldContent = fs.readFileSync(absolutePath, "utf-8");
|
||||||
|
|
||||||
// 记录删除变更
|
// 记录删除变更
|
||||||
const relativePath = path.relative(workspacePath, absolutePath);
|
const relativePath = path.relative(workspacePath, absolutePath);
|
||||||
changeTracker.trackChange(relativePath, oldContent, '');
|
changeTracker.trackChange(relativePath, oldContent, "");
|
||||||
|
|
||||||
// 删除文件
|
// 删除文件(移到回收站)
|
||||||
fs.unlinkSync(absolutePath);
|
const uri = vscode.Uri.file(absolutePath);
|
||||||
|
await vscode.workspace.fs.delete(uri, {
|
||||||
|
recursive: false, // 不是目录,设为 false
|
||||||
|
useTrash: true, // 移到回收站而非永久删除
|
||||||
|
});
|
||||||
|
|
||||||
// Verilog 文件添加知识图谱提示
|
// Verilog 文件添加知识图谱提示
|
||||||
const isVerilogFile = filePath.endsWith('.v') || filePath.endsWith('.sv');
|
const isVerilogFile = filePath.endsWith(".v") || filePath.endsWith(".sv");
|
||||||
if (isVerilogFile) {
|
if (isVerilogFile) {
|
||||||
return `文件已删除: ${filePath}\n\n[提示] 请删除知识图谱中相关节点`;
|
return `文件已删除: ${filePath}\n\n[提示] 请删除知识图谱中相关节点`;
|
||||||
}
|
}
|
||||||
@ -203,13 +271,13 @@ async function executeFileDelete(args: FileDeleteArgs): Promise<string> {
|
|||||||
* 执行 file_list 工具
|
* 执行 file_list 工具
|
||||||
*/
|
*/
|
||||||
async function executeFileList(args: FileListArgs): Promise<string> {
|
async function executeFileList(args: FileListArgs): Promise<string> {
|
||||||
const dirPath = args.path || '.';
|
const dirPath = args.path || ".";
|
||||||
const extensions = args.extension ? [args.extension] : undefined;
|
const extensions = args.extension ? [args.extension] : undefined;
|
||||||
|
|
||||||
const files = await readDirectory(dirPath, extensions);
|
const files = await readDirectory(dirPath, extensions);
|
||||||
const fileList = files.map(f => f.path).join('\n');
|
const fileList = files.map((f) => f.path).join("\n");
|
||||||
|
|
||||||
return fileList || '(目录为空)';
|
return fileList || "(目录为空)";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,7 +286,7 @@ async function executeFileList(args: FileListArgs): Promise<string> {
|
|||||||
*/
|
*/
|
||||||
async function executeSyntaxCheck(
|
async function executeSyntaxCheck(
|
||||||
args: SyntaxCheckArgs,
|
args: SyntaxCheckArgs,
|
||||||
context: ToolExecutorContext
|
context: ToolExecutorContext,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// 检查 iverilog 是否可用
|
// 检查 iverilog 是否可用
|
||||||
const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
|
const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
|
||||||
@ -232,33 +300,33 @@ async function executeSyntaxCheck(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 写入代码到临时文件
|
// 写入代码到临时文件
|
||||||
fs.writeFileSync(tempFile, args.code, 'utf-8');
|
fs.writeFileSync(tempFile, args.code, "utf-8");
|
||||||
|
|
||||||
// 调用 iverilog 进行语法检查
|
// 调用 iverilog 进行语法检查
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require("child_process");
|
||||||
const iverilogPath = getIverilogPath(context.extensionPath);
|
const iverilogPath = getIverilogPath(context.extensionPath);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const child = spawn(iverilogPath, ['-t', 'null', tempFile], {
|
const child = spawn(iverilogPath, ["-t", "null", tempFile], {
|
||||||
cwd: tempDir,
|
cwd: tempDir,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
IVERILOG_ROOT: path.join(context.extensionPath, 'tools', 'iverilog')
|
IVERILOG_ROOT: path.join(context.extensionPath, "tools", "iverilog"),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let stdout = '';
|
let stdout = "";
|
||||||
let stderr = '';
|
let stderr = "";
|
||||||
|
|
||||||
child.stdout.on('data', (data: Buffer) => {
|
child.stdout.on("data", (data: Buffer) => {
|
||||||
stdout += data.toString();
|
stdout += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
child.stderr.on('data', (data: Buffer) => {
|
child.stderr.on("data", (data: Buffer) => {
|
||||||
stderr += data.toString();
|
stderr += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
child.on('close', (code: number) => {
|
child.on("close", (code: number) => {
|
||||||
// 清理临时文件
|
// 清理临时文件
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(tempFile);
|
fs.unlinkSync(tempFile);
|
||||||
@ -267,13 +335,13 @@ async function executeSyntaxCheck(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
resolve('语法检查通过,无错误。');
|
resolve("语法检查通过,无错误。");
|
||||||
} else {
|
} else {
|
||||||
resolve(`语法检查发现错误:\n${stderr || stdout}`);
|
resolve(`语法检查发现错误:\n${stderr || stdout}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
child.on('error', (error: Error) => {
|
child.on("error", (error: Error) => {
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(tempFile);
|
fs.unlinkSync(tempFile);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -282,7 +350,6 @@ async function executeSyntaxCheck(
|
|||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 确保清理临时文件
|
// 确保清理临时文件
|
||||||
try {
|
try {
|
||||||
@ -300,7 +367,7 @@ async function executeSyntaxCheck(
|
|||||||
*/
|
*/
|
||||||
async function executeIverilog(
|
async function executeIverilog(
|
||||||
args: IverilogArgs,
|
args: IverilogArgs,
|
||||||
context: ToolExecutorContext
|
context: ToolExecutorContext,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// 检查 iverilog 是否可用
|
// 检查 iverilog 是否可用
|
||||||
const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
|
const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
|
||||||
@ -311,7 +378,7 @@ async function executeIverilog(
|
|||||||
// 获取工作目录
|
// 获取工作目录
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||||
throw new Error('没有打开的工作区');
|
throw new Error("没有打开的工作区");
|
||||||
}
|
}
|
||||||
const projectPath = workspaceFolders[0].uri.fsPath;
|
const projectPath = workspaceFolders[0].uri.fsPath;
|
||||||
const workDir = args.workDir
|
const workDir = args.workDir
|
||||||
@ -320,32 +387,32 @@ async function executeIverilog(
|
|||||||
|
|
||||||
// 解析参数
|
// 解析参数
|
||||||
const iverilogPath = getIverilogPath(context.extensionPath);
|
const iverilogPath = getIverilogPath(context.extensionPath);
|
||||||
const cmdArgs = args.args.split(/\s+/).filter(a => a.length > 0);
|
const cmdArgs = args.args.split(/\s+/).filter((a) => a.length > 0);
|
||||||
|
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require("child_process");
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const child = spawn(iverilogPath, cmdArgs, {
|
const child = spawn(iverilogPath, cmdArgs, {
|
||||||
cwd: workDir,
|
cwd: workDir,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
IVERILOG_ROOT: path.join(context.extensionPath, 'tools', 'iverilog')
|
IVERILOG_ROOT: path.join(context.extensionPath, "tools", "iverilog"),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let stdout = '';
|
let stdout = "";
|
||||||
let stderr = '';
|
let stderr = "";
|
||||||
|
|
||||||
child.stdout.on('data', (data: Buffer) => {
|
child.stdout.on("data", (data: Buffer) => {
|
||||||
stdout += data.toString();
|
stdout += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
child.stderr.on('data', (data: Buffer) => {
|
child.stderr.on("data", (data: Buffer) => {
|
||||||
stderr += data.toString();
|
stderr += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
child.on('close', (code: number) => {
|
child.on("close", (code: number) => {
|
||||||
const output = stderr || stdout || '(无输出)';
|
const output = stderr || stdout || "(无输出)";
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
resolve(`执行成功\n${output}`);
|
resolve(`执行成功\n${output}`);
|
||||||
} else {
|
} else {
|
||||||
@ -353,7 +420,7 @@ async function executeIverilog(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
child.on('error', (error: Error) => {
|
child.on("error", (error: Error) => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -364,12 +431,12 @@ async function executeIverilog(
|
|||||||
*/
|
*/
|
||||||
async function executeSimulation(
|
async function executeSimulation(
|
||||||
args: SimulationArgs,
|
args: SimulationArgs,
|
||||||
context: ToolExecutorContext
|
context: ToolExecutorContext,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// 获取工作区路径
|
// 获取工作区路径
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||||
throw new Error('请先打开一个工作区');
|
throw new Error("请先打开一个工作区");
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectPath = workspaceFolders[0].uri.fsPath;
|
const projectPath = workspaceFolders[0].uri.fsPath;
|
||||||
@ -377,21 +444,24 @@ async function executeSimulation(
|
|||||||
// 检查是否有 dumpModules 参数(多 VCD 模式)
|
// 检查是否有 dumpModules 参数(多 VCD 模式)
|
||||||
if (args.dumpModules) {
|
if (args.dumpModules) {
|
||||||
const modules = parseDumpModules(args.dumpModules);
|
const modules = parseDumpModules(args.dumpModules);
|
||||||
const vcdDir = args.vcdDir || 'vcd';
|
const vcdDir = args.vcdDir || "vcd";
|
||||||
|
|
||||||
const result = await generateMultiVCD(
|
const result = await generateMultiVCD(
|
||||||
projectPath,
|
projectPath,
|
||||||
context.extensionPath,
|
context.extensionPath,
|
||||||
args.tbPath,
|
args.tbPath,
|
||||||
modules,
|
modules,
|
||||||
vcdDir
|
vcdDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const vcdList = result.vcdFiles
|
const vcdList = result.vcdFiles
|
||||||
.map(f => `- ${f.moduleName}: ${f.success ? f.vcdPath : '失败 - ' + f.error}`)
|
.map(
|
||||||
.join('\n');
|
(f) =>
|
||||||
return `${result.message}\n\nVCD 文件列表:\n${vcdList}${result.stdout ? '\n\n仿真输出:' + result.stdout : ''}`;
|
`- ${f.moduleName}: ${f.success ? f.vcdPath : "失败 - " + f.error}`,
|
||||||
|
)
|
||||||
|
.join("\n");
|
||||||
|
return `${result.message}\n\nVCD 文件列表:\n${vcdList}${result.stdout ? "\n\n仿真输出:" + result.stdout : ""}`;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message);
|
throw new Error(result.message);
|
||||||
}
|
}
|
||||||
@ -420,8 +490,8 @@ async function executeSimulation(
|
|||||||
* 格式:name:path,name:path
|
* 格式:name:path,name:path
|
||||||
*/
|
*/
|
||||||
function parseDumpModules(dumpModules: string): DumpModule[] {
|
function parseDumpModules(dumpModules: string): DumpModule[] {
|
||||||
return dumpModules.split(',').map(item => {
|
return dumpModules.split(",").map((item) => {
|
||||||
const [name, modulePath] = item.trim().split(':');
|
const [name, modulePath] = item.trim().split(":");
|
||||||
return { name: name.trim(), path: modulePath.trim() };
|
return { name: name.trim(), path: modulePath.trim() };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -430,13 +500,15 @@ function parseDumpModules(dumpModules: string): DumpModule[] {
|
|||||||
* 执行 waveform_summary 工具
|
* 执行 waveform_summary 工具
|
||||||
* 解析 VCD 文件并返回波形摘要
|
* 解析 VCD 文件并返回波形摘要
|
||||||
*/
|
*/
|
||||||
async function executeWaveformSummary(args: WaveformSummaryArgs): Promise<string> {
|
async function executeWaveformSummary(
|
||||||
|
args: WaveformSummaryArgs,
|
||||||
|
): Promise<string> {
|
||||||
const { vcdPath, signals, checkpoints } = args;
|
const { vcdPath, signals, checkpoints } = args;
|
||||||
|
|
||||||
// 获取工作区路径
|
// 获取工作区路径
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||||
throw new Error('请先打开一个工作区');
|
throw new Error("请先打开一个工作区");
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspacePath = workspaceFolders[0].uri.fsPath;
|
const workspacePath = workspaceFolders[0].uri.fsPath;
|
||||||
@ -467,17 +539,20 @@ async function executeWaveformSummary(args: WaveformSummaryArgs): Promise<string
|
|||||||
async function executeKnowledgeSave(args: KnowledgeSaveArgs): Promise<string> {
|
async function executeKnowledgeSave(args: KnowledgeSaveArgs): Promise<string> {
|
||||||
const workspaceFolder = getWorkspaceFolder();
|
const workspaceFolder = getWorkspaceFolder();
|
||||||
if (!workspaceFolder) {
|
if (!workspaceFolder) {
|
||||||
throw new Error('请先打开一个工作区');
|
throw new Error("请先打开一个工作区");
|
||||||
}
|
}
|
||||||
|
|
||||||
const iccoderDirUri = vscode.Uri.joinPath(workspaceFolder.uri, '.iccoder');
|
const iccoderDirUri = vscode.Uri.joinPath(workspaceFolder.uri, ".iccoder");
|
||||||
const knowledgeUri = vscode.Uri.joinPath(iccoderDirUri, 'knowledge.json');
|
const knowledgeUri = vscode.Uri.joinPath(iccoderDirUri, "knowledge.json");
|
||||||
|
|
||||||
// 确保 .iccoder 目录存在(兼容远程/虚拟工作区)
|
// 确保 .iccoder 目录存在(兼容远程/虚拟工作区)
|
||||||
await vscode.workspace.fs.createDirectory(iccoderDirUri);
|
await vscode.workspace.fs.createDirectory(iccoderDirUri);
|
||||||
|
|
||||||
// 写入知识图谱(UTF-8)
|
// 写入知识图谱(UTF-8)
|
||||||
await vscode.workspace.fs.writeFile(knowledgeUri, Buffer.from(args.data || '', 'utf-8'));
|
await vscode.workspace.fs.writeFile(
|
||||||
|
knowledgeUri,
|
||||||
|
Buffer.from(args.data || "", "utf-8"),
|
||||||
|
);
|
||||||
|
|
||||||
return `知识图谱已保存: .iccoder/knowledge.json`;
|
return `知识图谱已保存: .iccoder/knowledge.json`;
|
||||||
}
|
}
|
||||||
@ -489,20 +564,33 @@ async function executeKnowledgeSave(args: KnowledgeSaveArgs): Promise<string> {
|
|||||||
async function executeKnowledgeLoad(): Promise<string> {
|
async function executeKnowledgeLoad(): Promise<string> {
|
||||||
const workspaceFolder = getWorkspaceFolder();
|
const workspaceFolder = getWorkspaceFolder();
|
||||||
if (!workspaceFolder) {
|
if (!workspaceFolder) {
|
||||||
throw new Error('请先打开一个工作区');
|
throw new Error("请先打开一个工作区");
|
||||||
}
|
}
|
||||||
|
|
||||||
const knowledgeUri = vscode.Uri.joinPath(workspaceFolder.uri, '.iccoder', 'knowledge.json');
|
const knowledgeUri = vscode.Uri.joinPath(
|
||||||
|
workspaceFolder.uri,
|
||||||
|
".iccoder",
|
||||||
|
"knowledge.json",
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bytes = await vscode.workspace.fs.readFile(knowledgeUri);
|
const bytes = await vscode.workspace.fs.readFile(knowledgeUri);
|
||||||
const content = Buffer.from(bytes).toString('utf-8');
|
const content = Buffer.from(bytes).toString("utf-8");
|
||||||
return content;
|
return content;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 文件不存在:返回空图谱
|
// 文件不存在:返回空图谱
|
||||||
if (error instanceof vscode.FileSystemError && error.code === 'FileNotFound') {
|
if (
|
||||||
|
error instanceof vscode.FileSystemError &&
|
||||||
|
error.code === "FileNotFound"
|
||||||
|
) {
|
||||||
// 与后端 KnowledgeGraph 结构保持一致(nodes/edges + nodeClass 多态字段)
|
// 与后端 KnowledgeGraph 结构保持一致(nodes/edges + nodeClass 多态字段)
|
||||||
return JSON.stringify({ taskId: '', version: 1, module: null, nodes: [], edges: [] });
|
return JSON.stringify({
|
||||||
|
taskId: "",
|
||||||
|
version: 1,
|
||||||
|
module: null,
|
||||||
|
nodes: [],
|
||||||
|
edges: [],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -515,7 +603,9 @@ function getWorkspaceFolder(): vscode.WorkspaceFolder | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const activeUri = vscode.window.activeTextEditor?.document?.uri;
|
const activeUri = vscode.window.activeTextEditor?.document?.uri;
|
||||||
const activeFolder = activeUri ? vscode.workspace.getWorkspaceFolder(activeUri) : undefined;
|
const activeFolder = activeUri
|
||||||
|
? vscode.workspace.getWorkspaceFolder(activeUri)
|
||||||
|
: undefined;
|
||||||
return activeFolder ?? folders[0];
|
return activeFolder ?? folders[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,22 +614,24 @@ function getWorkspaceFolder(): vscode.WorkspaceFolder | undefined {
|
|||||||
*/
|
*/
|
||||||
function getIverilogPath(extensionPath: string): string {
|
function getIverilogPath(extensionPath: string): string {
|
||||||
const platform = process.platform;
|
const platform = process.platform;
|
||||||
if (platform === 'win32') {
|
if (platform === "win32") {
|
||||||
return path.join(extensionPath, 'tools', 'iverilog', 'bin', 'iverilog.exe');
|
return path.join(extensionPath, "tools", "iverilog", "bin", "iverilog.exe");
|
||||||
} else {
|
} else {
|
||||||
return path.join(extensionPath, 'tools', 'iverilog', 'bin', 'iverilog');
|
return path.join(extensionPath, "tools", "iverilog", "bin", "iverilog");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建工具执行器上下文
|
* 创建工具执行器上下文
|
||||||
*/
|
*/
|
||||||
export function createToolExecutorContext(extensionPath: string): ToolExecutorContext {
|
export function createToolExecutorContext(
|
||||||
|
extensionPath: string,
|
||||||
|
): ToolExecutorContext {
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
const workspacePath = workspaceFolders?.[0]?.uri.fsPath || '';
|
const workspacePath = workspaceFolders?.[0]?.uri.fsPath || "";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extensionPath,
|
extensionPath,
|
||||||
workspacePath
|
workspacePath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user