写点什么

插件开发实录:我用 Comate 在 VS Code 里造了一场“能被代码融化”的初雪

  • 2025-12-22
    北京
  • 本文字数:13542 字

    阅读完需:约 44 分钟

插件开发实录:我用Comate在VS Code里造了一场“能被代码融化”的初雪

2025 年的第一场雪,我是在报错日志里度过的😭

朋友圈在晒雪景,我在盯着 VS Code 万年不变的界面发呆。

既然错过了现实里的初雪,那我为什么不能在我的 IDE 里下一场雪?

于是,一个极其离谱又带感的脑洞诞生了——我想做一个 VS Code 插件,实现:

  • 落雪:当我停下来思考或者单纯摸鱼时,让屏幕飘落初雪,积雪慢慢覆盖代码,假装我也在过冬。

  • 燃火:一旦我开始狂修 Bug,指尖的每一次敲击都要在屏幕底部点燃烈火。手速越快,火势越旺,甚至要让火星子溅满整个屏幕,主打一个物理取暖和辛劳可视化。

  • 互动:圣诞节快到了,在没有操作的时候,跳出 NPC 和我互动,增添圣诞氛围。

想法很丰满,现实很骨感。

要在 VS Code 极其受限的 Webview 环境里,同时跑通物理积雪堆叠算法和 Doom Fire 火焰渲染,还要保证不卡顿,这对我这个只有碎片时间的开发者来说,简直是降维打击。

好在,这个冬天,虽然没有女朋友暖手,但我有随叫随到的 文心快码(Comate) 暖心🐶


01 智能规划:从一句话需求到 MVP 落地

项目启动阶段,我面临的最大挑战是架构设计。VS Code 插件开发涉及 Extension 主进程与 Webview 渲染进程的通信,配置繁琐且容易出错。这次我没有直接写代码,而是使用了 Comate 的 Spec 模式。

我向 Comate 抛出了我的核心构想:

“我要做一个 VS Code 插件,核心逻辑是监听键盘输入。输入频率高时底部渲染火焰,闲置时顶部下雪并积雪。请帮我设计架构并生成代码。”

Comate 迅速进入了需求分析模式,几秒钟后,它返还了一份结构完整的 FrostFire 插件需求文档。我仔细审视了这份文档,发现大框架非常完美,逻辑层和渲染层分得清清楚楚。

插件需求文档见文章末尾【附录 1】

为了把错误拦截在开发之前,我基于这份文档进行了一些微调:

  • package.json:我补充了 activationEvents 配置,确保插件在打开任何文件时都能自动激活,而不仅仅是特定语言。

  • src/webview/index.html:我特别强调了要配置 CSP 策略,并将背景强制设为纯黑,以配合 Canvas 的渲染效果。

确认修改后,Comate 自动根据需求文档拆解出了具体的开发任务列表(Tasks),从环境配置到核心算法实现,条理清晰。

插件开发任务计划见文章末尾【附录 2】

随着一个个 Task 被自动执行,仅仅 10 分钟,FrostFire 的 V1.0 版本(MVP)诞生了。

生成完毕后,Comate 还给出了贴心的下一步引导,清晰地告诉了我们如何迁移到 VS Code 里使用插件⬇️

我打开 VS Code,按照它说的一步步做,屏幕上真的燃起了火焰!

👉观看燃起火焰效果视频https://mp.weixin.qq.com/s/zDAbDvEc-2-8yKNBEeY86A

对了,VS Code 里也支持文心快码插件~插件市场搜索文心快码,即可下载~

文心快码插件下载下来后,会自动出生在左边,有点和文件目录重合了。没关系,我们可以点击左侧项目栏中文心快码标识,把它拖到右边

👉观看具体操作视频https://mp.weixin.qq.com/s/zDAbDvEc-2-8yKNBEeY86A


02 核心维稳:用“记忆”根除鬼火 Bug

然而,V1 版本很快暴露出了严重的稳定性问题——幽灵火。

症状非常诡异:

  1. 有时候我明明双手离开了键盘,屏幕上的火却突然烧了起来。

  2. 刚打开 VS Code,还没开始工作,火焰就直接铺满了屏幕。

经过排查,原来是后端的心跳包和自动保存机制干扰了 idleTime 的计算,导致系统误判我有输入。

