Files
IC-Coder-Plugin/docs/system-notification-implementation.md
Roe-xin 885e2cef75 feat:实现Windows系统通知功能
- 集成node-notifier实现跨平台系统通知
   - AI响应完成时自动弹出Windows Toast通知
   - 支持通知防抖机制,避免频繁弹窗
   - 添加通知配置项:启用/禁用、声音、超时时间
   - 移除VS Code内置弹窗,仅在系统通知失败时作为备用
2026-01-26 22:44:17 +08:00

23 KiB
Raw Permalink Blame History

IC Coder 系统通知功能实现方案

目录


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 APIVS 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-sendlibnotify
  • 显示位置取决于桌面环境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 设置界面

  1. 打开 VS Code 设置 (Ctrl+, / Cmd+,)
  2. 搜索 "IC Coder"
  3. 找到 "Enable System Notification" 选项
  4. 勾选或取消勾选

方式 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 后,系统没有显示通知

可能原因:

  1. 用户禁用了系统通知权限
  2. 操作系统的"勿扰模式"已启用
  3. 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