feat:实现Windows系统通知功能
- 集成node-notifier实现跨平台系统通知 - AI响应完成时自动弹出Windows Toast通知 - 支持通知防抖机制,避免频繁弹窗 - 添加通知配置项:启用/禁用、声音、超时时间 - 移除VS Code内置弹窗,仅在系统通知失败时作为备用
This commit is contained in:
911
docs/system-notification-implementation.md
Normal file
911
docs/system-notification-implementation.md
Normal file
@ -0,0 +1,911 @@
|
|||||||
|
# IC Coder 系统通知功能实现方案
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
- [1. 需求背景](#1-需求背景)
|
||||||
|
- [2. 技术方案对比](#2-技术方案对比)
|
||||||
|
- [3. 推荐方案详解](#3-推荐方案详解)
|
||||||
|
- [4. 实现步骤](#4-实现步骤)
|
||||||
|
- [5. API 设计](#5-api-设计)
|
||||||
|
- [6. 配置选项](#6-配置选项)
|
||||||
|
- [7. 测试方案](#7-测试方案)
|
||||||
|
- [8. 注意事项](#8-注意事项)
|
||||||
|
- [9. 常见问题](#9-常见问题)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 需求背景
|
||||||
|
|
||||||
|
### 1.1 问题描述
|
||||||
|
|
||||||
|
当前 IC Coder 插件使用 VS Code 内置的通知 API (`vscode.window.showInformationMessage`) 来提示用户任务完成。这种方式存在以下问题:
|
||||||
|
|
||||||
|
- **可见性问题**: 用户切换到其他应用时,无法看到 VS Code 内部的通知
|
||||||
|
- **错过通知**: 长时间运行的任务(如 iverilog 仿真)完成时,用户可能已经离开 VS Code
|
||||||
|
- **用户体验**: 需要用户主动回到 VS Code 才能知道任务状态
|
||||||
|
|
||||||
|
### 1.2 目标
|
||||||
|
|
||||||
|
实现系统级通知功能,使得:
|
||||||
|
|
||||||
|
1. 用户在任何应用中都能收到任务完成通知
|
||||||
|
2. 通知显示在操作系统的通知中心(Windows Action Center / macOS Notification Center / Linux notify-send)
|
||||||
|
3. 支持自定义通知内容、图标、声音
|
||||||
|
4. 用户可以配置是否启用系统通知
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 技术方案对比
|
||||||
|
|
||||||
|
### 2.1 方案一:node-notifier(推荐)
|
||||||
|
|
||||||
|
**描述**: 使用 `node-notifier` 库,封装了各平台的原生通知 API
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- ✅ 跨平台支持(Windows/macOS/Linux)
|
||||||
|
- ✅ API 简单易用
|
||||||
|
- ✅ 支持自定义图标、声音、操作按钮
|
||||||
|
- ✅ 活跃维护,社区支持良好
|
||||||
|
- ✅ 支持通知点击回调
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- ❌ 需要添加额外依赖(~500KB)
|
||||||
|
- ❌ 首次使用需要用户授权
|
||||||
|
|
||||||
|
**适用场景**: 需要跨平台支持的生产环境
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 方案二:Windows PowerShell Toast 通知
|
||||||
|
|
||||||
|
**描述**: 使用 PowerShell 脚本调用 Windows 10/11 的 Toast 通知 API
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- ✅ 无需额外依赖
|
||||||
|
- ✅ 支持丰富的 Toast 样式(按钮、输入框等)
|
||||||
|
- ✅ 与 Windows 系统深度集成
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- ❌ 仅支持 Windows 10/11
|
||||||
|
- ❌ 需要执行 PowerShell 脚本,可能有安全限制
|
||||||
|
- ❌ 实现复杂度较高
|
||||||
|
|
||||||
|
**适用场景**: 仅针对 Windows 平台的专用功能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 方案三:Electron Notification API
|
||||||
|
|
||||||
|
**描述**: 使用 Electron 的 `Notification` API(VS Code 基于 Electron)
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- ✅ 无需额外依赖
|
||||||
|
- ✅ 跨平台支持
|
||||||
|
- ✅ API 简洁
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- ❌ VS Code 扩展 API 未直接暴露 Electron API
|
||||||
|
- ❌ 需要通过 `@vscode/webview-ui-toolkit` 或其他方式间接调用
|
||||||
|
- ❌ 可能存在兼容性问题
|
||||||
|
|
||||||
|
**适用场景**: 理论可行,但实际受限于 VS Code 扩展沙箱
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.4 方案四:结合 VS Code 通知 + 系统通知
|
||||||
|
|
||||||
|
**描述**: 同时使用 VS Code 内置通知和系统通知
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- ✅ 双重保障,覆盖所有场景
|
||||||
|
- ✅ 用户在 VS Code 内外都能看到
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- ❌ 可能显得冗余
|
||||||
|
- ❌ 需要处理两种通知的协调逻辑
|
||||||
|
|
||||||
|
**适用场景**: 对通知可靠性要求极高的场景
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.5 方案对比表
|
||||||
|
|
||||||
|
| 方案 | 跨平台 | 依赖大小 | 实现难度 | 用户体验 | 推荐度 |
|
||||||
|
|------|--------|----------|----------|----------|--------|
|
||||||
|
| node-notifier | ✅ | ~500KB | ⭐ 低 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||||
|
| PowerShell Toast | ❌ Windows Only | 0 | ⭐⭐⭐ 高 | ⭐⭐⭐⭐ | ⭐⭐ |
|
||||||
|
| Electron API | ✅ | 0 | ⭐⭐⭐⭐ 很高 | ⭐⭐⭐ | ⭐ |
|
||||||
|
| 双重通知 | ✅ | ~500KB | ⭐⭐ 中 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 推荐方案详解
|
||||||
|
|
||||||
|
### 3.1 选择 node-notifier 的理由
|
||||||
|
|
||||||
|
1. **成熟稳定**: 被广泛使用(npm 周下载量 > 200 万)
|
||||||
|
2. **跨平台**: 自动适配不同操作系统的通知机制
|
||||||
|
3. **功能丰富**: 支持图标、声音、操作按钮、回调
|
||||||
|
4. **易于集成**: 与 VS Code 扩展开发无缝集成
|
||||||
|
|
||||||
|
### 3.2 node-notifier 工作原理
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ IC Coder Extension │
|
||||||
|
│ ┌────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ notificationService.ts │ │
|
||||||
|
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ sendSystemNotification(title, message, options) │ │ │
|
||||||
|
│ │ └──────────────────┬───────────────────────────────┘ │ │
|
||||||
|
│ └────────────────────┼──────────────────────────────────┘ │
|
||||||
|
└────────────────────────┼─────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ node-notifier │
|
||||||
|
│ (跨平台适配层) │
|
||||||
|
└──────────┬───────────┘
|
||||||
|
│
|
||||||
|
┌───────────────┼───────────────┐
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────┐ ┌──────────┐ ┌──────────┐
|
||||||
|
│ Windows │ │ macOS │ │ Linux │
|
||||||
|
│ Toast │ │ NSUser │ │ notify- │
|
||||||
|
│ Notif. │ │ Notif. │ │ send │
|
||||||
|
└─────────┘ └──────────┘ └──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 各平台通知效果
|
||||||
|
|
||||||
|
#### Windows 10/11
|
||||||
|
- 显示在右下角 Action Center
|
||||||
|
- 支持应用图标、标题、消息、操作按钮
|
||||||
|
- 可以播放系统声音
|
||||||
|
- 通知历史保存在通知中心
|
||||||
|
|
||||||
|
#### macOS
|
||||||
|
- 显示在右上角 Notification Center
|
||||||
|
- 支持应用图标、标题、副标题、消息
|
||||||
|
- 可以播放系统声音
|
||||||
|
- 支持回复和操作按钮
|
||||||
|
|
||||||
|
#### Linux
|
||||||
|
- 使用 `notify-send` 或 `libnotify`
|
||||||
|
- 显示位置取决于桌面环境(GNOME/KDE/XFCE)
|
||||||
|
- 支持图标、标题、消息、紧急程度
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 实现步骤
|
||||||
|
|
||||||
|
### 4.1 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 node-notifier
|
||||||
|
pnpm add node-notifier
|
||||||
|
|
||||||
|
# 安装类型定义
|
||||||
|
pnpm add -D @types/node-notifier
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 创建通知服务模块
|
||||||
|
|
||||||
|
创建 `src/services/notificationService.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import * as notifier from 'node-notifier';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知类型枚举
|
||||||
|
*/
|
||||||
|
export enum NotificationType {
|
||||||
|
INFO = 'info',
|
||||||
|
SUCCESS = 'success',
|
||||||
|
WARNING = 'warning',
|
||||||
|
ERROR = 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知选项接口
|
||||||
|
*/
|
||||||
|
export interface NotificationOptions {
|
||||||
|
/** 通知标题 */
|
||||||
|
title: string;
|
||||||
|
/** 通知消息 */
|
||||||
|
message: string;
|
||||||
|
/** 通知类型 */
|
||||||
|
type?: NotificationType;
|
||||||
|
/** 是否播放声音 */
|
||||||
|
sound?: boolean;
|
||||||
|
/** 超时时间(秒),0 表示不自动消失 */
|
||||||
|
timeout?: number;
|
||||||
|
/** 自定义图标路径 */
|
||||||
|
icon?: string;
|
||||||
|
/** 点击通知时的回调 */
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统通知服务类
|
||||||
|
*/
|
||||||
|
export class NotificationService {
|
||||||
|
private static instance: NotificationService;
|
||||||
|
private readonly extensionPath: string;
|
||||||
|
private readonly iconPath: string;
|
||||||
|
|
||||||
|
private constructor(context: vscode.ExtensionContext) {
|
||||||
|
this.extensionPath = context.extensionPath;
|
||||||
|
this.iconPath = path.join(this.extensionPath, 'resources', 'icon.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单例实例
|
||||||
|
*/
|
||||||
|
public static getInstance(context?: vscode.ExtensionContext): NotificationService {
|
||||||
|
if (!NotificationService.instance && context) {
|
||||||
|
NotificationService.instance = new NotificationService(context);
|
||||||
|
}
|
||||||
|
return NotificationService.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否启用系统通知
|
||||||
|
*/
|
||||||
|
private isSystemNotificationEnabled(): boolean {
|
||||||
|
const config = vscode.workspace.getConfiguration('ic-coder');
|
||||||
|
return config.get<boolean>('enableSystemNotification', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送系统通知
|
||||||
|
*/
|
||||||
|
public sendNotification(options: NotificationOptions): void {
|
||||||
|
// 检查用户配置
|
||||||
|
if (!this.isSystemNotificationEnabled()) {
|
||||||
|
console.log('[NotificationService] 系统通知已禁用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type = NotificationType.INFO,
|
||||||
|
sound = true,
|
||||||
|
timeout = 10,
|
||||||
|
icon,
|
||||||
|
onClick
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// 准备通知参数
|
||||||
|
const notificationConfig: notifier.Notification = {
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
icon: icon || this.iconPath,
|
||||||
|
sound: sound,
|
||||||
|
wait: false,
|
||||||
|
timeout: timeout,
|
||||||
|
appID: 'IC Coder' // Windows 10/11 需要
|
||||||
|
};
|
||||||
|
|
||||||
|
// 发送通知
|
||||||
|
notifier.notify(notificationConfig, (err, response, metadata) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('[NotificationService] 通知发送失败:', err);
|
||||||
|
// 降级到 VS Code 内置通知
|
||||||
|
this.fallbackToVSCodeNotification(title, message, type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[NotificationService] 通知已发送:', response, metadata);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听通知点击事件
|
||||||
|
if (onClick) {
|
||||||
|
notifier.on('click', (notifierObject, options, event) => {
|
||||||
|
onClick();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 降级到 VS Code 内置通知
|
||||||
|
*/
|
||||||
|
private fallbackToVSCodeNotification(
|
||||||
|
title: string,
|
||||||
|
message: string,
|
||||||
|
type: NotificationType
|
||||||
|
): void {
|
||||||
|
const fullMessage = `${title}: ${message}`;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case NotificationType.ERROR:
|
||||||
|
vscode.window.showErrorMessage(fullMessage);
|
||||||
|
break;
|
||||||
|
case NotificationType.WARNING:
|
||||||
|
vscode.window.showWarningMessage(fullMessage);
|
||||||
|
break;
|
||||||
|
case NotificationType.SUCCESS:
|
||||||
|
case NotificationType.INFO:
|
||||||
|
default:
|
||||||
|
vscode.window.showInformationMessage(fullMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送成功通知
|
||||||
|
*/
|
||||||
|
public success(title: string, message: string, onClick?: () => void): void {
|
||||||
|
this.sendNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: NotificationType.SUCCESS,
|
||||||
|
sound: true,
|
||||||
|
timeout: 10,
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送错误通知
|
||||||
|
*/
|
||||||
|
public error(title: string, message: string, onClick?: () => void): void {
|
||||||
|
this.sendNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: NotificationType.ERROR,
|
||||||
|
sound: true,
|
||||||
|
timeout: 15,
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送警告通知
|
||||||
|
*/
|
||||||
|
public warning(title: string, message: string, onClick?: () => void): void {
|
||||||
|
this.sendNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: NotificationType.WARNING,
|
||||||
|
sound: true,
|
||||||
|
timeout: 10,
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送信息通知
|
||||||
|
*/
|
||||||
|
public info(title: string, message: string, onClick?: () => void): void {
|
||||||
|
this.sendNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: NotificationType.INFO,
|
||||||
|
sound: false,
|
||||||
|
timeout: 8,
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 在扩展入口初始化服务
|
||||||
|
|
||||||
|
修改 `src/extension.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { NotificationService } from './services/notificationService';
|
||||||
|
|
||||||
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
// 初始化通知服务
|
||||||
|
const notificationService = NotificationService.getInstance(context);
|
||||||
|
|
||||||
|
// ... 其他初始化代码
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 在消息处理器中使用
|
||||||
|
|
||||||
|
修改 `src/utils/messageHandler.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { NotificationService } from '../services/notificationService';
|
||||||
|
|
||||||
|
// 在适当的位置添加通知
|
||||||
|
export async function handleMessage(message: any, panel: vscode.WebviewPanel) {
|
||||||
|
const notificationService = NotificationService.getInstance();
|
||||||
|
|
||||||
|
// 示例:iverilog 仿真完成
|
||||||
|
if (message.type === 'simulationComplete') {
|
||||||
|
notificationService.success(
|
||||||
|
'IC Coder - 仿真完成',
|
||||||
|
'iverilog 仿真已成功完成,VCD 文件已生成',
|
||||||
|
() => {
|
||||||
|
// 点击通知时聚焦到 VS Code
|
||||||
|
vscode.window.showTextDocument(vscode.window.activeTextEditor!.document);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 示例:仿真失败
|
||||||
|
if (message.type === 'simulationError') {
|
||||||
|
notificationService.error(
|
||||||
|
'IC Coder - 仿真失败',
|
||||||
|
`仿真过程中发生错误: ${message.error}`,
|
||||||
|
() => {
|
||||||
|
// 点击通知时打开输出面板
|
||||||
|
panel.reveal();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 添加配置项
|
||||||
|
|
||||||
|
修改 `package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"contributes": {
|
||||||
|
"configuration": {
|
||||||
|
"title": "IC Coder",
|
||||||
|
"properties": {
|
||||||
|
"ic-coder.enableSystemNotification": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "启用系统级通知(任务完成时显示操作系统通知)"
|
||||||
|
},
|
||||||
|
"ic-coder.notificationSound": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "通知时播放系统声音"
|
||||||
|
},
|
||||||
|
"ic-coder.notificationTimeout": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 10,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 60,
|
||||||
|
"description": "通知自动消失时间(秒),0 表示不自动消失"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. API 设计
|
||||||
|
|
||||||
|
### 5.1 核心 API
|
||||||
|
|
||||||
|
#### `NotificationService.getInstance(context?)`
|
||||||
|
|
||||||
|
获取通知服务单例实例
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `context` (可选): `vscode.ExtensionContext` - 扩展上下文,首次调用时必须提供
|
||||||
|
|
||||||
|
**返回**: `NotificationService` 实例
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```typescript
|
||||||
|
const notificationService = NotificationService.getInstance(context);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `sendNotification(options)`
|
||||||
|
|
||||||
|
发送自定义通知
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `options`: `NotificationOptions` - 通知选项对象
|
||||||
|
|
||||||
|
**返回**: `void`
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```typescript
|
||||||
|
notificationService.sendNotification({
|
||||||
|
title: 'IC Coder',
|
||||||
|
message: '任务已完成',
|
||||||
|
type: NotificationType.SUCCESS,
|
||||||
|
sound: true,
|
||||||
|
timeout: 10,
|
||||||
|
onClick: () => {
|
||||||
|
console.log('用户点击了通知');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `success(title, message, onClick?)`
|
||||||
|
|
||||||
|
发送成功通知(快捷方法)
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `title`: `string` - 通知标题
|
||||||
|
- `message`: `string` - 通知消息
|
||||||
|
- `onClick` (可选): `() => void` - 点击回调
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```typescript
|
||||||
|
notificationService.success(
|
||||||
|
'IC Coder',
|
||||||
|
'VCD 文件生成成功',
|
||||||
|
() => panel.reveal()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `error(title, message, onClick?)`
|
||||||
|
|
||||||
|
发送错误通知(快捷方法)
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `title`: `string` - 通知标题
|
||||||
|
- `message`: `string` - 通知消息
|
||||||
|
- `onClick` (可选): `() => void` - 点击回调
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```typescript
|
||||||
|
notificationService.error(
|
||||||
|
'IC Coder',
|
||||||
|
'编译失败: 语法错误',
|
||||||
|
() => vscode.commands.executeCommand('workbench.action.showErrorsWarnings')
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `warning(title, message, onClick?)`
|
||||||
|
|
||||||
|
发送警告通知(快捷方法)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `info(title, message, onClick?)`
|
||||||
|
|
||||||
|
发送信息通知(快捷方法)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.2 类型定义
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
enum NotificationType {
|
||||||
|
INFO = 'info',
|
||||||
|
SUCCESS = 'success',
|
||||||
|
WARNING = 'warning',
|
||||||
|
ERROR = 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NotificationOptions {
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
type?: NotificationType;
|
||||||
|
sound?: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
icon?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 配置选项
|
||||||
|
|
||||||
|
### 6.1 用户配置项
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `ic-coder.enableSystemNotification` | `boolean` | `true` | 是否启用系统通知 |
|
||||||
|
| `ic-coder.notificationSound` | `boolean` | `true` | 是否播放通知声音 |
|
||||||
|
| `ic-coder.notificationTimeout` | `number` | `10` | 通知自动消失时间(秒) |
|
||||||
|
|
||||||
|
### 6.2 配置方式
|
||||||
|
|
||||||
|
#### 方式 1: VS Code 设置界面
|
||||||
|
|
||||||
|
1. 打开 VS Code 设置 (`Ctrl+,` / `Cmd+,`)
|
||||||
|
2. 搜索 "IC Coder"
|
||||||
|
3. 找到 "Enable System Notification" 选项
|
||||||
|
4. 勾选或取消勾选
|
||||||
|
|
||||||
|
#### 方式 2: settings.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ic-coder.enableSystemNotification": true,
|
||||||
|
"ic-coder.notificationSound": true,
|
||||||
|
"ic-coder.notificationTimeout": 10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 测试方案
|
||||||
|
|
||||||
|
### 7.1 单元测试
|
||||||
|
|
||||||
|
创建 `src/test/suite/notificationService.test.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { NotificationService, NotificationType } from '../../services/notificationService';
|
||||||
|
|
||||||
|
suite('NotificationService Test Suite', () => {
|
||||||
|
let notificationService: NotificationService;
|
||||||
|
|
||||||
|
suiteSetup(() => {
|
||||||
|
const context = {
|
||||||
|
extensionPath: __dirname
|
||||||
|
} as vscode.ExtensionContext;
|
||||||
|
notificationService = NotificationService.getInstance(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('应该成功创建单例实例', () => {
|
||||||
|
const instance1 = NotificationService.getInstance();
|
||||||
|
const instance2 = NotificationService.getInstance();
|
||||||
|
assert.strictEqual(instance1, instance2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('应该发送成功通知', (done) => {
|
||||||
|
notificationService.success('测试标题', '测试消息');
|
||||||
|
setTimeout(() => done(), 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 手动测试清单
|
||||||
|
|
||||||
|
#### Windows 测试
|
||||||
|
- [ ] 通知显示在 Action Center
|
||||||
|
- [ ] 点击通知能够聚焦到 VS Code
|
||||||
|
- [ ] 通知声音正常播放
|
||||||
|
- [ ] 通知图标正确显示
|
||||||
|
- [ ] 通知在设定时间后自动消失
|
||||||
|
- [ ] 禁用系统通知后不再显示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 注意事项
|
||||||
|
|
||||||
|
### 8.1 权限问题
|
||||||
|
|
||||||
|
**Windows**:
|
||||||
|
- 首次使用时,Windows 可能会弹出权限请求
|
||||||
|
- 用户需要在"设置 > 系统 > 通知和操作"中允许应用通知
|
||||||
|
|
||||||
|
**macOS**:
|
||||||
|
- 需要在"系统偏好设置 > 通知"中允许 VS Code 发送通知
|
||||||
|
|
||||||
|
**Linux**:
|
||||||
|
- 需要安装 `libnotify-bin` 包
|
||||||
|
- 不同桌面环境的通知样式可能不同
|
||||||
|
|
||||||
|
### 8.2 通知频率控制
|
||||||
|
|
||||||
|
为避免通知轰炸,建议实现防抖机制:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class NotificationService {
|
||||||
|
private lastNotificationTime: Map<string, number> = new Map();
|
||||||
|
private readonly DEBOUNCE_INTERVAL = 3000; // 3 秒
|
||||||
|
|
||||||
|
private shouldSendNotification(key: string): boolean {
|
||||||
|
const now = Date.now();
|
||||||
|
const lastTime = this.lastNotificationTime.get(key) || 0;
|
||||||
|
|
||||||
|
if (now - lastTime < this.DEBOUNCE_INTERVAL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastNotificationTime.set(key, now);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 错误处理
|
||||||
|
|
||||||
|
通知发送失败时,自动降级到 VS Code 内置通知。
|
||||||
|
|
||||||
|
### 8.4 安全考虑
|
||||||
|
|
||||||
|
- **不要在通知中显示敏感信息**(如 token、密码)
|
||||||
|
- **验证通知内容**,防止 XSS 攻击
|
||||||
|
- **限制通知频率**,防止滥用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 常见问题
|
||||||
|
|
||||||
|
### 9.1 通知不显示
|
||||||
|
|
||||||
|
**问题**: 调用通知 API 后,系统没有显示通知
|
||||||
|
|
||||||
|
**可能原因**:
|
||||||
|
1. 用户禁用了系统通知权限
|
||||||
|
2. 操作系统的"勿扰模式"已启用
|
||||||
|
3. `node-notifier` 安装失败或版本不兼容
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```typescript
|
||||||
|
// 添加调试日志
|
||||||
|
notifier.notify(notificationConfig, (err, response, metadata) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('[NotificationService] 错误:', err);
|
||||||
|
} else {
|
||||||
|
console.log('[NotificationService] 响应:', response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 通知点击回调不触发
|
||||||
|
|
||||||
|
**问题**: 点击通知后,`onClick` 回调没有执行
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```typescript
|
||||||
|
// 设置 wait: true
|
||||||
|
const notificationConfig: notifier.Notification = {
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
wait: true, // 等待用户交互
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 通知图标不显示
|
||||||
|
|
||||||
|
**问题**: 通知显示时没有自定义图标
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```typescript
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
// 检查图标是否存在
|
||||||
|
if (!fs.existsSync(this.iconPath)) {
|
||||||
|
console.warn(`图标文件不存在: ${this.iconPath}`);
|
||||||
|
this.iconPath = ''; // 使用系统默认图标
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.4 Linux 上通知不工作
|
||||||
|
|
||||||
|
**问题**: 在 Linux 系统上通知无法显示
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt-get install libnotify-bin
|
||||||
|
|
||||||
|
# Fedora/RHEL
|
||||||
|
sudo dnf install libnotify
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 最佳实践
|
||||||
|
|
||||||
|
### 10.1 通知时机
|
||||||
|
|
||||||
|
**推荐发送通知的场景**:
|
||||||
|
- ✅ 长时间运行的任务完成(> 10 秒)
|
||||||
|
- ✅ 后台任务完成(用户可能已切换到其他应用)
|
||||||
|
- ✅ 发生错误需要用户关注
|
||||||
|
- ✅ 重要状态变更
|
||||||
|
|
||||||
|
**不推荐发送通知的场景**:
|
||||||
|
- ❌ 即时完成的操作(< 3 秒)
|
||||||
|
- ❌ 用户主动触发且立即完成的操作
|
||||||
|
- ❌ 频繁发生的事件(如自动保存)
|
||||||
|
- ❌ 调试信息或日志
|
||||||
|
|
||||||
|
### 10.2 通知内容
|
||||||
|
|
||||||
|
**标题**:
|
||||||
|
- 简洁明了,不超过 20 个字符
|
||||||
|
- 包含应用名称(如 "IC Coder - 仿真完成")
|
||||||
|
- 使用动作完成时态("已完成" 而不是 "完成中")
|
||||||
|
|
||||||
|
**消息**:
|
||||||
|
- 提供具体信息,不超过 100 个字符
|
||||||
|
- 包含关键细节(如文件名、错误类型)
|
||||||
|
- 避免技术术语,使用用户友好的语言
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的通知
|
||||||
|
notificationService.success(
|
||||||
|
'IC Coder - 仿真完成',
|
||||||
|
'testbench.v 仿真成功,VCD 文件已生成'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ❌ 不好的通知
|
||||||
|
notificationService.success('完成', '操作已完成');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 通知优先级
|
||||||
|
|
||||||
|
根据重要性设置不同的通知类型和超时时间:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 高优先级:错误(15 秒)
|
||||||
|
notificationService.error(
|
||||||
|
'IC Coder - 编译失败',
|
||||||
|
'发现 3 个语法错误,请检查代码'
|
||||||
|
);
|
||||||
|
|
||||||
|
// 中优先级:警告(10 秒)
|
||||||
|
notificationService.warning(
|
||||||
|
'IC Coder - 警告',
|
||||||
|
'仿真时间过长,可能存在死循环'
|
||||||
|
);
|
||||||
|
|
||||||
|
// 低优先级:信息(8 秒,无声音)
|
||||||
|
notificationService.info(
|
||||||
|
'IC Coder - 提示',
|
||||||
|
'已自动保存工作区'
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 性能指标
|
||||||
|
|
||||||
|
### 11.1 预期性能
|
||||||
|
|
||||||
|
| 指标 | 目标值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 通知发送延迟 | < 100ms | 从调用到系统显示 |
|
||||||
|
| 内存占用 | < 5MB | 通知服务常驻内存 |
|
||||||
|
| CPU 占用 | < 1% | 空闲时 CPU 使用率 |
|
||||||
|
| 包体积增加 | ~500KB | node-notifier 依赖 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 参考资料
|
||||||
|
|
||||||
|
### 12.1 官方文档
|
||||||
|
|
||||||
|
- [node-notifier GitHub](https://github.com/mikaelbr/node-notifier)
|
||||||
|
- [VS Code Extension API](https://code.visualstudio.com/api)
|
||||||
|
- [Windows Toast Notifications](https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-notifications-overview)
|
||||||
|
|
||||||
|
### 12.2 相关文章
|
||||||
|
|
||||||
|
- [Best Practices for Desktop Notifications](https://web.dev/notifications/)
|
||||||
|
- [Designing Better Notifications](https://uxdesign.cc/designing-better-notifications-36ba9c0b3e0e)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 总结
|
||||||
|
|
||||||
|
本文档详细介绍了在 IC Coder 插件中实现系统级通知功能的完整方案,包括:
|
||||||
|
|
||||||
|
✅ **技术选型**: 选择 `node-notifier` 作为跨平台通知解决方案
|
||||||
|
✅ **架构设计**: 单例模式的通知服务类,支持多种通知类型
|
||||||
|
✅ **实现细节**: 完整的代码示例和配置说明
|
||||||
|
✅ **测试方案**: 单元测试、集成测试和手动测试清单
|
||||||
|
✅ **最佳实践**: 通知时机、内容设计和用户体验优化
|
||||||
|
✅ **故障排查**: 常见问题和解决方案
|
||||||
|
|
||||||
|
通过实现系统级通知,IC Coder 插件能够在用户切换到其他应用时仍然及时通知任务状态,显著提升用户体验。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**: v1.0
|
||||||
|
**最后更新**: 2026-01-26
|
||||||
|
**作者**: IC Coder Team
|
||||||
|
**许可**: MIT License
|
||||||
|
|
||||||
31
package.json
31
package.json
@ -49,6 +49,11 @@
|
|||||||
"command": "ic-coder.openVCDViewer",
|
"command": "ic-coder.openVCDViewer",
|
||||||
"title": "打开 VCD 波形查看器",
|
"title": "打开 VCD 波形查看器",
|
||||||
"category": "IC Coder"
|
"category": "IC Coder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "ic-coder.testNotification",
|
||||||
|
"title": "测试系统通知",
|
||||||
|
"category": "IC Coder"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"viewsContainers": {
|
"viewsContainers": {
|
||||||
@ -86,7 +91,29 @@
|
|||||||
],
|
],
|
||||||
"priority": "default"
|
"priority": "default"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"configuration": {
|
||||||
|
"title": "IC Coder",
|
||||||
|
"properties": {
|
||||||
|
"ic-coder.enableSystemNotification": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "启用系统级通知(任务完成时显示操作系统通知)"
|
||||||
|
},
|
||||||
|
"ic-coder.notificationSound": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "通知时播放系统声音"
|
||||||
|
},
|
||||||
|
"ic-coder.notificationTimeout": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 10,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 60,
|
||||||
|
"description": "通知自动消失时间(秒),0 表示不自动消失"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vscode:prepublish": "pnpm run package",
|
"vscode:prepublish": "pnpm run package",
|
||||||
@ -103,6 +130,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mocha": "^10.0.10",
|
"@types/mocha": "^10.0.10",
|
||||||
"@types/node": "22.x",
|
"@types/node": "22.x",
|
||||||
|
"@types/node-notifier": "^8.0.5",
|
||||||
"@types/vscode": "^1.80.0",
|
"@types/vscode": "^1.80.0",
|
||||||
"@vscode/test-cli": "^0.0.12",
|
"@vscode/test-cli": "^0.0.12",
|
||||||
"@vscode/test-electron": "^2.5.2",
|
"@vscode/test-electron": "^2.5.2",
|
||||||
@ -126,6 +154,7 @@
|
|||||||
"@wavedrom/doppler": "^1.14.0",
|
"@wavedrom/doppler": "^1.14.0",
|
||||||
"eventsource-parser": "^3.0.6",
|
"eventsource-parser": "^3.0.6",
|
||||||
"iconv-lite": "^0.7.1",
|
"iconv-lite": "^0.7.1",
|
||||||
|
"node-notifier": "^10.0.1",
|
||||||
"onml": "^2.1.0",
|
"onml": "^2.1.0",
|
||||||
"style-mod": "^4.1.3",
|
"style-mod": "^4.1.3",
|
||||||
"vcd-stream": "^1.5.0",
|
"vcd-stream": "^1.5.0",
|
||||||
|
|||||||
50
pnpm-lock.yaml
generated
50
pnpm-lock.yaml
generated
@ -17,6 +17,9 @@ importers:
|
|||||||
iconv-lite:
|
iconv-lite:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
|
node-notifier:
|
||||||
|
specifier: ^10.0.1
|
||||||
|
version: 10.0.1
|
||||||
onml:
|
onml:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
@ -39,6 +42,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 22.x
|
specifier: 22.x
|
||||||
version: 22.19.2
|
version: 22.19.2
|
||||||
|
'@types/node-notifier':
|
||||||
|
specifier: ^8.0.5
|
||||||
|
version: 8.0.5
|
||||||
'@types/vscode':
|
'@types/vscode':
|
||||||
specifier: ^1.80.0
|
specifier: ^1.80.0
|
||||||
version: 1.107.0
|
version: 1.107.0
|
||||||
@ -349,6 +355,9 @@ packages:
|
|||||||
'@types/mocha@10.0.10':
|
'@types/mocha@10.0.10':
|
||||||
resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==}
|
resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==}
|
||||||
|
|
||||||
|
'@types/node-notifier@8.0.5':
|
||||||
|
resolution: {integrity: sha512-LX7+8MtTsv6szumAp6WOy87nqMEdGhhry/Qfprjm1Ma6REjVzeF7SCyvPtp5RaF6IkXCS9V4ra8g5fwvf2ZAYg==}
|
||||||
|
|
||||||
'@types/node@22.19.2':
|
'@types/node@22.19.2':
|
||||||
resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==}
|
resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==}
|
||||||
|
|
||||||
@ -1185,6 +1194,9 @@ packages:
|
|||||||
graceful-fs@4.2.11:
|
graceful-fs@4.2.11:
|
||||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
|
|
||||||
|
growly@1.3.0:
|
||||||
|
resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==}
|
||||||
|
|
||||||
has-flag@4.0.0:
|
has-flag@4.0.0:
|
||||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -1284,6 +1296,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-docker@2.2.1:
|
||||||
|
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
is-docker@3.0.0:
|
is-docker@3.0.0:
|
||||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
@ -1342,6 +1359,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
|
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
is-wsl@2.2.0:
|
||||||
|
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
is-wsl@3.1.0:
|
is-wsl@3.1.0:
|
||||||
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@ -1622,6 +1643,9 @@ packages:
|
|||||||
node-addon-api@4.3.0:
|
node-addon-api@4.3.0:
|
||||||
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
|
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
|
||||||
|
|
||||||
|
node-notifier@10.0.1:
|
||||||
|
resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==}
|
||||||
|
|
||||||
node-releases@2.0.27:
|
node-releases@2.0.27:
|
||||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||||
|
|
||||||
@ -1919,6 +1943,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
shellwords@0.1.1:
|
||||||
|
resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==}
|
||||||
|
|
||||||
side-channel-list@1.0.0:
|
side-channel-list@1.0.0:
|
||||||
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -2702,6 +2729,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/mocha@10.0.10': {}
|
'@types/mocha@10.0.10': {}
|
||||||
|
|
||||||
|
'@types/node-notifier@8.0.5':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.19.2
|
||||||
|
|
||||||
'@types/node@22.19.2':
|
'@types/node@22.19.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 6.21.0
|
||||||
@ -3646,6 +3677,8 @@ snapshots:
|
|||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
|
|
||||||
|
growly@1.3.0: {}
|
||||||
|
|
||||||
has-flag@4.0.0: {}
|
has-flag@4.0.0: {}
|
||||||
|
|
||||||
has-symbols@1.1.0: {}
|
has-symbols@1.1.0: {}
|
||||||
@ -3737,6 +3770,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
|
|
||||||
|
is-docker@2.2.1: {}
|
||||||
|
|
||||||
is-docker@3.0.0: {}
|
is-docker@3.0.0: {}
|
||||||
|
|
||||||
is-extglob@2.1.1: {}
|
is-extglob@2.1.1: {}
|
||||||
@ -3771,6 +3806,10 @@ snapshots:
|
|||||||
|
|
||||||
is-unicode-supported@2.1.0: {}
|
is-unicode-supported@2.1.0: {}
|
||||||
|
|
||||||
|
is-wsl@2.2.0:
|
||||||
|
dependencies:
|
||||||
|
is-docker: 2.2.1
|
||||||
|
|
||||||
is-wsl@3.1.0:
|
is-wsl@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-inside-container: 1.0.0
|
is-inside-container: 1.0.0
|
||||||
@ -4074,6 +4113,15 @@ snapshots:
|
|||||||
node-addon-api@4.3.0:
|
node-addon-api@4.3.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
node-notifier@10.0.1:
|
||||||
|
dependencies:
|
||||||
|
growly: 1.3.0
|
||||||
|
is-wsl: 2.2.0
|
||||||
|
semver: 7.7.3
|
||||||
|
shellwords: 0.1.1
|
||||||
|
uuid: 8.3.2
|
||||||
|
which: 2.0.2
|
||||||
|
|
||||||
node-releases@2.0.27: {}
|
node-releases@2.0.27: {}
|
||||||
|
|
||||||
node-sarif-builder@3.3.1:
|
node-sarif-builder@3.3.1:
|
||||||
@ -4395,6 +4443,8 @@ snapshots:
|
|||||||
|
|
||||||
shebang-regex@3.0.0: {}
|
shebang-regex@3.0.0: {}
|
||||||
|
|
||||||
|
shellwords@0.1.1: {}
|
||||||
|
|
||||||
side-channel-list@1.0.0:
|
side-channel-list@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
|
|||||||
@ -8,10 +8,15 @@ import { VCDFileServer } from "./services/vcdFileServer";
|
|||||||
import { initUserService } from "./services/userService";
|
import { initUserService } from "./services/userService";
|
||||||
import { initCreditsService } from "./services/creditsService";
|
import { initCreditsService } from "./services/creditsService";
|
||||||
import { isTokenExpired } from "./utils/jwtUtils";
|
import { isTokenExpired } from "./utils/jwtUtils";
|
||||||
|
import { NotificationService } from "./services/notificationService";
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext) {
|
export async function activate(context: vscode.ExtensionContext) {
|
||||||
console.log("🎉 IC Coder 插件已激活!");
|
console.log("🎉 IC Coder 插件已激活!");
|
||||||
|
|
||||||
|
// 初始化通知服务
|
||||||
|
const notificationService = NotificationService.getInstance(context);
|
||||||
|
console.log('[Extension] 通知服务已初始化');
|
||||||
|
|
||||||
// 【关键】在创建 AuthProvider 之前,先检查并清除过期的 session
|
// 【关键】在创建 AuthProvider 之前,先检查并清除过期的 session
|
||||||
const storedSessions = context.globalState.get<any[]>('icCoderSessions', []);
|
const storedSessions = context.globalState.get<any[]>('icCoderSessions', []);
|
||||||
console.log('[Extension] 检查 sessions 数量:', storedSessions.length);
|
console.log('[Extension] 检查 sessions 数量:', storedSessions.length);
|
||||||
@ -191,6 +196,28 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 注册命令:测试系统通知
|
||||||
|
const testNotificationCommand = vscode.commands.registerCommand(
|
||||||
|
"ic-coder.testNotification",
|
||||||
|
() => {
|
||||||
|
console.log('[Extension] ========== 测试通知命令被调用 ==========');
|
||||||
|
|
||||||
|
// 先显示 VS Code 通知确认命令执行
|
||||||
|
vscode.window.showInformationMessage('正在测试系统通知...');
|
||||||
|
|
||||||
|
// 发送系统通知
|
||||||
|
notificationService.success(
|
||||||
|
'IC Coder - 测试通知',
|
||||||
|
'系统通知功能正常工作!',
|
||||||
|
() => {
|
||||||
|
vscode.window.showInformationMessage('您点击了系统通知!');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('[Extension] 测试通知命令执行完成');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 注册命令:查看会话历史
|
// 注册命令:查看会话历史
|
||||||
// TODO: 这些命令需要根据新的任务架构重新实现
|
// TODO: 这些命令需要根据新的任务架构重新实现
|
||||||
// 暂时注释掉,等待重新实现
|
// 暂时注释掉,等待重新实现
|
||||||
@ -256,6 +283,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
openVCDViewerInBrowserCommand,
|
openVCDViewerInBrowserCommand,
|
||||||
loginCommand,
|
loginCommand,
|
||||||
logoutCommand,
|
logoutCommand,
|
||||||
|
testNotificationCommand,
|
||||||
// TODO: 等待重新实现这些命令
|
// TODO: 等待重新实现这些命令
|
||||||
// viewHistoryCommand,
|
// viewHistoryCommand,
|
||||||
// newSessionCommand,
|
// newSessionCommand,
|
||||||
|
|||||||
257
src/services/notificationService.ts
Normal file
257
src/services/notificationService.ts
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
// 使用 require 导入 node-notifier
|
||||||
|
const notifier = require('node-notifier');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知类型枚举
|
||||||
|
*/
|
||||||
|
export enum NotificationType {
|
||||||
|
INFO = 'info',
|
||||||
|
SUCCESS = 'success',
|
||||||
|
WARNING = 'warning',
|
||||||
|
ERROR = 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知选项接口
|
||||||
|
*/
|
||||||
|
export interface NotificationOptions {
|
||||||
|
/** 通知标题 */
|
||||||
|
title: string;
|
||||||
|
/** 通知消息 */
|
||||||
|
message: string;
|
||||||
|
/** 通知类型 */
|
||||||
|
type?: NotificationType;
|
||||||
|
/** 是否播放声音 */
|
||||||
|
sound?: boolean;
|
||||||
|
/** 超时时间(秒),0 表示不自动消失 */
|
||||||
|
timeout?: number;
|
||||||
|
/** 自定义图标路径 */
|
||||||
|
icon?: string;
|
||||||
|
/** 点击通知时的回调 */
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统通知服务类
|
||||||
|
*/
|
||||||
|
export class NotificationService {
|
||||||
|
private static instance: NotificationService;
|
||||||
|
private readonly extensionPath: string;
|
||||||
|
private readonly iconPath: string;
|
||||||
|
private lastNotificationTime: Map<string, number> = new Map();
|
||||||
|
private readonly DEBOUNCE_INTERVAL = 3000; // 3 秒防抖
|
||||||
|
|
||||||
|
private constructor(context: vscode.ExtensionContext) {
|
||||||
|
this.extensionPath = context.extensionPath;
|
||||||
|
this.iconPath = path.join(this.extensionPath, 'media', 'icon.png');
|
||||||
|
console.log('[NotificationService] 初始化通知服务');
|
||||||
|
console.log('[NotificationService] 扩展路径:', this.extensionPath);
|
||||||
|
console.log('[NotificationService] 图标路径:', this.iconPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单例实例
|
||||||
|
*/
|
||||||
|
public static getInstance(context?: vscode.ExtensionContext): NotificationService {
|
||||||
|
if (!NotificationService.instance && context) {
|
||||||
|
NotificationService.instance = new NotificationService(context);
|
||||||
|
}
|
||||||
|
return NotificationService.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否启用系统通知
|
||||||
|
*/
|
||||||
|
private isSystemNotificationEnabled(): boolean {
|
||||||
|
const config = vscode.workspace.getConfiguration('ic-coder');
|
||||||
|
return config.get<boolean>('enableSystemNotification', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否应该发送通知(防抖)
|
||||||
|
*/
|
||||||
|
private shouldSendNotification(key: string): boolean {
|
||||||
|
const now = Date.now();
|
||||||
|
const lastTime = this.lastNotificationTime.get(key) || 0;
|
||||||
|
|
||||||
|
if (now - lastTime < this.DEBOUNCE_INTERVAL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastNotificationTime.set(key, now);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送系统通知
|
||||||
|
*/
|
||||||
|
public sendNotification(options: NotificationOptions): void {
|
||||||
|
console.log('[NotificationService] ========== 开始发送通知 ==========');
|
||||||
|
console.log('[NotificationService] 通知选项:', options);
|
||||||
|
|
||||||
|
// 检查用户配置
|
||||||
|
if (!this.isSystemNotificationEnabled()) {
|
||||||
|
console.log('[NotificationService] 系统通知已禁用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('[NotificationService] 系统通知已启用');
|
||||||
|
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type = NotificationType.INFO,
|
||||||
|
onClick
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// 防抖检查
|
||||||
|
const notificationKey = `${title}-${message}`;
|
||||||
|
if (!this.shouldSendNotification(notificationKey)) {
|
||||||
|
console.log('[NotificationService] 通知被防抖机制拦截');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('[NotificationService] 通过防抖检查');
|
||||||
|
|
||||||
|
// 使用 node-notifier 发送系统通知
|
||||||
|
console.log('[NotificationService] 使用 node-notifier 发送系统通知');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const notificationConfig: any = {
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
sound: true,
|
||||||
|
wait: false,
|
||||||
|
timeout: 10,
|
||||||
|
appID: 'IC Coder'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Windows 特定配置
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
notificationConfig.icon = this.iconPath;
|
||||||
|
console.log('[NotificationService] Windows 平台,图标路径:', this.iconPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[NotificationService] 通知配置:', notificationConfig);
|
||||||
|
|
||||||
|
notifier.notify(notificationConfig, (err: any, response: any, metadata: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('[NotificationService] ❌ node-notifier 失败:', err);
|
||||||
|
} else {
|
||||||
|
console.log('[NotificationService] ✅ node-notifier 成功');
|
||||||
|
console.log('[NotificationService] 响应:', response);
|
||||||
|
console.log('[NotificationService] 元数据:', metadata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onClick) {
|
||||||
|
notifier.on('click', () => {
|
||||||
|
console.log('[NotificationService] 用户点击了系统通知');
|
||||||
|
onClick();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[NotificationService] ❌ node-notifier 异常:', error);
|
||||||
|
// 如果系统通知失败,显示 VS Code 内置通知作为备用
|
||||||
|
console.log('[NotificationService] 系统通知失败,显示 VS Code 内置通知');
|
||||||
|
this.showVSCodeNotification(title, message, type, onClick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示 VS Code 内置通知
|
||||||
|
*/
|
||||||
|
private showVSCodeNotification(
|
||||||
|
title: string,
|
||||||
|
message: string,
|
||||||
|
type: NotificationType,
|
||||||
|
onClick?: () => void
|
||||||
|
): void {
|
||||||
|
const fullMessage = `${title}: ${message}`;
|
||||||
|
console.log('[NotificationService] 显示 VS Code 通知:', fullMessage);
|
||||||
|
|
||||||
|
let notificationPromise: Thenable<string | undefined>;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case NotificationType.ERROR:
|
||||||
|
notificationPromise = vscode.window.showErrorMessage(fullMessage, '查看详情');
|
||||||
|
break;
|
||||||
|
case NotificationType.WARNING:
|
||||||
|
notificationPromise = vscode.window.showWarningMessage(fullMessage, '查看详情');
|
||||||
|
break;
|
||||||
|
case NotificationType.SUCCESS:
|
||||||
|
case NotificationType.INFO:
|
||||||
|
default:
|
||||||
|
notificationPromise = vscode.window.showInformationMessage(fullMessage, '查看详情');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理点击事件
|
||||||
|
if (onClick) {
|
||||||
|
notificationPromise.then((selection) => {
|
||||||
|
if (selection === '查看详情') {
|
||||||
|
console.log('[NotificationService] 用户点击了通知');
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送成功通知
|
||||||
|
*/
|
||||||
|
public success(title: string, message: string, onClick?: () => void): void {
|
||||||
|
this.sendNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: NotificationType.SUCCESS,
|
||||||
|
sound: true,
|
||||||
|
timeout: 10,
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送错误通知
|
||||||
|
*/
|
||||||
|
public error(title: string, message: string, onClick?: () => void): void {
|
||||||
|
this.sendNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: NotificationType.ERROR,
|
||||||
|
sound: true,
|
||||||
|
timeout: 15,
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送警告通知
|
||||||
|
*/
|
||||||
|
public warning(title: string, message: string, onClick?: () => void): void {
|
||||||
|
this.sendNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: NotificationType.WARNING,
|
||||||
|
sound: true,
|
||||||
|
timeout: 10,
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送信息通知
|
||||||
|
*/
|
||||||
|
public info(title: string, message: string, onClick?: () => void): void {
|
||||||
|
this.sendNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: NotificationType.INFO,
|
||||||
|
sound: false,
|
||||||
|
timeout: 8,
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -24,6 +24,7 @@ import {
|
|||||||
fetchBalance,
|
fetchBalance,
|
||||||
} from "../services/creditsService";
|
} from "../services/creditsService";
|
||||||
import { optimizePrompt } from "../services/promptOptimizeService";
|
import { optimizePrompt } from "../services/promptOptimizeService";
|
||||||
|
import { NotificationService } from "../services/notificationService";
|
||||||
|
|
||||||
import type { RunMode, ServiceTier } from "../types/api";
|
import type { RunMode, ServiceTier } from "../types/api";
|
||||||
|
|
||||||
@ -333,6 +334,17 @@ async function handleUserMessageWithBackend(
|
|||||||
isComplete: true,
|
isComplete: true,
|
||||||
});
|
});
|
||||||
console.log("[MessageHandler] postMessage 返回值:", result);
|
console.log("[MessageHandler] postMessage 返回值:", result);
|
||||||
|
|
||||||
|
// 发送系统通知 - AI 响应完成
|
||||||
|
const notificationService = NotificationService.getInstance();
|
||||||
|
notificationService.success(
|
||||||
|
'IC Coder - AI 响应完成',
|
||||||
|
'您的问题已得到回复,点击查看详情',
|
||||||
|
() => {
|
||||||
|
// 点击通知时聚焦到面板
|
||||||
|
panel.reveal();
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("[MessageHandler] 更新面板失败(面板可能已关闭):", error);
|
console.warn("[MessageHandler] 更新面板失败(面板可能已关闭):", error);
|
||||||
}
|
}
|
||||||
@ -845,6 +857,16 @@ export async function handleCreateFile(
|
|||||||
message: " 文件创建成功",
|
message: " 文件创建成功",
|
||||||
});
|
});
|
||||||
vscode.window.showInformationMessage(`文件创建成功: ${filePath}`);
|
vscode.window.showInformationMessage(`文件创建成功: ${filePath}`);
|
||||||
|
|
||||||
|
// 发送系统通知
|
||||||
|
const notificationService = NotificationService.getInstance();
|
||||||
|
notificationService.success(
|
||||||
|
'IC Coder - 文件创建',
|
||||||
|
`文件已创建: ${path.basename(filePath)}`,
|
||||||
|
() => {
|
||||||
|
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath));
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "fileCreateError",
|
command: "fileCreateError",
|
||||||
@ -872,6 +894,13 @@ export async function handleUpdateFile(
|
|||||||
message: " 文件更新成功",
|
message: " 文件更新成功",
|
||||||
});
|
});
|
||||||
vscode.window.showInformationMessage(`文件更新成功: ${filePath}`);
|
vscode.window.showInformationMessage(`文件更新成功: ${filePath}`);
|
||||||
|
|
||||||
|
// 发送系统通知
|
||||||
|
const notificationService = NotificationService.getInstance();
|
||||||
|
notificationService.info(
|
||||||
|
'IC Coder - 文件更新',
|
||||||
|
`文件已更新: ${path.basename(filePath)}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "fileUpdateError",
|
command: "fileUpdateError",
|
||||||
@ -1079,6 +1108,17 @@ async function handleVCDGeneration(
|
|||||||
});
|
});
|
||||||
|
|
||||||
vscode.window.showInformationMessage(`VCD 文件生成成功: ${fileName}`);
|
vscode.window.showInformationMessage(`VCD 文件生成成功: ${fileName}`);
|
||||||
|
|
||||||
|
// 发送系统通知
|
||||||
|
const notificationService = NotificationService.getInstance();
|
||||||
|
notificationService.success(
|
||||||
|
'IC Coder - 仿真完成',
|
||||||
|
`VCD 文件已生成: ${fileName}`,
|
||||||
|
() => {
|
||||||
|
// 点击通知时打开 VCD 查看器
|
||||||
|
vscode.commands.executeCommand('ic-coder.openVCDViewer', result.vcdFilePath);
|
||||||
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "receiveMessage",
|
command: "receiveMessage",
|
||||||
@ -1102,6 +1142,17 @@ async function handleVCDGeneration(
|
|||||||
});
|
});
|
||||||
|
|
||||||
vscode.window.showErrorMessage("VCD 文件生成失败");
|
vscode.window.showErrorMessage("VCD 文件生成失败");
|
||||||
|
|
||||||
|
// 发送系统通知
|
||||||
|
const notificationService = NotificationService.getInstance();
|
||||||
|
notificationService.error(
|
||||||
|
'IC Coder - 仿真失败',
|
||||||
|
'VCD 文件生成失败,请查看错误信息',
|
||||||
|
() => {
|
||||||
|
// 点击通知时聚焦到面板
|
||||||
|
panel.reveal();
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = `❌ 生成 VCD 文件时出错: ${
|
const errorMsg = `❌ 生成 VCD 文件时出错: ${
|
||||||
@ -1114,6 +1165,16 @@ async function handleVCDGeneration(
|
|||||||
});
|
});
|
||||||
|
|
||||||
vscode.window.showErrorMessage(errorMsg);
|
vscode.window.showErrorMessage(errorMsg);
|
||||||
|
|
||||||
|
// 发送系统通知
|
||||||
|
const notificationService = NotificationService.getInstance();
|
||||||
|
notificationService.error(
|
||||||
|
'IC Coder - 仿真错误',
|
||||||
|
error instanceof Error ? error.message : '生成 VCD 文件时出错',
|
||||||
|
() => {
|
||||||
|
panel.reveal();
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,8 @@ const extensionConfig = {
|
|||||||
libraryTarget: 'commonjs2'
|
libraryTarget: 'commonjs2'
|
||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
|
vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
|
||||||
|
'node-notifier': 'commonjs node-notifier' // node-notifier 依赖原生模块,必须排除
|
||||||
// modules added here also need to be added in the .vscodeignore file
|
// modules added here also need to be added in the .vscodeignore file
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
Reference in New Issue
Block a user