为了解决这个问题,我与 Comate 进行了多次深度的 Debug 交互。最终,我们共同制定了一套基于趋势判定的“安全栓”逻辑:只有当监测到闲置时间出现骤降(时间倒流)时,才认定为有效输入,绝不能仅依赖数值大小来判断。

这段逻辑非常关键,为了防止在未来的迭代中 Comate 忘记这个核心规则,我使用了 Comate 的 Memory(记忆) 功能。

我在 Memory 设置中手动录入了一条核心指令:

“在 FrostFire 项目中,必须始终保留基于‘安全栓’(如 hasWitnessedDrop 变量)的逻辑:只有当检测到 idleTime 确实发生下降(current < prev)时才允许解锁点火功能,绝对不能仅依赖 idleTime 的绝对数值来判定打字状态,以防止‘刚打开或静止时自动起火’的 Bug 复发。”

这一步操作至关重要。 从此之后,无论我要求 Comate 如何重构代码,它都会死死守住这条“安全底线”。

当 Comate 生成了最终修复版的代码后,我点击了对话框下方的 “赞” 按钮反馈了满意度,并使用了 “全文复制”功能,一键将这段复杂的逻辑同步到了我的项目中。


03 迭代开发:从“能用”到“惊艳”

搞定了稳定性之后,FrostFire 虽然能跑了,但看起来还很廉价。我决定给它注入灵魂,开启了三次关键的迭代。


迭代一:挑战“物理积雪”算法

我希望雪花落下时,能像真实的沙堆一样,形成中间高、两边低的自然坡度,而不是像水一样平铺。但这需要编写复杂的“休止角”算法,涉及大量的数据结构计算。

我向 Comate 描述了需求:

“我需要积雪堆叠的感觉,最高堆叠到屏幕最上方。”

Comate 完美执行了我的指令。它在 snowEffect.js 中重构了数据结构,引入了一个双向平滑算法。 现在的积雪,不仅有自然的起伏,甚至能把代码编辑器底部的状态栏慢慢“埋”起来,那种沉浸感简直绝了。


迭代二:手感调优与温和模式

V1 的火焰太暴躁了,稍微打几个字就满屏火光。我希望它能更优雅一些,引入一种温和模式:平时只是小火苗,只有在疯狂输出时才会有火星四溅的效果。而且,单纯的“不打字下雪,打字起火”太生硬了。我想要一种线性的对抗感。

怕智能体乱改改错,我换成了 Ask 智能体,觉得 AI 说的有道理,就点插入,代码就自动插入到了我的光标位置,体验感十分丝滑。


迭代三:节日限定的浪漫

既然是圣诞特供,怎么能少了节日气氛?

我给 Comate 下达了新的增量需求:

“我需要增加两个彩蛋。第一,当积雪变厚时,雪地里要钻出一个雪人;一旦我开始打字起火,雪人要表现得很惊恐并逃跑。第二,在火势旺盛时,偶尔要有圣诞老人坐雪橇飞过,投递礼物。”

Comate 的表现令人惊喜:

  • 怕热的雪人:它设计了一个简易的状态机。雪人平时在发呆,检测到热量上升时,会绘制出流汗和颤抖的动画,并加速移出屏幕。

  • 圣诞空投:它完美实现了雪橇的飞行轨迹和礼物盒的物理抛物线。

最让我感动的是,Comate 还记得我之前的 Memory。在实现这些新功能时,它依然严格遵守了“安全栓”逻辑,确保雪人和圣诞老人不会因为误触而乱跑。

最后,让我们一起来看看最终版本视频🤩:https://mp.weixin.qq.com/s/zDAbDvEc-2-8yKNBEeY86A


04 总结:AI 时代的开发新范式

回看 FrostFire 的开发历程,从解决底层的“幽灵火”逻辑漏洞,到实现细腻的物理积雪,再到充满创意的圣诞彩蛋,文心快码(Comate) 展现出的能力远超一个代码补全工具。

  • Spec 模式 让架构设计不再是难题,将我的抽象想法快速转化为可执行的代码框架。

  • Memory 机制 解决了 AI 容易“遗忘上下文”的痛点,让它成为了一个越用越懂我的专属工程师。

  • 流畅的交互体验(如可编辑输入、一键复制)则极大地降低了沟通成本,让开发过程如丝般顺滑。

