- 集成node-notifier实现跨平台系统通知 - AI响应完成时自动弹出Windows Toast通知 - 支持通知防抖机制,避免频繁弹窗 - 添加通知配置项:启用/禁用、声音、超时时间 - 移除VS Code内置弹窗,仅在系统通知失败时作为备用
23 KiB
IC Coder 系统通知功能实现方案
目录
1. 需求背景
1.1 问题描述
当前 IC Coder 插件使用 VS Code 内置的通知 API (vscode.window.showInformationMessage) 来提示用户任务完成。这种方式存在以下问题:
- 可见性问题: 用户切换到其他应用时,无法看到 VS Code 内部的通知
- 错过通知: 长时间运行的任务(如 iverilog 仿真)完成时,用户可能已经离开 VS Code
- 用户体验: 需要用户主动回到 VS Code 才能知道任务状态
1.2 目标
实现系统级通知功能,使得:
- 用户在任何应用中都能收到任务完成通知
- 通知显示在操作系统的通知中心(Windows Action Center / macOS Notification Center / Linux notify-send)
- 支持自定义通知内容、图标、声音
- 用户可以配置是否启用系统通知
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 的理由
- 成熟稳定: 被广泛使用(npm 周下载量 > 200 万)
- 跨平台: 自动适配不同操作系统的通知机制
- 功能丰富: 支持图标、声音、操作按钮、回调
- 易于集成: 与 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 安装依赖
# 安装 node-notifier
pnpm add node-notifier
# 安装类型定义
pnpm add -D @types/node-notifier
4.2 创建通知服务模块
创建 src/services/notificationService.ts
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
import { NotificationService } from './services/notificationService';
export function activate(context: vscode.ExtensionContext) {
// 初始化通知服务
const notificationService = NotificationService.getInstance(context);
// ... 其他初始化代码
}
4.4 在消息处理器中使用
修改 src/utils/messageHandler.ts
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
{
"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 实例
示例:
const notificationService = NotificationService.getInstance(context);
sendNotification(options)
发送自定义通知
参数:
options:NotificationOptions- 通知选项对象
返回: void
示例:
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- 点击回调
示例:
notificationService.success(
'IC Coder',
'VCD 文件生成成功',
() => panel.reveal()
);
error(title, message, onClick?)
发送错误通知(快捷方法)
参数:
title:string- 通知标题message:string- 通知消息onClick(可选):() => void- 点击回调
示例:
notificationService.error(
'IC Coder',
'编译失败: 语法错误',
() => vscode.commands.executeCommand('workbench.action.showErrorsWarnings')
);
warning(title, message, onClick?)
发送警告通知(快捷方法)
info(title, message, onClick?)
发送信息通知(快捷方法)
5.2 类型定义
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 设置界面
- 打开 VS Code 设置 (
Ctrl+,/Cmd+,) - 搜索 "IC Coder"
- 找到 "Enable System Notification" 选项
- 勾选或取消勾选
方式 2: settings.json
{
"ic-coder.enableSystemNotification": true,
"ic-coder.notificationSound": true,
"ic-coder.notificationTimeout": 10
}
7. 测试方案
7.1 单元测试
创建 src/test/suite/notificationService.test.ts
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 通知频率控制
为避免通知轰炸,建议实现防抖机制:
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 后,系统没有显示通知
可能原因:
- 用户禁用了系统通知权限
- 操作系统的"勿扰模式"已启用
node-notifier安装失败或版本不兼容
解决方案:
// 添加调试日志
notifier.notify(notificationConfig, (err, response, metadata) => {
if (err) {
console.error('[NotificationService] 错误:', err);
} else {
console.log('[NotificationService] 响应:', response);
}
});
9.2 通知点击回调不触发
问题: 点击通知后,onClick 回调没有执行
解决方案:
// 设置 wait: true
const notificationConfig: notifier.Notification = {
title: title,
message: message,
wait: true, // 等待用户交互
};
9.3 通知图标不显示
问题: 通知显示时没有自定义图标
解决方案:
import * as fs from 'fs';
// 检查图标是否存在
if (!fs.existsSync(this.iconPath)) {
console.warn(`图标文件不存在: ${this.iconPath}`);
this.iconPath = ''; // 使用系统默认图标
}
9.4 Linux 上通知不工作
问题: 在 Linux 系统上通知无法显示
解决方案:
# 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 个字符
- 包含关键细节(如文件名、错误类型)
- 避免技术术语,使用用户友好的语言
示例:
// ✅ 好的通知
notificationService.success(
'IC Coder - 仿真完成',
'testbench.v 仿真成功,VCD 文件已生成'
);
// ❌ 不好的通知
notificationService.success('完成', '操作已完成');
10.3 通知优先级
根据重要性设置不同的通知类型和超时时间:
// 高优先级:错误(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 官方文档
12.2 相关文章
13. 总结
本文档详细介绍了在 IC Coder 插件中实现系统级通知功能的完整方案,包括:
✅ 技术选型: 选择 node-notifier 作为跨平台通知解决方案
✅ 架构设计: 单例模式的通知服务类,支持多种通知类型
✅ 实现细节: 完整的代码示例和配置说明
✅ 测试方案: 单元测试、集成测试和手动测试清单
✅ 最佳实践: 通知时机、内容设计和用户体验优化
✅ 故障排查: 常见问题和解决方案
通过实现系统级通知,IC Coder 插件能够在用户切换到其他应用时仍然及时通知任务状态,显著提升用户体验。
文档版本: v1.0 最后更新: 2026-01-26 作者: IC Coder Team 许可: MIT License