Compare commits
10 Commits
c9e6b383c6
...
feat/Delet
| Author | SHA1 | Date | |
|---|---|---|---|
| ea19dfcbe6 | |||
| fa55e32153 | |||
| f6b1f5c45a | |||
| 1f9a1822c9 | |||
| 63015c6bbc | |||
| 24b30df992 | |||
| 67b1003831 | |||
| 00d37bdaf0 | |||
| c5fcb1427e | |||
| 9118ebd662 |
@ -16,6 +16,10 @@ out/test/**
|
|||||||
# 依赖
|
# 依赖
|
||||||
node_modules/**
|
node_modules/**
|
||||||
|
|
||||||
|
# 文档(避免中文文件名打包问题)
|
||||||
|
docs/**
|
||||||
|
PUBLISH.md
|
||||||
|
|
||||||
# 只排除 waveform_trace 的 src/dist 目录
|
# 只排除 waveform_trace 的 src/dist 目录
|
||||||
tools/waveform_trace/src/dist/**
|
tools/waveform_trace/src/dist/**
|
||||||
|
|
||||||
|
|||||||
13
CHANGELOG.md
13
CHANGELOG.md
@ -2,6 +2,19 @@
|
|||||||
|
|
||||||
所有重要的项目变更都将记录在此文件中。
|
所有重要的项目变更都将记录在此文件中。
|
||||||
|
|
||||||
|
## [1.0.9] - 2026-03-04
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
|
||||||
|
- 将工具折叠图标颜色从蓝色改为灰色
|
||||||
|
- 统一使用蓝色主题色
|
||||||
|
- 优化打包配置,排除重复的 exe 文件
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
- 修复代码变更继续对话查找不到之前的代码变更信息的 bug
|
||||||
|
- 修复对话展示两遍的问题
|
||||||
|
|
||||||
## [1.0.8] - 2026-03-03
|
## [1.0.8] - 2026-03-03
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
|
|||||||
804
docs/VSCode-Extension-API-Guide.md
Normal file
804
docs/VSCode-Extension-API-Guide.md
Normal file
@ -0,0 +1,804 @@
|
|||||||
|
# VS Code Extension API 核心知识点
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
- [1. Extension 生命周期](#1-extension-生命周期) ⭐⭐⭐
|
||||||
|
- [2. 激活事件 (Activation Events)](#2-激活事件-activation-events) ⭐⭐
|
||||||
|
- [3. 命令系统 (Commands)](#3-命令系统-commands) ⭐⭐
|
||||||
|
- [4. Webview API](#4-webview-api) ⭐⭐⭐⭐⭐ **面试重点**
|
||||||
|
- [5. TreeView 和自定义视图](#5-treeview-和自定义视图) ⭐⭐
|
||||||
|
- [6. 文件系统操作](#6-文件系统操作) ⭐⭐⭐
|
||||||
|
- [7. 配置和存储](#7-配置和存储) ⭐⭐⭐⭐ **面试重点**
|
||||||
|
- [8. 消息通知](#8-消息通知) ⭐
|
||||||
|
- [9. 语言特性支持](#9-语言特性支持) ⭐
|
||||||
|
- [10. 调试和诊断](#10-调试和诊断) ⭐
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Extension 生命周期 ⭐⭐⭐
|
||||||
|
|
||||||
|
### 1.1 核心函数 🔥必考
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// extension.ts
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
// 插件激活时调用(只调用一次)
|
||||||
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
console.log('Extension is now active!');
|
||||||
|
|
||||||
|
// 注册命令、视图、事件监听等
|
||||||
|
// 使用 context.subscriptions 管理资源
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插件停用时调用(清理资源)
|
||||||
|
export function deactivate() {
|
||||||
|
console.log('Extension is deactivated');
|
||||||
|
// 清理资源、关闭连接等
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 ExtensionContext 重要属性 🔥必考
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ExtensionContext {
|
||||||
|
// 插件订阅管理(自动清理)
|
||||||
|
subscriptions: { dispose(): any }[];
|
||||||
|
|
||||||
|
// 工作区存储路径
|
||||||
|
storageUri: vscode.Uri | undefined;
|
||||||
|
globalStorageUri: vscode.Uri;
|
||||||
|
|
||||||
|
// 插件路径
|
||||||
|
extensionUri: vscode.Uri;
|
||||||
|
extensionPath: string;
|
||||||
|
|
||||||
|
// 状态存储
|
||||||
|
workspaceState: Memento; // 工作区级别
|
||||||
|
globalState: Memento; // 全局级别
|
||||||
|
secrets: SecretStorage; // 敏感信息存储
|
||||||
|
|
||||||
|
// 环境变量
|
||||||
|
environmentVariableCollection: EnvironmentVariableCollection;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 资源管理最佳实践 🔥必考
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
// ✅ 推荐:使用 context.subscriptions 自动管理
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('extension.command', () => {})
|
||||||
|
);
|
||||||
|
|
||||||
|
// ❌ 不推荐:手动管理容易忘记清理
|
||||||
|
const disposable = vscode.commands.registerCommand('extension.command', () => {});
|
||||||
|
// 需要在 deactivate 中手动调用 disposable.dispose()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 激活事件 (Activation Events) ⭐⭐
|
||||||
|
|
||||||
|
### 2.1 常用激活事件 📌重要
|
||||||
|
|
||||||
|
```json
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"activationEvents": [
|
||||||
|
// 启动时激活
|
||||||
|
"onStartupFinished",
|
||||||
|
|
||||||
|
// 执行命令时激活
|
||||||
|
"onCommand:extension.helloWorld",
|
||||||
|
|
||||||
|
// 打开特定语言文件时激活
|
||||||
|
"onLanguage:javascript",
|
||||||
|
"onLanguage:verilog",
|
||||||
|
|
||||||
|
// 打开特定文件类型时激活
|
||||||
|
"onFileSystem:sftp",
|
||||||
|
|
||||||
|
// 打开特定视图时激活
|
||||||
|
"onView:myCustomView",
|
||||||
|
|
||||||
|
// 调试时激活
|
||||||
|
"onDebug",
|
||||||
|
|
||||||
|
// 打开特定 URI 时激活
|
||||||
|
"onUri",
|
||||||
|
|
||||||
|
// Webview 恢复时激活
|
||||||
|
"onWebviewPanel:myWebview",
|
||||||
|
|
||||||
|
// 任务执行时激活
|
||||||
|
"onTaskType:npm"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 延迟激活策略 🔥必考
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 推荐:使用 onStartupFinished 延迟激活
|
||||||
|
"activationEvents": ["onStartupFinished"]
|
||||||
|
|
||||||
|
// ❌ 不推荐:使用 * 会拖慢启动速度
|
||||||
|
"activationEvents": ["*"]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 命令系统 (Commands)
|
||||||
|
|
||||||
|
### 3.1 注册命令
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 注册简单命令
|
||||||
|
const disposable = vscode.commands.registerCommand(
|
||||||
|
'extension.helloWorld',
|
||||||
|
() => {
|
||||||
|
vscode.window.showInformationMessage('Hello World!');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
context.subscriptions.push(disposable);
|
||||||
|
|
||||||
|
// 注册带参数的命令
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
'extension.openFile',
|
||||||
|
(filePath: string) => {
|
||||||
|
vscode.workspace.openTextDocument(filePath).then(doc => {
|
||||||
|
vscode.window.showTextDocument(doc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 执行命令
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 执行内置命令
|
||||||
|
await vscode.commands.executeCommand('workbench.action.files.save');
|
||||||
|
|
||||||
|
// 执行自定义命令
|
||||||
|
await vscode.commands.executeCommand('extension.openFile', '/path/to/file');
|
||||||
|
|
||||||
|
// 获取所有可用命令
|
||||||
|
const commands = await vscode.commands.getCommands();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 常用内置命令
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 文件操作
|
||||||
|
'workbench.action.files.save'
|
||||||
|
'workbench.action.files.saveAll'
|
||||||
|
'workbench.action.closeActiveEditor'
|
||||||
|
|
||||||
|
// 编辑器操作
|
||||||
|
'editor.action.formatDocument'
|
||||||
|
'editor.action.commentLine'
|
||||||
|
'editor.action.selectAll'
|
||||||
|
|
||||||
|
// 窗口操作
|
||||||
|
'workbench.action.toggleSidebarVisibility'
|
||||||
|
'workbench.action.terminal.new'
|
||||||
|
'workbench.action.quickOpen'
|
||||||
|
|
||||||
|
// Git 操作
|
||||||
|
'git.commit'
|
||||||
|
'git.push'
|
||||||
|
'git.pull'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Webview API ⭐⭐⭐⭐⭐ **面试重点**
|
||||||
|
|
||||||
|
### 4.1 创建 Webview Panel 🔥必考
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const panel = vscode.window.createWebviewPanel(
|
||||||
|
'myWebview', // viewType(唯一标识)
|
||||||
|
'My Webview', // 标题
|
||||||
|
vscode.ViewColumn.One, // 显示位置
|
||||||
|
{
|
||||||
|
enableScripts: true, // 启用 JavaScript
|
||||||
|
retainContextWhenHidden: true, // 隐藏时保留状态
|
||||||
|
localResourceRoots: [ // 允许访问的本地资源路径
|
||||||
|
vscode.Uri.joinPath(context.extensionUri, 'media')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 设置 Webview 内容
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
panel.webview.html = getWebviewContent();
|
||||||
|
|
||||||
|
function getWebviewContent() {
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>My Webview</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello from Webview!</h1>
|
||||||
|
<button onclick="sendMessage()">Send Message</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'alert',
|
||||||
|
text: 'Hello from Webview!'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接收来自 Extension 的消息
|
||||||
|
window.addEventListener('message', event => {
|
||||||
|
const message = event.data;
|
||||||
|
console.log('Received:', message);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Webview 消息通信 🔥必考(项目核心)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Extension → Webview
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: 'update',
|
||||||
|
data: 'some data'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Webview → Extension
|
||||||
|
panel.webview.onDidReceiveMessage(
|
||||||
|
message => {
|
||||||
|
switch (message.command) {
|
||||||
|
case 'alert':
|
||||||
|
vscode.window.showInformationMessage(message.text);
|
||||||
|
break;
|
||||||
|
case 'getData':
|
||||||
|
// 处理数据请求
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: 'dataResponse',
|
||||||
|
data: fetchData()
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
context.subscriptions
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Webview 生命周期管理 📌重要
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 监听 Webview 关闭事件
|
||||||
|
panel.onDidDispose(
|
||||||
|
() => {
|
||||||
|
// 清理资源
|
||||||
|
console.log('Webview disposed');
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
context.subscriptions
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听 Webview 可见性变化
|
||||||
|
panel.onDidChangeViewState(
|
||||||
|
e => {
|
||||||
|
if (e.webviewPanel.visible) {
|
||||||
|
console.log('Webview is now visible');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
context.subscriptions
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 加载本地资源 📌重要
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取本地资源 URI
|
||||||
|
const scriptUri = panel.webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(context.extensionUri, 'media', 'script.js')
|
||||||
|
);
|
||||||
|
|
||||||
|
const styleUri = panel.webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(context.extensionUri, 'media', 'style.css')
|
||||||
|
);
|
||||||
|
|
||||||
|
// 在 HTML 中使用
|
||||||
|
const html = `
|
||||||
|
<link href="${styleUri}" rel="stylesheet">
|
||||||
|
<script src="${scriptUri}"></script>
|
||||||
|
`;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.6 Webview 状态持久化 📌重要
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Webview 中保存状态
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
const state = vscode.getState() || { count: 0 };
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
state.count++;
|
||||||
|
vscode.setState(state);
|
||||||
|
|
||||||
|
// Extension 中序列化状态
|
||||||
|
panel.webview.options = {
|
||||||
|
enableScripts: true,
|
||||||
|
retainContextWhenHidden: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// 恢复 Webview
|
||||||
|
vscode.window.registerWebviewPanelSerializer('myWebview', {
|
||||||
|
async deserializeWebviewPanel(webviewPanel, state) {
|
||||||
|
webviewPanel.webview.html = getWebviewContent();
|
||||||
|
// 恢复状态
|
||||||
|
webviewPanel.webview.postMessage({ command: 'restore', state });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. TreeView 和自定义视图
|
||||||
|
|
||||||
|
### 5.1 创建 TreeView Provider
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MyTreeDataProvider implements vscode.TreeDataProvider<TreeItem> {
|
||||||
|
private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
|
||||||
|
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
||||||
|
|
||||||
|
refresh(): void {
|
||||||
|
this._onDidChangeTreeData.fire(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeItem(element: TreeItem): vscode.TreeItem {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildren(element?: TreeItem): Thenable<TreeItem[]> {
|
||||||
|
if (!element) {
|
||||||
|
// 返回根节点
|
||||||
|
return Promise.resolve([
|
||||||
|
new TreeItem('Item 1', vscode.TreeItemCollapsibleState.None),
|
||||||
|
new TreeItem('Item 2', vscode.TreeItemCollapsibleState.Collapsed)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// 返回子节点
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TreeItem extends vscode.TreeItem {
|
||||||
|
constructor(
|
||||||
|
public readonly label: string,
|
||||||
|
public readonly collapsibleState: vscode.TreeItemCollapsibleState
|
||||||
|
) {
|
||||||
|
super(label, collapsibleState);
|
||||||
|
this.tooltip = `Tooltip for ${label}`;
|
||||||
|
this.command = {
|
||||||
|
command: 'extension.itemClicked',
|
||||||
|
title: 'Click Item',
|
||||||
|
arguments: [this]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 注册 TreeView
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const treeDataProvider = new MyTreeDataProvider();
|
||||||
|
const treeView = vscode.window.createTreeView('myTreeView', {
|
||||||
|
treeDataProvider,
|
||||||
|
showCollapseAll: true
|
||||||
|
});
|
||||||
|
|
||||||
|
context.subscriptions.push(treeView);
|
||||||
|
|
||||||
|
// 刷新视图
|
||||||
|
treeDataProvider.refresh();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 WebviewView Provider(侧边栏 Webview)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MyWebviewViewProvider implements vscode.WebviewViewProvider {
|
||||||
|
resolveWebviewView(
|
||||||
|
webviewView: vscode.WebviewView,
|
||||||
|
context: vscode.WebviewViewResolveContext,
|
||||||
|
token: vscode.CancellationToken
|
||||||
|
) {
|
||||||
|
webviewView.webview.options = {
|
||||||
|
enableScripts: true
|
||||||
|
};
|
||||||
|
|
||||||
|
webviewView.webview.html = getWebviewContent();
|
||||||
|
|
||||||
|
webviewView.webview.onDidReceiveMessage(message => {
|
||||||
|
// 处理消息
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册
|
||||||
|
vscode.window.registerWebviewViewProvider(
|
||||||
|
'myWebviewView',
|
||||||
|
new MyWebviewViewProvider()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 文件系统操作 ⭐⭐⭐
|
||||||
|
|
||||||
|
### 6.1 读取文件 📌重要
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 读取文本文件
|
||||||
|
const uri = vscode.Uri.file('/path/to/file.txt');
|
||||||
|
const content = await vscode.workspace.fs.readFile(uri);
|
||||||
|
const text = Buffer.from(content).toString('utf8');
|
||||||
|
|
||||||
|
// 使用 TextDocument API
|
||||||
|
const document = await vscode.workspace.openTextDocument(uri);
|
||||||
|
const text = document.getText();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 写入文件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 写入文件
|
||||||
|
const uri = vscode.Uri.file('/path/to/file.txt');
|
||||||
|
const content = Buffer.from('Hello World', 'utf8');
|
||||||
|
await vscode.workspace.fs.writeFile(uri, content);
|
||||||
|
|
||||||
|
// 使用 WorkspaceEdit
|
||||||
|
const edit = new vscode.WorkspaceEdit();
|
||||||
|
edit.createFile(uri, { overwrite: true });
|
||||||
|
edit.insert(uri, new vscode.Position(0, 0), 'Hello World');
|
||||||
|
await vscode.workspace.applyEdit(edit);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 文件监听
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 监听文件变化
|
||||||
|
const watcher = vscode.workspace.createFileSystemWatcher('**/*.js');
|
||||||
|
|
||||||
|
watcher.onDidCreate(uri => {
|
||||||
|
console.log('File created:', uri.fsPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher.onDidChange(uri => {
|
||||||
|
console.log('File changed:', uri.fsPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher.onDidDelete(uri => {
|
||||||
|
console.log('File deleted:', uri.fsPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
context.subscriptions.push(watcher);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 工作区操作
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取工作区文件夹
|
||||||
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
|
if (workspaceFolders) {
|
||||||
|
const rootPath = workspaceFolders[0].uri.fsPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找文件
|
||||||
|
const files = await vscode.workspace.findFiles(
|
||||||
|
'**/*.ts', // include pattern
|
||||||
|
'**/node_modules/**' // exclude pattern
|
||||||
|
);
|
||||||
|
|
||||||
|
// 打开文件
|
||||||
|
const document = await vscode.workspace.openTextDocument(uri);
|
||||||
|
await vscode.window.showTextDocument(document);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 配置和存储 ⭐⭐⭐⭐ **面试重点**
|
||||||
|
|
||||||
|
### 7.1 读取配置 📌重要
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 读取配置
|
||||||
|
const config = vscode.workspace.getConfiguration('myExtension');
|
||||||
|
const value = config.get<string>('settingName', 'defaultValue');
|
||||||
|
|
||||||
|
// 监听配置变化
|
||||||
|
vscode.workspace.onDidChangeConfiguration(e => {
|
||||||
|
if (e.affectsConfiguration('myExtension.settingName')) {
|
||||||
|
console.log('Configuration changed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 更新配置
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const config = vscode.workspace.getConfiguration('myExtension');
|
||||||
|
|
||||||
|
// 更新用户配置(全局)
|
||||||
|
await config.update('settingName', 'newValue', vscode.ConfigurationTarget.Global);
|
||||||
|
|
||||||
|
// 更新工作区配置
|
||||||
|
await config.update('settingName', 'newValue', vscode.ConfigurationTarget.Workspace);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 状态存储 🔥必考
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 工作区状态(仅当前工作区)
|
||||||
|
await context.workspaceState.update('key', 'value');
|
||||||
|
const value = context.workspaceState.get('key');
|
||||||
|
|
||||||
|
// 全局状态(跨工作区)
|
||||||
|
await context.globalState.update('key', 'value');
|
||||||
|
const value = context.globalState.get('key');
|
||||||
|
|
||||||
|
// 存储对象
|
||||||
|
await context.globalState.update('userData', { name: 'John', age: 30 });
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 敏感信息存储 🔥必考(Token 管理)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 存储密码、Token 等敏感信息
|
||||||
|
await context.secrets.store('apiToken', 'secret-token-value');
|
||||||
|
|
||||||
|
// 读取
|
||||||
|
const token = await context.secrets.get('apiToken');
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
await context.secrets.delete('apiToken');
|
||||||
|
|
||||||
|
// 监听变化
|
||||||
|
context.secrets.onDidChange(e => {
|
||||||
|
console.log('Secret changed:', e.key);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 消息通知
|
||||||
|
|
||||||
|
### 8.1 信息提示
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 普通信息
|
||||||
|
vscode.window.showInformationMessage('Operation completed!');
|
||||||
|
|
||||||
|
// 警告
|
||||||
|
vscode.window.showWarningMessage('This action may cause issues');
|
||||||
|
|
||||||
|
// 错误
|
||||||
|
vscode.window.showErrorMessage('Operation failed!');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 带按钮的提示
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const result = await vscode.window.showInformationMessage(
|
||||||
|
'Do you want to continue?',
|
||||||
|
'Yes',
|
||||||
|
'No',
|
||||||
|
'Cancel'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result === 'Yes') {
|
||||||
|
// 用户点击了 Yes
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 输入框
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 简单输入
|
||||||
|
const input = await vscode.window.showInputBox({
|
||||||
|
prompt: 'Enter your name',
|
||||||
|
placeHolder: 'John Doe',
|
||||||
|
validateInput: (value) => {
|
||||||
|
return value.length < 3 ? 'Name too short' : null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 快速选择
|
||||||
|
const selected = await vscode.window.showQuickPick(
|
||||||
|
['Option 1', 'Option 2', 'Option 3'],
|
||||||
|
{
|
||||||
|
placeHolder: 'Select an option',
|
||||||
|
canPickMany: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 进度提示
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await vscode.window.withProgress(
|
||||||
|
{
|
||||||
|
location: vscode.ProgressLocation.Notification,
|
||||||
|
title: 'Processing...',
|
||||||
|
cancellable: true
|
||||||
|
},
|
||||||
|
async (progress, token) => {
|
||||||
|
token.onCancellationRequested(() => {
|
||||||
|
console.log('User canceled');
|
||||||
|
});
|
||||||
|
|
||||||
|
progress.report({ increment: 0, message: 'Starting...' });
|
||||||
|
await doWork1();
|
||||||
|
|
||||||
|
progress.report({ increment: 50, message: 'Half done...' });
|
||||||
|
await doWork2();
|
||||||
|
|
||||||
|
progress.report({ increment: 100, message: 'Complete!' });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 语言特性支持
|
||||||
|
|
||||||
|
### 9.1 代码补全
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const provider = vscode.languages.registerCompletionItemProvider(
|
||||||
|
'javascript',
|
||||||
|
{
|
||||||
|
provideCompletionItems(document, position) {
|
||||||
|
const item = new vscode.CompletionItem('myFunction');
|
||||||
|
item.kind = vscode.CompletionItemKind.Function;
|
||||||
|
item.detail = 'My custom function';
|
||||||
|
item.documentation = 'This is a custom function';
|
||||||
|
item.insertText = new vscode.SnippetString('myFunction($1)$0');
|
||||||
|
|
||||||
|
return [item];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'.' // 触发字符
|
||||||
|
);
|
||||||
|
|
||||||
|
context.subscriptions.push(provider);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 悬停提示
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const provider = vscode.languages.registerHoverProvider('javascript', {
|
||||||
|
provideHover(document, position) {
|
||||||
|
const range = document.getWordRangeAtPosition(position);
|
||||||
|
const word = document.getText(range);
|
||||||
|
|
||||||
|
return new vscode.Hover([
|
||||||
|
`**${word}**`,
|
||||||
|
'This is a hover tooltip'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 诊断(错误提示)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const diagnosticCollection = vscode.languages.createDiagnosticCollection('myExtension');
|
||||||
|
context.subscriptions.push(diagnosticCollection);
|
||||||
|
|
||||||
|
function updateDiagnostics(document: vscode.TextDocument) {
|
||||||
|
const diagnostics: vscode.Diagnostic[] = [];
|
||||||
|
|
||||||
|
const text = document.getText();
|
||||||
|
const regex = /TODO/g;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = regex.exec(text))) {
|
||||||
|
const range = new vscode.Range(
|
||||||
|
document.positionAt(match.index),
|
||||||
|
document.positionAt(match.index + match[0].length)
|
||||||
|
);
|
||||||
|
|
||||||
|
const diagnostic = new vscode.Diagnostic(
|
||||||
|
range,
|
||||||
|
'TODO found',
|
||||||
|
vscode.DiagnosticSeverity.Warning
|
||||||
|
);
|
||||||
|
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnosticCollection.set(document.uri, diagnostics);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 调试和诊断
|
||||||
|
|
||||||
|
### 10.1 输出通道
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const outputChannel = vscode.window.createOutputChannel('My Extension');
|
||||||
|
context.subscriptions.push(outputChannel);
|
||||||
|
|
||||||
|
outputChannel.appendLine('Extension activated');
|
||||||
|
outputChannel.show(); // 显示输出面板
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 日志记录
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 使用 LogOutputChannel(带时间戳)
|
||||||
|
const logger = vscode.window.createOutputChannel('My Extension', { log: true });
|
||||||
|
|
||||||
|
logger.trace('Trace message');
|
||||||
|
logger.debug('Debug message');
|
||||||
|
logger.info('Info message');
|
||||||
|
logger.warn('Warning message');
|
||||||
|
logger.error('Error message');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 错误处理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
await riskyOperation();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
vscode.window.showErrorMessage(`Error: ${error.message}`);
|
||||||
|
logger.error(error.stack || error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 最佳实践总结
|
||||||
|
|
||||||
|
### ✅ 推荐做法
|
||||||
|
|
||||||
|
1. **资源管理**:所有 disposable 对象都放入 `context.subscriptions`
|
||||||
|
2. **延迟激活**:使用 `onStartupFinished` 而不是 `*`
|
||||||
|
3. **异步操作**:使用 `async/await` 处理异步操作
|
||||||
|
4. **错误处理**:捕获异常并给用户友好提示
|
||||||
|
5. **类型安全**:充分利用 TypeScript 类型系统
|
||||||
|
6. **状态持久化**:使用 `globalState`/`workspaceState` 保存状态
|
||||||
|
7. **敏感信息**:使用 `secrets` API 存储 Token、密码等
|
||||||
|
|
||||||
|
### ❌ 避免做法
|
||||||
|
|
||||||
|
1. 不要在 `activate` 中执行耗时操作
|
||||||
|
2. 不要忘记清理资源(监听器、Webview 等)
|
||||||
|
3. 不要在 Webview 中直接访问文件系统
|
||||||
|
4. 不要在配置中存储敏感信息
|
||||||
|
5. 不要阻塞主线程(使用 Worker 或异步操作)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考资源
|
||||||
|
|
||||||
|
- [VS Code Extension API 官方文档](https://code.visualstudio.com/api)
|
||||||
|
- [Extension Samples](https://github.com/microsoft/vscode-extension-samples)
|
||||||
|
- [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
|
||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "iccoder",
|
"name": "iccoder",
|
||||||
"displayName": "IC Coder: Agentic Verilog Platform",
|
"displayName": "IC Coder: Agentic Verilog Platform",
|
||||||
"description": "Agentic Verilog Coding Platform for Real-World FPGAs",
|
"description": "Agentic Verilog Coding Platform for Real-World FPGAs",
|
||||||
"version": "1.0.8",
|
"version": "1.0.11",
|
||||||
"publisher": "ICCoderAgenticVerilogPlatform",
|
"publisher": "ICCoderAgenticVerilogPlatform",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.80.0"
|
"vscode": "^1.80.0"
|
||||||
@ -135,6 +135,7 @@
|
|||||||
"@vscode/test-cli": "^0.0.12",
|
"@vscode/test-cli": "^0.0.12",
|
||||||
"@vscode/test-electron": "^2.5.2",
|
"@vscode/test-electron": "^2.5.2",
|
||||||
"@vscode/vsce": "^3.7.1",
|
"@vscode/vsce": "^3.7.1",
|
||||||
|
"copy-webpack-plugin": "^14.0.0",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"ts-loader": "^9.5.4",
|
"ts-loader": "^9.5.4",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
|
|||||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -57,6 +57,9 @@ importers:
|
|||||||
'@vscode/vsce':
|
'@vscode/vsce':
|
||||||
specifier: ^3.7.1
|
specifier: ^3.7.1
|
||||||
version: 3.7.1
|
version: 3.7.1
|
||||||
|
copy-webpack-plugin:
|
||||||
|
specifier: ^14.0.0
|
||||||
|
version: 14.0.0(webpack@5.103.0)
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.39.1
|
specifier: ^9.39.1
|
||||||
version: 9.39.1
|
version: 9.39.1
|
||||||
@ -823,6 +826,12 @@ packages:
|
|||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
|
copy-webpack-plugin@14.0.0:
|
||||||
|
resolution: {integrity: sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==}
|
||||||
|
engines: {node: '>= 20.9.0'}
|
||||||
|
peerDependencies:
|
||||||
|
webpack: ^5.1.0
|
||||||
|
|
||||||
core-util-is@1.0.3:
|
core-util-is@1.0.3:
|
||||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||||
|
|
||||||
@ -1928,6 +1937,10 @@ packages:
|
|||||||
serialize-javascript@6.0.2:
|
serialize-javascript@6.0.2:
|
||||||
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
|
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
|
||||||
|
|
||||||
|
serialize-javascript@7.0.4:
|
||||||
|
resolution: {integrity: sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
setimmediate@1.0.5:
|
setimmediate@1.0.5:
|
||||||
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
||||||
|
|
||||||
@ -3297,6 +3310,15 @@ snapshots:
|
|||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
|
copy-webpack-plugin@14.0.0(webpack@5.103.0):
|
||||||
|
dependencies:
|
||||||
|
glob-parent: 6.0.2
|
||||||
|
normalize-path: 3.0.0
|
||||||
|
schema-utils: 4.3.3
|
||||||
|
serialize-javascript: 7.0.4
|
||||||
|
tinyglobby: 0.2.15
|
||||||
|
webpack: 5.103.0(webpack-cli@6.0.1)
|
||||||
|
|
||||||
core-util-is@1.0.3: {}
|
core-util-is@1.0.3: {}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
@ -4431,6 +4453,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
randombytes: 2.1.0
|
randombytes: 2.1.0
|
||||||
|
|
||||||
|
serialize-javascript@7.0.4: {}
|
||||||
|
|
||||||
setimmediate@1.0.5: {}
|
setimmediate@1.0.5: {}
|
||||||
|
|
||||||
shallow-clone@3.0.1:
|
shallow-clone@3.0.1:
|
||||||
|
|||||||
@ -53,7 +53,7 @@ function getTierIconUri(
|
|||||||
const iconUri = webview.asWebviewUri(
|
const iconUri = webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(
|
vscode.Uri.joinPath(
|
||||||
context.extensionUri,
|
context.extensionUri,
|
||||||
"src",
|
"dist",
|
||||||
"assets",
|
"assets",
|
||||||
"titleIcon",
|
"titleIcon",
|
||||||
iconFile,
|
iconFile,
|
||||||
@ -138,7 +138,7 @@ export async function showICHelperPanel(
|
|||||||
retainContextWhenHidden: true,
|
retainContextWhenHidden: true,
|
||||||
localResourceRoots: [
|
localResourceRoots: [
|
||||||
vscode.Uri.joinPath(context.extensionUri, "media"),
|
vscode.Uri.joinPath(context.extensionUri, "media"),
|
||||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets"),
|
vscode.Uri.joinPath(context.extensionUri, "dist", "assets"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -166,7 +166,7 @@ export async function showICHelperPanel(
|
|||||||
const autoIconUri = panel.webview.asWebviewUri(
|
const autoIconUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(
|
vscode.Uri.joinPath(
|
||||||
context.extensionUri,
|
context.extensionUri,
|
||||||
"src",
|
"dist",
|
||||||
"assets",
|
"assets",
|
||||||
"model",
|
"model",
|
||||||
"Auto.png",
|
"Auto.png",
|
||||||
@ -175,7 +175,7 @@ export async function showICHelperPanel(
|
|||||||
const liteIconUri = panel.webview.asWebviewUri(
|
const liteIconUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(
|
vscode.Uri.joinPath(
|
||||||
context.extensionUri,
|
context.extensionUri,
|
||||||
"src",
|
"dist",
|
||||||
"assets",
|
"assets",
|
||||||
"model",
|
"model",
|
||||||
"lite.png",
|
"lite.png",
|
||||||
@ -184,7 +184,7 @@ export async function showICHelperPanel(
|
|||||||
const syIconUri = panel.webview.asWebviewUri(
|
const syIconUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(
|
vscode.Uri.joinPath(
|
||||||
context.extensionUri,
|
context.extensionUri,
|
||||||
"src",
|
"dist",
|
||||||
"assets",
|
"assets",
|
||||||
"model",
|
"model",
|
||||||
"Sy.png",
|
"Sy.png",
|
||||||
@ -193,7 +193,7 @@ export async function showICHelperPanel(
|
|||||||
const maxIconUri = panel.webview.asWebviewUri(
|
const maxIconUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(
|
vscode.Uri.joinPath(
|
||||||
context.extensionUri,
|
context.extensionUri,
|
||||||
"src",
|
"dist",
|
||||||
"assets",
|
"assets",
|
||||||
"model",
|
"model",
|
||||||
"Max.png",
|
"Max.png",
|
||||||
@ -204,7 +204,7 @@ export async function showICHelperPanel(
|
|||||||
const qrCodeUri = panel.webview.asWebviewUri(
|
const qrCodeUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(
|
vscode.Uri.joinPath(
|
||||||
context.extensionUri,
|
context.extensionUri,
|
||||||
"src",
|
"dist",
|
||||||
"assets",
|
"assets",
|
||||||
"QRCode",
|
"QRCode",
|
||||||
"wx.png",
|
"wx.png",
|
||||||
@ -436,6 +436,7 @@ export async function showICHelperPanel(
|
|||||||
message.askId,
|
message.askId,
|
||||||
message.selected,
|
message.selected,
|
||||||
message.customInput,
|
message.customInput,
|
||||||
|
message.answers
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
// 新增:中止对话
|
// 新增:中止对话
|
||||||
|
|||||||
@ -43,8 +43,7 @@ export interface MessageSegment {
|
|||||||
toolResult?: string;
|
toolResult?: string;
|
||||||
toolDescription?: string;
|
toolDescription?: string;
|
||||||
askId?: string;
|
askId?: string;
|
||||||
question?: string;
|
questions?: import("../types/api").QuestionItem[];
|
||||||
options?: string[];
|
|
||||||
// 智能体相关字段
|
// 智能体相关字段
|
||||||
agentId?: string;
|
agentId?: string;
|
||||||
agentName?: string;
|
agentName?: string;
|
||||||
@ -97,7 +96,7 @@ export interface DialogCallbacks {
|
|||||||
summary: string
|
summary: string
|
||||||
) => void;
|
) => void;
|
||||||
/** 显示问题(ask_user) */
|
/** 显示问题(ask_user) */
|
||||||
onQuestion?: (askId: string, question: string, options: string[]) => void;
|
onQuestion?: (askId: string, questions: import("../types/api").QuestionItem[]) => void;
|
||||||
/** 实时更新段落(流式过程中) */
|
/** 实时更新段落(流式过程中) */
|
||||||
onSegmentUpdate?: (segments: MessageSegment[]) => void;
|
onSegmentUpdate?: (segments: MessageSegment[]) => void;
|
||||||
/** 对话完成,返回所有段落 */
|
/** 对话完成,返回所有段落 */
|
||||||
@ -647,8 +646,11 @@ export class DialogSession {
|
|||||||
this.segments.push({
|
this.segments.push({
|
||||||
type: "question",
|
type: "question",
|
||||||
askId: askId,
|
askId: askId,
|
||||||
|
questions: [{
|
||||||
question: question,
|
question: question,
|
||||||
options: ["确认执行", "取消"],
|
options: ["确认执行", "取消"],
|
||||||
|
multiSelect: false
|
||||||
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
// 实时发送段落更新
|
// 实时发送段落更新
|
||||||
@ -666,8 +668,11 @@ export class DialogSession {
|
|||||||
await userInteractionManager.handleAskUser(
|
await userInteractionManager.handleAskUser(
|
||||||
{
|
{
|
||||||
askId: askId,
|
askId: askId,
|
||||||
|
questions: [{
|
||||||
question: question,
|
question: question,
|
||||||
options: ["确认执行", "取消"],
|
options: ["确认执行", "取消"],
|
||||||
|
multiSelect: false
|
||||||
|
}]
|
||||||
} as AskUserEvent,
|
} as AskUserEvent,
|
||||||
this.taskId
|
this.taskId
|
||||||
);
|
);
|
||||||
@ -714,8 +719,11 @@ export class DialogSession {
|
|||||||
// 注册问题到前端(类似 askUser),以便用户回答时能找到
|
// 注册问题到前端(类似 askUser),以便用户回答时能找到
|
||||||
const planEvent = {
|
const planEvent = {
|
||||||
askId: askId,
|
askId: askId,
|
||||||
|
questions: [{
|
||||||
question: `请确认执行计划:${data.title}`,
|
question: `请确认执行计划:${data.title}`,
|
||||||
options: ["确认执行", "修改计划", "取消"],
|
options: ["确认执行", "修改计划", "取消"],
|
||||||
|
multiSelect: false
|
||||||
|
}]
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await userInteractionManager.handleAskUser(
|
await userInteractionManager.handleAskUser(
|
||||||
@ -856,13 +864,12 @@ export class DialogSession {
|
|||||||
this.segments.push({
|
this.segments.push({
|
||||||
type: "question",
|
type: "question",
|
||||||
askId: data.askId,
|
askId: data.askId,
|
||||||
question: data.question,
|
questions: data.questions,
|
||||||
options: data.options,
|
|
||||||
});
|
});
|
||||||
// 实时发送段落更新(包含问题)
|
// 实时发送段落更新(包含问题)
|
||||||
callbacks.onSegmentUpdate?.(this.segments);
|
callbacks.onSegmentUpdate?.(this.segments);
|
||||||
// 同时调用 onQuestion 用于更新状态栏等
|
// 同时调用 onQuestion 用于更新状态栏等
|
||||||
callbacks.onQuestion?.(data.askId, data.question, data.options);
|
callbacks.onQuestion?.(data.askId, data.questions);
|
||||||
try {
|
try {
|
||||||
await userInteractionManager.handleAskUser(data, this.taskId);
|
await userInteractionManager.handleAskUser(data, this.taskId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1110,7 +1117,8 @@ export class DialogSession {
|
|||||||
async submitAnswer(
|
async submitAnswer(
|
||||||
askId: string,
|
askId: string,
|
||||||
selected?: string[],
|
selected?: string[],
|
||||||
customInput?: string
|
customInput?: string,
|
||||||
|
answers?: { [questionIndex: string]: string[] }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// 直接调用 receiveAnswer,传递 taskId 作为 fallbackTaskId
|
// 直接调用 receiveAnswer,传递 taskId 作为 fallbackTaskId
|
||||||
// 如果 pendingQuestions 中有问题,走正常流程
|
// 如果 pendingQuestions 中有问题,走正常流程
|
||||||
@ -1119,6 +1127,7 @@ export class DialogSession {
|
|||||||
askId,
|
askId,
|
||||||
selected,
|
selected,
|
||||||
customInput,
|
customInput,
|
||||||
|
answers,
|
||||||
this.taskId
|
this.taskId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { submitAnswer, submitToolConfirm } from './apiClient';
|
import { submitAnswer, submitToolConfirm } from './apiClient';
|
||||||
import type { AskUserEvent, AnswerRequest } from '../types/api';
|
import type { AskUserEvent, AnswerRequest, QuestionItem } from '../types/api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 待处理的用户问题
|
* 待处理的用户问题
|
||||||
@ -12,8 +12,7 @@ import type { AskUserEvent, AnswerRequest } from '../types/api';
|
|||||||
interface PendingQuestion {
|
interface PendingQuestion {
|
||||||
askId: string;
|
askId: string;
|
||||||
taskId: string;
|
taskId: string;
|
||||||
question: string;
|
questions: QuestionItem[];
|
||||||
options: string[];
|
|
||||||
resolve: (answer: string) => void;
|
resolve: (answer: string) => void;
|
||||||
reject: (error: Error) => void;
|
reject: (error: Error) => void;
|
||||||
}
|
}
|
||||||
@ -45,9 +44,9 @@ export class UserInteractionManager {
|
|||||||
* @param taskId 当前任务ID
|
* @param taskId 当前任务ID
|
||||||
*/
|
*/
|
||||||
async handleAskUser(event: AskUserEvent, taskId: string): Promise<void> {
|
async handleAskUser(event: AskUserEvent, taskId: string): Promise<void> {
|
||||||
const { askId, question, options } = event;
|
const { askId, questions } = event;
|
||||||
|
|
||||||
console.log(`[UserInteraction] 收到问题: askId=${askId}, question=${question}`);
|
console.log(`[UserInteraction] 收到问题: askId=${askId}, count=${questions.length}`);
|
||||||
|
|
||||||
// 注意:问题显示已经通过 dialogService 的 onSegmentUpdate 统一处理
|
// 注意:问题显示已经通过 dialogService 的 onSegmentUpdate 统一处理
|
||||||
// 这里不再单独发送 showQuestion 命令,避免重复显示
|
// 这里不再单独发送 showQuestion 命令,避免重复显示
|
||||||
@ -57,8 +56,7 @@ export class UserInteractionManager {
|
|||||||
this.pendingQuestions.set(askId, {
|
this.pendingQuestions.set(askId, {
|
||||||
askId,
|
askId,
|
||||||
taskId,
|
taskId,
|
||||||
question,
|
questions,
|
||||||
options,
|
|
||||||
resolve: (answer: string) => {
|
resolve: (answer: string) => {
|
||||||
this.submitUserAnswer(askId, taskId, answer)
|
this.submitUserAnswer(askId, taskId, answer)
|
||||||
.then(() => resolve())
|
.then(() => resolve())
|
||||||
@ -80,24 +78,38 @@ export class UserInteractionManager {
|
|||||||
/**
|
/**
|
||||||
* 处理用户提交的回答(从 WebView 调用)
|
* 处理用户提交的回答(从 WebView 调用)
|
||||||
* @param askId 问题ID
|
* @param askId 问题ID
|
||||||
* @param selected 选中的选项
|
* @param selected 选中的选项(旧格式)
|
||||||
* @param customInput 自定义输入
|
* @param customInput 自定义输入(旧格式)
|
||||||
|
* @param answers 新格式:按问题索引的答案
|
||||||
* @param fallbackTaskId 当问题不存在时使用的 taskId(用于直接发送到后端)
|
* @param fallbackTaskId 当问题不存在时使用的 taskId(用于直接发送到后端)
|
||||||
*/
|
*/
|
||||||
async receiveAnswer(
|
async receiveAnswer(
|
||||||
askId: string,
|
askId: string,
|
||||||
selected?: string[],
|
selected?: string[],
|
||||||
customInput?: string,
|
customInput?: string,
|
||||||
|
answers?: { [questionIndex: string]: string[] },
|
||||||
fallbackTaskId?: string
|
fallbackTaskId?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const pending = this.pendingQuestions.get(askId);
|
const pending = this.pendingQuestions.get(askId);
|
||||||
const answer = customInput || selected?.join(', ') || '';
|
|
||||||
|
// 构建答案字符串
|
||||||
|
let answer = '';
|
||||||
|
if (answers && Object.keys(answers).length > 0) {
|
||||||
|
// 新格式:多问题答案
|
||||||
|
answer = Object.entries(answers)
|
||||||
|
.sort(([a], [b]) => parseInt(a) - parseInt(b))
|
||||||
|
.map(([_, vals]) => vals.join('; '))
|
||||||
|
.join(' | ');
|
||||||
|
} else {
|
||||||
|
// 旧格式:单问题答案
|
||||||
|
answer = customInput || selected?.join(', ') || '';
|
||||||
|
}
|
||||||
|
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
// 问题不存在(可能是页面刷新或会话切换后),尝试直接发送到后端
|
// 问题不存在(可能是页面刷新或会话切换后),尝试直接发送到后端
|
||||||
if (fallbackTaskId) {
|
if (fallbackTaskId) {
|
||||||
console.log(`[UserInteraction] 问题不在 pendingQuestions 中,直接发送到后端: askId=${askId}, taskId=${fallbackTaskId}`);
|
console.log(`[UserInteraction] 问题不在 pendingQuestions 中,直接发送到后端: askId=${askId}, taskId=${fallbackTaskId}`);
|
||||||
await this.submitUserAnswer(askId, fallbackTaskId, answer);
|
await this.submitUserAnswer(askId, fallbackTaskId, answer, answers);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[UserInteraction] 问题不存在且无 fallbackTaskId: askId=${askId}`);
|
console.warn(`[UserInteraction] 问题不存在且无 fallbackTaskId: askId=${askId}`);
|
||||||
}
|
}
|
||||||
@ -119,7 +131,8 @@ export class UserInteractionManager {
|
|||||||
private async submitUserAnswer(
|
private async submitUserAnswer(
|
||||||
askId: string,
|
askId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
answer: string
|
answer: string,
|
||||||
|
answers?: { [questionIndex: string]: string[] }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// 检查是否是工具确认类型的问题
|
// 检查是否是工具确认类型的问题
|
||||||
if (askId.startsWith('tool_confirm_')) {
|
if (askId.startsWith('tool_confirm_')) {
|
||||||
@ -148,7 +161,8 @@ export class UserInteractionManager {
|
|||||||
const request: AnswerRequest = {
|
const request: AnswerRequest = {
|
||||||
askId,
|
askId,
|
||||||
taskId,
|
taskId,
|
||||||
customInput: answer
|
answers: answers,
|
||||||
|
customInput: answers ? undefined : answer
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -194,11 +194,17 @@ export interface PlanSummaryUpdateEvent {
|
|||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 单个问题项 */
|
||||||
|
export interface QuestionItem {
|
||||||
|
question: string;
|
||||||
|
options: string[];
|
||||||
|
multiSelect?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/** ask_user 事件数据 */
|
/** ask_user 事件数据 */
|
||||||
export interface AskUserEvent {
|
export interface AskUserEvent {
|
||||||
askId: string;
|
askId: string;
|
||||||
question: string;
|
questions: QuestionItem[];
|
||||||
options: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** complete 事件数据 */
|
/** complete 事件数据 */
|
||||||
@ -351,10 +357,12 @@ export interface AnswerRequest {
|
|||||||
askId: string;
|
askId: string;
|
||||||
/** 任务ID */
|
/** 任务ID */
|
||||||
taskId: string;
|
taskId: string;
|
||||||
/** 选中的选项列表 */
|
/** 选中的选项列表(旧格式,兼容) */
|
||||||
selected?: string[];
|
selected?: string[];
|
||||||
/** 自定义输入内容 */
|
/** 自定义输入内容(旧格式,兼容) */
|
||||||
customInput?: string;
|
customInput?: string;
|
||||||
|
/** 新格式:按问题索引的答案 */
|
||||||
|
answers?: { [questionIndex: string]: string[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 用户回答响应 */
|
/** 用户回答响应 */
|
||||||
|
|||||||
@ -324,7 +324,7 @@ async function handleUserMessageWithBackend(
|
|||||||
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
|
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
|
||||||
},
|
},
|
||||||
|
|
||||||
onQuestion: (askId, question, options) => {
|
onQuestion: (askId: string, questions: import("../types/api").QuestionItem[]) => {
|
||||||
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
|
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "updateStatus",
|
command: "updateStatus",
|
||||||
@ -469,10 +469,11 @@ async function handleUserMessageWithBackend(
|
|||||||
export async function handleUserAnswer(
|
export async function handleUserAnswer(
|
||||||
askId: string,
|
askId: string,
|
||||||
selected?: string[],
|
selected?: string[],
|
||||||
customInput?: string
|
customInput?: string,
|
||||||
|
answers?: { [questionIndex: string]: string[] }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (currentSession) {
|
if (currentSession) {
|
||||||
await currentSession.submitAnswer(askId, selected, customInput);
|
await currentSession.submitAnswer(askId, selected, customInput, answers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -92,7 +92,8 @@ export async function executeWaveformTrace(
|
|||||||
|
|
||||||
child.on('close', (code: number | null) => {
|
child.on('close', (code: number | null) => {
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
resolve(stdout);
|
// 成功时返回 stdout,忽略 stderr 中的进度信息
|
||||||
|
resolve(stdout || stderr);
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(
|
reject(new Error(
|
||||||
`waveform_trace 执行失败 (code=${code}):\n${stderr || stdout}`
|
`waveform_trace 执行失败 (code=${code}):\n${stderr || stdout}`
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
|||||||
retainContextWhenHidden: true,
|
retainContextWhenHidden: true,
|
||||||
localResourceRoots: [
|
localResourceRoots: [
|
||||||
vscode.Uri.joinPath(context.extensionUri, "media"),
|
vscode.Uri.joinPath(context.extensionUri, "media"),
|
||||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets")
|
vscode.Uri.joinPath(context.extensionUri, "dist", "assets")
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -47,21 +47,21 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
// 获取模型图标URI
|
// 获取模型图标URI
|
||||||
const autoIconUri = panel.webview.asWebviewUri(
|
const autoIconUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Auto.png")
|
vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "Auto.png")
|
||||||
);
|
);
|
||||||
const liteIconUri = panel.webview.asWebviewUri(
|
const liteIconUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "lite.png")
|
vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "lite.png")
|
||||||
);
|
);
|
||||||
const syIconUri = panel.webview.asWebviewUri(
|
const syIconUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Sy.png")
|
vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "Sy.png")
|
||||||
);
|
);
|
||||||
const maxIconUri = panel.webview.asWebviewUri(
|
const maxIconUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Max.png")
|
vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "Max.png")
|
||||||
);
|
);
|
||||||
|
|
||||||
// 获取二维码图片URI
|
// 获取二维码图片URI
|
||||||
const qrCodeUri = panel.webview.asWebviewUri(
|
const qrCodeUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "QRCode", "wx.png")
|
vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "QRCode", "wx.png")
|
||||||
);
|
);
|
||||||
|
|
||||||
// 获取Logo URI
|
// 获取Logo URI
|
||||||
@ -129,7 +129,8 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
|||||||
handleUserAnswer(
|
handleUserAnswer(
|
||||||
message.askId,
|
message.askId,
|
||||||
message.selected,
|
message.selected,
|
||||||
message.customInput
|
message.customInput,
|
||||||
|
message.answers
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
// 新增:中止对话
|
// 新增:中止对话
|
||||||
|
|||||||
@ -1188,64 +1188,60 @@ export function getMessageAreaScript(): string {
|
|||||||
} else if (segment.type === 'question') {
|
} else if (segment.type === 'question') {
|
||||||
segmentDiv.className += ' segment-question';
|
segmentDiv.className += ' segment-question';
|
||||||
|
|
||||||
|
// 兼容旧格式:如果有 segment.question,转换为 questions 数组
|
||||||
|
const questions = segment.questions || (segment.question ? [{
|
||||||
|
question: segment.question,
|
||||||
|
options: segment.options || [],
|
||||||
|
multiSelect: false
|
||||||
|
}] : []);
|
||||||
|
|
||||||
// 检查是否已回答
|
// 检查是否已回答
|
||||||
const isAnswered = answeredQuestions.has(segment.askId);
|
const isAnswered = answeredQuestions.has(segment.askId);
|
||||||
const selectedAnswer = answeredQuestions.get(segment.askId);
|
const savedAnswers = answeredQuestions.get(segment.askId) || {};
|
||||||
|
|
||||||
if (isAnswered) {
|
if (isAnswered) {
|
||||||
segmentDiv.classList.add('answered');
|
segmentDiv.classList.add('answered');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有选项
|
// 渲染多个问题
|
||||||
const hasOptions = segment.options && segment.options.length > 0;
|
const questionsHtml = questions.map((q, qIndex) => {
|
||||||
|
const inputType = q.multiSelect ? 'checkbox' : 'radio';
|
||||||
|
const inputName = \`q\${qIndex}\`;
|
||||||
|
const selectedAnswers = savedAnswers[qIndex] || [];
|
||||||
|
|
||||||
const optionsHtml = hasOptions
|
const optionsHtml = q.options.map(opt => {
|
||||||
? (segment.options || []).map(opt => {
|
const isSelected = selectedAnswers.includes(opt);
|
||||||
const isSelected = isAnswered && opt === selectedAnswer;
|
return \`<label style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 0;">
|
||||||
return \`<button class="question-option\${isSelected ? ' selected' : ''}" data-option="\${opt}">\${opt}</button>\`;
|
<input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}>
|
||||||
}).join('')
|
<span>\${opt}</span>
|
||||||
: '';
|
</label>\`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
return \`
|
||||||
|
<div class="question-item" data-question-index="\${qIndex}" style="margin-bottom:12px;">
|
||||||
|
<div class="question-text" style="margin-bottom:8px;">\${formatText(q.question)}</div>
|
||||||
|
<div class="question-options">\${optionsHtml}</div>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
segmentDiv.innerHTML = \`
|
||||||
<div class="question-text">\${formatText(segment.question || '')}</div>
|
\${questionsHtml}
|
||||||
\${hasOptions ? \`<div class="question-options" data-ask-id="\${segment.askId}">\${optionsHtml}</div>\` : ''}
|
<button class="custom-submit" style="display:\${isAnswered ? 'none' : 'block'};margin-top:8px;padding:8px 16px;background:var(--vscode-button-background);color:var(--vscode-button-foreground);border:none;border-radius:6px;cursor:pointer;">提交答案</button>
|
||||||
<div class="custom-input-container" style="display: \${isAnswered ? 'none' : 'flex'};">
|
|
||||||
<input type="text" class="custom-input" placeholder="\${hasOptions ? '输入其他答案...' : '请输入您的答案...'}" />
|
|
||||||
<button class="custom-submit">提交</button>
|
|
||||||
</div>
|
|
||||||
\`;
|
\`;
|
||||||
|
|
||||||
// 只在未回答时添加事件监听
|
// 只在未回答时添加事件监听
|
||||||
if (!isAnswered) {
|
if (!isAnswered) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (hasOptions) {
|
|
||||||
const optionButtons = segmentDiv.querySelectorAll('.question-option');
|
|
||||||
optionButtons.forEach(btn => {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
const option = this.getAttribute('data-option');
|
|
||||||
handleQuestionAnswerInSegment(segment.askId, option, segmentDiv);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitBtn = segmentDiv.querySelector('.custom-submit');
|
const submitBtn = segmentDiv.querySelector('.custom-submit');
|
||||||
const customInput = segmentDiv.querySelector('.custom-input');
|
if (submitBtn) {
|
||||||
if (submitBtn && customInput) {
|
|
||||||
submitBtn.addEventListener('click', function() {
|
submitBtn.addEventListener('click', function() {
|
||||||
const customValue = customInput.value.trim();
|
const answers = {};
|
||||||
if (customValue) {
|
questions.forEach((q, qIndex) => {
|
||||||
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
const inputs = segmentDiv.querySelectorAll(\`input[name="q\${qIndex}"]:checked\`);
|
||||||
}
|
answers[qIndex] = Array.from(inputs).map(input => input.value);
|
||||||
});
|
});
|
||||||
|
handleMultiQuestionAnswer(segment.askId, answers, segmentDiv);
|
||||||
// 支持回车提交
|
|
||||||
customInput.addEventListener('keypress', function(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
const customValue = customInput.value.trim();
|
|
||||||
if (customValue) {
|
|
||||||
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
@ -1723,6 +1719,34 @@ export function getMessageAreaScript(): string {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理多问题答案提交
|
||||||
|
function handleMultiQuestionAnswer(askId, answers, segmentDiv) {
|
||||||
|
console.log('[WebView] 多问题答案提交:', askId, answers);
|
||||||
|
|
||||||
|
// 保存答案到 Map 中
|
||||||
|
answeredQuestions.set(askId, answers);
|
||||||
|
|
||||||
|
// 标记问题已回答
|
||||||
|
segmentDiv.classList.add('answered');
|
||||||
|
|
||||||
|
// 禁用所有输入
|
||||||
|
const inputs = segmentDiv.querySelectorAll('input');
|
||||||
|
inputs.forEach(input => input.disabled = true);
|
||||||
|
|
||||||
|
// 隐藏提交按钮
|
||||||
|
const submitBtn = segmentDiv.querySelector('.custom-submit');
|
||||||
|
if (submitBtn) {
|
||||||
|
submitBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送答案到后端
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'submitAnswer',
|
||||||
|
askId: askId,
|
||||||
|
answers: answers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
${getWaveformPreviewScript()}
|
${getWaveformPreviewScript()}
|
||||||
|
|
||||||
${getCodeHighlightScript()}
|
${getCodeHighlightScript()}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export function getNdtWelcomeModalContent(logoUri?: string): string {
|
|||||||
|
|
||||||
<div class="ndt-welcome-modal-header">
|
<div class="ndt-welcome-modal-header">
|
||||||
<div class="ndt-welcome-icon">🎉</div>
|
<div class="ndt-welcome-icon">🎉</div>
|
||||||
<h2>欢迎宁德时代新能源科技股份有限公司<span style="white-space: nowrap;">的各位专家</span>使用 IC Coder!</h2>
|
<h2>欢迎企业<span style="white-space: nowrap;">的各位专家</span>使用 IC Coder!</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ndt-welcome-modal-body">
|
<div class="ndt-welcome-modal-body">
|
||||||
|
|||||||
@ -19,34 +19,34 @@ export function getWelcomeModalContent(logoUri?: string): string {
|
|||||||
<div class="welcome-modal-header">
|
<div class="welcome-modal-header">
|
||||||
<div class="welcome-icon">🎉</div>
|
<div class="welcome-icon">🎉</div>
|
||||||
<h2>欢迎使用 IC Coder!</h2>
|
<h2>欢迎使用 IC Coder!</h2>
|
||||||
<p class="welcome-modal-subtitle">您已成功激活 15 天试用期,让我们开始探索 IC Coder 的强大功能吧!</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="welcome-modal-body">
|
<div class="welcome-modal-body">
|
||||||
<div class="welcome-step">
|
<!-- 试用期提示 -->
|
||||||
<div class="welcome-step-icon">📝</div>
|
<div class="trial-banner">
|
||||||
<div class="welcome-step-content">
|
<span>您已获得 <strong>5 天企业版试用期</strong>,企业版试用期内Credits用量无限,并可无限制使用所有功能</span>
|
||||||
<h3>步骤 1:打开聊天面板</h3>
|
</div>
|
||||||
<p>点击侧边栏的 IC Coder 图标,或使用命令面板搜索 "IC Coder: Open Chat"</p>
|
|
||||||
</div>
|
<!-- IC Coder 简介 -->
|
||||||
</div>
|
<div class="intro-section">
|
||||||
|
<h3 class="section-title">关于 IC Coder</h3>
|
||||||
<div class="welcome-step">
|
<p class="intro-text">IC Coder是一款The Agentic AI Verilog Coding Platform(自主式人工智能 Verilog 编码平台)。我们采用全球顶尖的IC Coder自研芯片设计微调模型,为代码生成提供强大的AI能力支撑。</p>
|
||||||
<div class="welcome-step-icon">💬</div>
|
|
||||||
<div class="welcome-step-content">
|
<div class="features">
|
||||||
<h3>步骤 2:输入您的需求</h3>
|
<div class="feature-item">
|
||||||
<p>描述您想要生成的 Verilog 代码或需要帮助的问题,AI 将为您提供专业的解决方案</p>
|
<span class="feature-text">多智能体架构(Multi-Agent System):多个专业化AI智能体协同工作,分别负责架构设计、代码生成、验证测试等不同环节</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="feature-item">
|
||||||
|
<span class="feature-text">增强上下文引擎:智能理解和管理大规模设计上下文,确保生成代码的一致性和准确性</span>
|
||||||
<div class="welcome-step">
|
</div>
|
||||||
<div class="welcome-step-icon">🔬</div>
|
<div class="feature-item">
|
||||||
<div class="welcome-step-content">
|
<span class="feature-text">AI自主仿真:IC Coder提供完全自动化的仿真验证流程,无需手动编写测试代码</span>
|
||||||
<h3>步骤 3:运行仿真</h3>
|
</div>
|
||||||
<p>使用 "生成 VCD" 命令运行 iverilog 仿真,并通过波形查看器查看仿真结果</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 按钮组 -->
|
||||||
|
<div class="button-group">
|
||||||
<button id="welcomeStartBtn" class="welcome-btn welcome-btn-primary">
|
<button id="welcomeStartBtn" class="welcome-btn welcome-btn-primary">
|
||||||
<span>开始使用</span>
|
<span>开始使用</span>
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||||
@ -56,6 +56,7 @@ export function getWelcomeModalContent(logoUri?: string): string {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ export function getWelcomeModalStyles(): string {
|
|||||||
border: 1px solid var(--vscode-widget-border);
|
border: 1px solid var(--vscode-widget-border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 500px;
|
max-width: 480px;
|
||||||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
||||||
animation: modalSlideIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
|
animation: modalSlideIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -124,9 +125,10 @@ export function getWelcomeModalStyles(): string {
|
|||||||
|
|
||||||
.welcome-modal-header h2 {
|
.welcome-modal-header h2 {
|
||||||
margin: 0 0 12px;
|
margin: 0 0 12px;
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--vscode-foreground);
|
color: var(--vscode-foreground);
|
||||||
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome-modal-subtitle {
|
.welcome-modal-subtitle {
|
||||||
@ -140,37 +142,70 @@ export function getWelcomeModalStyles(): string {
|
|||||||
padding: 0 32px 32px;
|
padding: 0 32px 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome-step {
|
/* 试用期横幅 */
|
||||||
|
.trial-banner {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
align-items: center;
|
||||||
margin: 20px 0;
|
justify-content: center;
|
||||||
padding: 16px;
|
padding: 12px 16px;
|
||||||
background: var(--vscode-editor-inactiveSelectionBackground);
|
background: var(--vscode-editor-inactiveSelectionBackground);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-left: 4px solid var(--vscode-textLink-foreground);
|
margin-bottom: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.welcome-step-icon {
|
|
||||||
font-size: 24px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.welcome-step-content h3 {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--vscode-textLink-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.welcome-step-content p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--vscode-descriptionForeground);
|
color: var(--vscode-descriptionForeground);
|
||||||
line-height: 1.5;
|
border-left: 3px solid var(--vscode-textLink-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trial-banner strong {
|
||||||
|
color: var(--vscode-textLink-foreground);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* IC Coder 简介区域 */
|
||||||
|
.intro-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item {
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--vscode-editor-inactiveSelectionBackground);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border-left: 2px solid var(--vscode-textLink-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮组 */
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome-btn {
|
.welcome-btn {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -181,18 +216,32 @@ export function getWelcomeModalStyles(): string {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
background: var(--vscode-button-background);
|
|
||||||
color: var(--vscode-button-foreground);
|
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
margin-top: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome-btn:hover {
|
.welcome-btn-primary {
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-btn-primary:hover {
|
||||||
background: var(--vscode-button-hoverBackground);
|
background: var(--vscode-button-hoverBackground);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.welcome-btn-secondary {
|
||||||
|
background: var(--vscode-button-secondaryBackground);
|
||||||
|
color: var(--vscode-button-secondaryForeground);
|
||||||
|
border: 1px solid var(--vscode-button-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-btn-secondary:hover {
|
||||||
|
background: var(--vscode-button-secondaryHoverBackground);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
.welcome-btn:active {
|
.welcome-btn:active {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
|
||||||
//@ts-check
|
//@ts-check
|
||||||
/** @typedef {import('webpack').Configuration} WebpackConfig **/
|
/** @typedef {import('webpack').Configuration} WebpackConfig **/
|
||||||
@ -45,5 +46,12 @@ const extensionConfig = {
|
|||||||
infrastructureLogging: {
|
infrastructureLogging: {
|
||||||
level: "log", // enables logging required for problem matchers
|
level: "log", // enables logging required for problem matchers
|
||||||
},
|
},
|
||||||
|
plugins: [
|
||||||
|
new CopyWebpackPlugin({
|
||||||
|
patterns: [
|
||||||
|
{ from: 'src/assets', to: 'assets' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
};
|
};
|
||||||
module.exports = [ extensionConfig ];
|
module.exports = [ extensionConfig ];
|
||||||
Reference in New Issue
Block a user