现在的 FrostFire,已经不仅仅是一个插件,它是我在 Comate 协助下,送给自己和所有开发者的一份冬日礼物。

在这个项目中,我只需负责构想和决策,而那些繁琐的实现细节,全部交给了 AI。

如果你也想体验这种心想事成的开发快感,不妨试试让文心快码成为你的 AI 编程助手。或许你的下一个脑洞,就是下一个爆款。


【附录 1】插件需求文档

# FrostFire VS Code 插件需求文档
## 1. 项目概述
**FrostFire** 是一个增加编程趣味性的 VS Code 视觉增强插件,通过在编辑器中渲染动态视觉效果(火焰和雪花),让编程体验更加生动有趣。
## 2. 核心功能
### 2.1 Fire 状态(火焰效果)- **触发条件**:用户键盘输入频率(Keystrokes per minute, KPM)较高时触发- **视觉效果**:编辑器底部渲染像素风格火焰(基于 Doom Fire Algorithm)- **动态响应**:输入越快,火焰越高越旺盛- **热度计算**:根据最近一段时间内的按键频率计算"热度值"
### 2.2 Ice 状态(雪花效果)- **触发条件**:用户停止输入超过 15 秒- **视觉效果**:编辑器顶部开始下雪- **积雪机制**:停止输入超过 60 秒后,雪花在底部积累形成积雪层- **遮挡效果**:积雪可轻微遮挡代码区域,增强沉浸感
### 2.3 状态切换- Fire 和 Ice 状态互斥,不会同时出现- 用户开始输入时,雪花效果逐渐消失,火焰效果逐渐出现- 状态切换有平滑过渡动画
## 3. 技术架构
### 3.1 技术栈- **语言**:TypeScript- **API**:VS Code Extension API- **渲染**:HTML5 Canvas(在 Webview 中运行)
### 3.2 项目目录结构```frostfire/├── .vscode/│ └── launch.json # 调试配置├── src/│ ├── extension.ts # 插件入口,注册命令和监听器│ ├── activityTracker.ts # 用户活跃度追踪器│ ├── webviewProvider.ts # Webview 面板管理│ └── webview/│ ├── index.html # Webview HTML 模板│ ├── main.js # Webview 主逻辑│ ├── fireEffect.js # Doom Fire 火焰算法实现│ └── snowEffect.js # 雪花和积雪效果实现├── package.json # 插件配置清单├── tsconfig.json # TypeScript 配置└── README.md # 项目说明```
### 3.3 架构设计
```┌─────────────────────────────────────────────────────────────┐│ VS Code Extension Host │├─────────────────────────────────────────────────────────────┤│ extension.ts ││ ┌─────────────────┐ ┌──────────────────┐ ││ │ ActivityTracker │───▶│ WebviewProvider │ ││ │ - 监听文档变化 │ │ - 管理 Webview │ ││ │ - 计算热度值 │ │ - 发送状态消息 │ ││ │ - 判断状态切换 │ │ │ ││ └─────────────────┘ └────────┬─────────┘ ││ │ postMessage │└──────────────────────────────────┼──────────────────────────┘┌─────────────────────────────────────────────────────────────┐│ Webview (Canvas) │├─────────────────────────────────────────────────────────────┤│ ┌─────────────────┐ ┌──────────────────┐ ││ │ FireEffect │ │ SnowEffect │ ││ │ - Doom Fire算法 │ │ - 雪花粒子系统 │ ││ │ - 火焰高度控制 │ │ - 积雪累积逻辑 │ ││ └─────────────────┘ └──────────────────┘ ││ main.js (消息调度) │└─────────────────────────────────────────────────────────────┘```
## 4. 实现细节
### 4.1 ActivityTracker(活跃度追踪器)
```typescript// src/activityTracker.tsexport class ActivityTracker { private keystrokeTimestamps: number[] = []; // 记录按键时间戳 private readonly WINDOW_SIZE = 60000; // 统计窗口:60秒 private readonly IDLE_THRESHOLD = 15000; // 空闲阈值:15秒 private readonly SNOW_ACCUMULATE_THRESHOLD = 60000; // 积雪阈值:60秒 // 记录一次按键 public recordKeystroke(): void { const now = Date.now(); this.keystrokeTimestamps.push(now); this.cleanOldTimestamps(now); } // 清理过期的时间戳 private cleanOldTimestamps(now: number): void { this.keystrokeTimestamps = this.keystrokeTimestamps.filter( ts => now - ts < this.WINDOW_SIZE ); } // 计算当前热度值 (0-100) public getHeatLevel(): number { const now = Date.now(); this.cleanOldTimestamps(now); // 基于最近10秒的按键数计算热度 const recentKeystrokes = this.keystrokeTimestamps.filter( ts => now - ts < 10000 ).length; // 假设每秒6次按键为满热度 return Math.min(100, (recentKeystrokes / 60) * 100); } // 获取空闲时间(毫秒) public getIdleTime(): number { if (this.keystrokeTimestamps.length === 0) return Infinity; const lastKeystroke = Math.max(...this.keystrokeTimestamps); return Date.now() - lastKeystroke; } // 判断当前状态 public getCurrentState(): 'fire' | 'idle' | 'snow' | 'snow_accumulate' { const idleTime = this.getIdleTime(); if (idleTime >= this.SNOW_ACCUMULATE_THRESHOLD) return 'snow_accumulate'; if (idleTime >= this.IDLE_THRESHOLD) return 'snow'; if (this.getHeatLevel() > 10) return 'fire'; return 'idle'; }}```
### 4.2 Extension 核心逻辑
```typescript// src/extension.tsimport * as vscode from 'vscode';import { ActivityTracker } from './activityTracker';import { FrostFireWebviewProvider } from './webviewProvider';
let activityTracker: ActivityTracker;let webviewProvider: FrostFireWebviewProvider;let updateInterval: NodeJS.Timeout;
export function activate(context: vscode.ExtensionContext) { activityTracker = new ActivityTracker(); webviewProvider = new FrostFireWebviewProvider(context.extensionUri); // 注册 Webview 视图 context.subscriptions.push( vscode.window.registerWebviewViewProvider( 'frostfire.effectView', webviewProvider ) ); // 注册启动命令 context.subscriptions.push( vscode.commands.registerCommand('frostfire.start', () => { vscode.commands.executeCommand('frostfire.effectView.focus'); }) ); // 监听文档变化(用户输入) context.subscriptions.push( vscode.workspace.onDidChangeTextDocument((event) => { // 只统计实际的文本变化 if (event.contentChanges.length > 0) { activityTracker.recordKeystroke(); } }) ); // 定时更新状态到 Webview updateInterval = setInterval(() => { const state = activityTracker.getCurrentState(); const heatLevel = activityTracker.getHeatLevel(); const idleTime = activityTracker.getIdleTime(); webviewProvider.postMessage({ type: 'stateUpdate', state: state, heatLevel: heatLevel, idleTime: idleTime }); }, 100); // 每100ms更新一次 context.subscriptions.push({ dispose: () => clearInterval(updateInterval) });}
export function deactivate() { if (updateInterval) { clearInterval(updateInterval); }}```
### 4.3 Doom Fire 火焰算法
```javascript// src/webview/fireEffect.jsclass FireEffect { constructor(canvas) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.fireWidth = 80; // 火焰像素宽度 this.fireHeight = 50; // 火焰像素高度 this.firePixels = []; this.fireColorPalette = this.createPalette(); this.intensity = 0; // 火焰强度 0-100 this.initFire(); } // 创建火焰颜色调色板(36色) createPalette() { return [ {r:7,g:7,b:7}, {r:31,g:7,b:7}, {r:47,g:15,b:7}, {r:71,g:15,b:7}, {r:87,g:23,b:7}, {r:103,g:31,b:7}, {r:119,g:31,b:7}, {r:143,g:39,b:7}, {r:159,g:47,b:7}, {r:175,g:63,b:7}, {r:191,g:71,b:7}, {r:199,g:71,b:7}, {r:223,g:79,b:7}, {r:223,g:87,b:7}, {r:223,g:87,b:7}, {r:215,g:95,b:7}, {r:215,g:103,b:15}, {r:207,g:111,b:15}, {r:207,g:119,b:15}, {r:207,g:127,b:15}, {r:207,g:135,b:23}, {r:199,g:135,b:23}, {r:199,g:143,b:23}, {r:199,g:151,b:31}, {r:191,g:159,b:31}, {r:191,g:159,b:31}, {r:191,g:167,b:39}, {r:191,g:167,b:39}, {r:191,g:175,b:47}, {r:183,g:175,b:47}, {r:183,g:183,b:47}, {r:183,g:183,b:55}, {r:207,g:207,b:111}, {r:223,g:223,b:159}, {r:239,g:239,b:199}, {r:255,g:255,b:255} ]; } // 初始化火焰数组 initFire() { const totalPixels = this.fireWidth * this.fireHeight; this.firePixels = new Array(totalPixels).fill(0); } // 设置火焰强度 setIntensity(level) { this.intensity = Math.max(0, Math.min(100, level)); // 根据强度设置底部火源 const maxColorIndex = Math.floor((this.intensity / 100) * 35); for (let x = 0; x < this.fireWidth; x++) { const bottomIndex = (this.fireHeight - 1) * this.fireWidth + x; this.firePixels[bottomIndex] = this.intensity > 5 ? maxColorIndex : 0; } } // 火焰传播算法 spreadFire() { for (let x = 0; x < this.fireWidth; x++) { for (let y = 1; y < this.fireHeight; y++) { const srcIndex = y * this.fireWidth + x; const pixel = this.firePixels[srcIndex]; if (pixel === 0) { this.firePixels[(y - 1) * this.fireWidth + x] = 0; } else { // 随机偏移产生飘动效果 const randIdx = Math.floor(Math.random() * 3); const dstX = Math.min(this.fireWidth - 1, Math.max(0, x - randIdx + 1)); const dstIndex = (y - 1) * this.fireWidth + dstX; this.firePixels[dstIndex] = Math.max(0, pixel - (randIdx & 1)); } } } } // 渲染火焰 render() { this.spreadFire(); const pixelWidth = this.canvas.width / this.fireWidth; const pixelHeight = this.canvas.height / this.fireHeight; for (let y = 0; y < this.fireHeight; y++) { for (let x = 0; x < this.fireWidth; x++) { const colorIndex = this.firePixels[y * this.fireWidth + x]; const color = this.fireColorPalette[colorIndex]; if (colorIndex > 0) { this.ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},0.9)`; this.ctx.fillRect( x * pixelWidth, y * pixelHeight, pixelWidth + 1, pixelHeight + 1 ); } } } }}```
### 4.4 雪花效果
```javascript// src/webview/snowEffect.jsclass SnowEffect { constructor(canvas) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.snowflakes = []; this.snowAccumulation = []; // 积雪层高度数组 this.maxSnowflakes = 200; this.isSnowing = false; this.isAccumulating = false; this.initAccumulation(); } // 初始化积雪层 initAccumulation() { const segments = Math.floor(this.canvas.width / 5); this.snowAccumulation = new Array(segments).fill(0); } // 创建雪花 createSnowflake() { return { x: Math.random() * this.canvas.width, y: -10, radius: Math.random() * 3 + 1, speed: Math.random() * 1 + 0.5, wind: Math.random() * 0.5 - 0.25, opacity: Math.random() * 0.5 + 0.5 }; } // 开始下雪 startSnow(accumulate = false) { this.isSnowing = true; this.isAccumulating = accumulate; } // 停止下雪 stopSnow() { this.isSnowing = false; this.isAccumulating = false; } // 清除效果 clear() { this.snowflakes = []; this.initAccumulation(); this.isSnowing = false; this.isAccumulating = false; } // 更新雪花位置 update() { // 添加新雪花 if (this.isSnowing && this.snowflakes.length < this.maxSnowflakes) { if (Math.random() < 0.3) { this.snowflakes.push(this.createSnowflake()); } } // 更新雪花位置 for (let i = this.snowflakes.length - 1; i >= 0; i--) { const flake = this.snowflakes[i]; flake.y += flake.speed; flake.x += flake.wind; // 检查是否落到底部或积雪上 const segmentIndex = Math.floor(flake.x / 5); const groundLevel = this.canvas.height - (this.snowAccumulation[segmentIndex] || 0); if (flake.y >= groundLevel) { // 积雪 if (this.isAccumulating && segmentIndex >= 0 && segmentIndex < this.snowAccumulation.length) { this.snowAccumulation[segmentIndex] = Math.min( this.snowAccumulation[segmentIndex] + 0.2, this.canvas.height * 0.3 // 最大积雪高度30% ); } this.snowflakes.splice(i, 1); } else if (flake.x < 0 || flake.x > this.canvas.width) { this.snowflakes.splice(i, 1); } } } // 渲染雪花和积雪 render() { this.update(); // 绘制雪花 this.ctx.fillStyle = 'white'; for (const flake of this.snowflakes) { this.ctx.globalAlpha = flake.opacity; this.ctx.beginPath(); this.ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2); this.ctx.fill(); } this.ctx.globalAlpha = 1; // 绘制积雪 if (this.isAccumulating) { this.ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; this.ctx.beginPath(); this.ctx.moveTo(0, this.canvas.height); for (let i = 0; i < this.snowAccumulation.length; i++) { const x = i * 5; const y = this.canvas.height - this.snowAccumulation[i]; this.ctx.lineTo(x, y); } this.ctx.lineTo(this.canvas.width, this.canvas.height); this.ctx.closePath(); this.ctx.fill(); } }}```
### 4.5 Webview 主逻辑
```javascript// src/webview/main.js(function() { const vscode = acquireVsCodeApi(); const canvas = document.getElementById('effectCanvas'); const ctx = canvas.getContext('2d'); let fireEffect, snowEffect; let currentState = 'idle'; let animationId; // 初始化 function init() { resizeCanvas(); fireEffect = new FireEffect(canvas); snowEffect = new SnowEffect(canvas); animate(); } // 调整画布大小 function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } // 动画循环 function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); if (currentState === 'fire') { fireEffect.render(); } else if (currentState === 'snow' || currentState === 'snow_accumulate') { snowEffect.render(); } animationId = requestAnimationFrame(animate); } // 处理来自扩展的消息 window.addEventListener('message', event => { const message = event.data; if (message.type === 'stateUpdate') { handleStateUpdate(message); } }); // 处理状态更新 function handleStateUpdate(message) { const newState = message.state; if (newState !== currentState) { // 状态切换 if (newState === 'fire') { snowEffect.clear(); } else if (newState === 'snow' || newState === 'snow_accumulate') { fireEffect.setIntensity(0); } currentState = newState; } // 更新效果参数 if (currentState === 'fire') { fireEffect.setIntensity(message.heatLevel); } else if (currentState === 'snow') { snowEffect.startSnow(false); } else if (currentState === 'snow_accumulate') { snowEffect.startSnow(true); } else { fireEffect.setIntensity(0); snowEffect.stopSnow(); } } // 窗口大小改变时重新调整 window.addEventListener('resize', () => { resizeCanvas(); if (snowEffect) snowEffect.initAccumulation(); }); // 启动 init();})();```
## 5. 边界条件与异常处理
### 5.1 输入边界- 热度值范围:0-100,超出范围自动截断- 空闲时间:使用 Infinity 表示从未输入- 按键时间戳数组:定期清理过期数据,避免内存泄漏
### 5.2 状态切换- 状态切换时清理前一状态的视觉残留- 使用平滑过渡避免突兀感
### 5.3 性能优化- 火焰像素矩阵使用固定大小(80x50),通过缩放渲染到实际尺寸- 雪花数量上限 200 个,避免性能问题- 使用 requestAnimationFrame 保证动画流畅
### 5.4 Webview 生命周期- Webview 隐藏时暂停动画- Webview 销毁时清理资源- 重新显示时恢复状态
## 6. 数据流动路径
```用户输入 → onDidChangeTextDocument → ActivityTracker.recordKeystroke() 计算热度值/空闲时间 定时器(100ms) → getCurrentState() WebviewProvider.postMessage() Webview 接收消息 → handleStateUpdate() 更新 FireEffect/SnowEffect 参数 requestAnimationFrame → render()```
## 7. 预期成果
完成后的 MVP 应具备:1. ✅ 一键启动视觉效果面板2. ✅ 实时响应用户输入,渲染火焰效果3. ✅ 空闲时自动切换到雪花效果4. ✅ 长时间空闲产生积雪效果5. ✅ 流畅的动画和状态切换6. ✅ 详细的代码注释,便于理解和扩展
复制代码


【附录 2】插件开发任务计划

# FrostFire VS Code 插件开发任务计划
- [ ] 任务 1:初始化项目结构与配置文件 - 1.1: 创建 `package.json`,配置插件元信息、activationEvents、commands 和 views(侧边栏注册) - 1.2: 创建 `tsconfig.json`,配置 TypeScript 编译选项 - 1.3: 创建 `.vscode/launch.json`,配置调试启动项 - 1.4: 创建 `.vscode/tasks.json`,配置 watch 编译任务 - 1.5: 创建 `README.md`,说明项目用途和使用方法
- [ ] 任务 2:实现 ActivityTracker 活跃度追踪模块 - 2.1: 创建 `src/activityTracker.ts`,定义 ActivityTracker 类 - 2.2: 实现 `recordKeystroke()` 方法,记录按键时间戳 - 2.3: 实现 `getHeatLevel()` 方法,计算热度值(0-100) - 2.4: 实现 `getIdleTime()` 方法,计算空闲时间 - 2.5: 实现 `getCurrentState()` 方法,判断当前状态(fire/idle/snow/snow_accumulate)
- [ ] 任务 3:实现 WebviewProvider 面板管理模块 - 3.1: 创建 `src/webviewProvider.ts`,定义 FrostFireWebviewProvider 类 - 3.2: 实现 `resolveWebviewView()` 方法,创建并配置 Webview - 3.3: 实现 `getHtmlContent()` 方法,生成包含 Canvas 和脚本的 HTML - 3.4: 实现 `postMessage()` 方法,向 Webview 发送状态更新消息
- [ ] 任务 4:实现插件主入口 extension.ts - 4.1: 创建 `src/extension.ts`,实现 `activate()` 函数 - 4.2: 注册 WebviewViewProvider 到 frostfire.effectView - 4.3: 注册 frostfire.start 和 frostfire.stop 命令 - 4.4: 添加 `onDidChangeTextDocument` 监听器,记录用户输入 - 4.5: 设置定时器(100ms),定期向 Webview 发送状态更新 - 4.6: 实现 `deactivate()` 函数,清理资源
- [ ] 任务 5:实现 Webview 前端效果(火焰效果) - 5.1: 创建 `src/webview/fireEffect.js`,定义 FireEffect 类 - 5.2: 实现 `createPalette()` 方法,创建 36 色火焰调色板 - 5.3: 实现 `setIntensity()` 方法,根据热度设置火焰强度 - 5.4: 实现 `spreadFire()` 方法,Doom Fire 传播算法 - 5.5: 实现 `render()` 方法,渲染火焰到 Canvas
- [ ] 任务 6:实现 Webview 前端效果(雪花效果) - 6.1: 创建 `src/webview/snowEffect.js`,定义 SnowEffect 类 - 6.2: 实现 `createSnowflake()` 方法,生成随机雪花粒子 - 6.3: 实现 `startSnow()` / `stopSnow()` / `clear()` 控制方法 - 6.4: 实现 `update()` 方法,更新雪花位置和积雪层 - 6.5: 实现 `render()` 方法,渲染雪花和积雪到 Canvas
- [ ] 任务 7:实现 Webview 主逻辑与 HTML 模板 - 7.1: 创建 `src/webview/main.js`,实现消息监听和状态调度 - 7.2: 实现 `handleStateUpdate()` 方法,根据状态切换效果 - 7.3: 实现动画循环 `animate()`,使用 requestAnimationFrame - 7.4: 创建 `src/webview/index.html`,纯黑背景 Canvas 模板
- [ ] 任务 8:安装依赖并编译验证 - 8.1: 执行 `npm install` 安装 TypeScript 和 VS Code 类型定义 - 8.2: 执行 `npm run compile` 编译 TypeScript - 8.3: 检查编译输出,确保无报错 - 8.4: 提供调试启动指南,确认 MVP 可运行有问题吗
复制代码


用户头像

码随心动,快人一步,更懂你的智能代码助手 2025-06-18 加入

基于文心大模型,结合百度积累多年的编程现场大数据和外部优秀开源数据,为你生成更符合实际研发场景的优质代码。提升编码效率,释放“十倍“软件生产力。

评论

发布
暂无评论
插件开发实录:我用Comate在VS Code里造了一场“能被代码融化”的初雪_#AI编程_Comate编码助手_InfoQ写作社区