开发插件
插件开发知识
VSCode插件开发需要前端开发的基础知识,包括但不限于Javascript(必须)、HTML和CSS(非必须)。
随着插件开发工具的版本提升,提供了对TS语法的支持,这个"躺平"插件就是采用TS开发的。
另外还需要一些Webpack方面的知识,不过不需要自己调整和优化配置,只需要根据官方教程设置即可。当然,如果后续涉及到复杂的开发工作,具备相关知识的jym也可以对工程进行优化。
VS Code 插件开发文档 - 中文
VS Code 插件开发文档 - 英文
由于我的"躺平"插件是一个已经具备完备功能的插件了,所以我就不把官方教程复制一遍了,而是跟着开发思路一步一步走下来。这样既可以贴近官方教程,又有一些脱离官方文档的内容和思考。不过由于时间有限,我只能提供思路,具体的代码请查看我的开源代码库。
创建插件工程
首先需要安装好Node和Git,不写具体步骤了,直接三、二、一,上链接:
安装Node
Mac系统安装Git
Windows系统安装Git
还有,需要安装VSCode。倒不是非得让你使用VSCode进行开发工作,但是你开发VSCode插件,不安装VSCode,就很......你懂得。
OK,万事俱备,正式开始。
首先打开终端,执行以下命令:
npm install -g yo generator-code
上面的命令作用是安装Yeoman和VS Code Extension Generator,这是VSCode官方提供的脚手架开发项目工具。
然后执行 yo code
,按提示输入项目信息
zsh
? What type of extension do you want to create? (Use arrow keys)
New Extension (TypeScript)
? What's the name of your extension?
lying
? What's the identifier of your extension?
lying
? What's the description of your extension?
遇事不决,躺平一下
?Initialize a git repository?
Yes
? Bundle the source code with webpack?
Yes
? Which package manager to use?
yarn
第一步选择Typescript是因为我平时使用的是TS,jym也可以选择New Extension (JavaScript),最后一步包管理工具选择npm、yarn、pnpm都可以。如果你选择yarn或pnpm,请确保你本地已经安装了对应的包管理工具。
填写完上面的配置信息后,lying 项目会被创建并开始构建过程。
构建完成后,执行 code ./lying
code命令会打开VSCode,并加载命令后面传入的项目目录。
因为创建的项目根目录是当前目录下的lying目录,所以需要使用 ./lying
,这一点需要注意。
VSCode打开项目后,按下F5,你会立即看到一个 插件发开主机 窗口,其中就运行着插件。
在命令面板 Ctrl+Shift+P
中输入 lying
命令即可看到 Hello World
提示弹窗。
上面这部分如果出现问题或者有疑问,请移步 VS Code 插件开发中文文档 仔细查阅教程,再次尝试。
配置插件信息
我们已经把"躺平"插件的项目创建并运行成功,现在开始配置插件信息。
插件项目的配置信息在package.json中,我直接po出来关键内容(只记录增加和修改的内容)。
json
{
"name": "lying", // 插件名称,英文
"displayName": "躺平", // 插件商店展示的插件名称
"description": "遇事不决,躺平一下 ~ ", // 项目描述,会展示在VSCode插件商店的插件描述位置
"version": "0.1.4", // 没错,已经是0.1.4版本了
"icon": "lying.png", // 插件图标,不配置的话在插件商店展示灰色占位图标
"LICENSE": "MIT", // 开源协议
"repository": {
"type": "git",
"url": "https://github.com/EvenZhu/lying.git"
},
"publisher": "EvenZhu", // 必须和创建的publisher保持一致
"categories": [
"Other"
],
"main": "./dist/extension.js", // 这里指定的路径是使用webpack打包编译后的路径
"contributes": {
"commands": [ // 配置命令列表,配置此项,用户才可以在命令面板查询并执行该命令
{
"command": "evenzhu.lying", // 命令包含两部分,第一部分是标识符,第二部分是命令名称
"title": "躺平"
}
],
"keybindings": [ // 为命令绑定快捷键
{
"command": "evenzhu.lying",
"title": "躺平",
"key": "shift+alt+ctrl+l",
"mac": "shift+alt+cmd+l",
"when": "true" // 代表在VSCode中任何情况下都可以通过快捷键唤起evenzhu.lying命令
}
]
},
"scripts": {
// 增加了下面这条指令,为了方便发布插件,后面的Access token是发布前在Azure DevOps创建的
"vp": "vsce publish -p Access token"
},
"devDependencies": {
...官方配置,不需要任何调整
},
"dependencies": {
"dayjs": "^1.11.10" // 为了计算和格式化时间
}
}
命令开发
根据package.json中的配置 "main": "./dist/extension.js"
可以看出,入口文件应该是src下的。extension.ts。
插件入口文件会导出两个函数,activate
和 deactivate
,你注册的 激活事件 被触发之时执行 activate
,deactivate
则提供了插件关闭前执行清理工作的机会。
接下来,在代码中出现的ExtensionContext、window、commands、workspace等,都是从'vscode'库中导入的。
插件入口代码
ts
// src/extension.ts
import {
ExtensionContext,
window,
commands,
workspace
} from 'vscode';
import command, { finished, lyingAlert } from './command';
export function activate(context: ExtensionContext) {
// 注册evenzhu.lying命令
context.subscriptions.push(commands.registerCommand('evenzhu.lying', command));
/**
* 监听编辑器焦点变化和输入事件
* 为了让你实现真正意义上的躺平,真是操碎了心
*/
context.subscriptions.push(window.onDidChangeActiveTextEditor(event => {
lyingAlert(event);
}));
context.subscriptions.push(workspace.onDidChangeTextDocument((event) => {
lyingAlert(event);
}));
}
export function deactivate() {
finished(); // 停止"躺平"插件插件的执行逻辑
}
命令执行逻辑
快捷键或者命令激活并触发"躺平"插件后,执行逻辑如下:
弹出输入框,选择立即躺平 or 番茄钟
- 使用QuickPick实现选择功能
ts
// src/command.ts
const options: { [key: string]: (context: ExtensionContext) => Promise<string | void> } = {
立即躺平: showInputBox,
番茄钟: (context: ExtensionContext) => multiStepInput(context, (workTime: number, lyingTime: number, repeatCount: number) => {
mode = 1;
repeat = --repeatCount;
doProgress(false, workHandler, workTime, lyingTime);
})
};
const quickPick = window.createQuickPick();
quickPick.items = Object.keys(options).map(label => ({ label }));
quickPick.onDidChangeSelection(selection => {
if (selection[0]) {
options[selection[0].label](context)
.then((text) => {
if (selection[0].label === '立即躺平') {
mode = 0;
doProgress(true, lyingHandler, 0, Number.parseInt(text ?? '1'));
}
})
.catch(console.error);
}
});
quickPick.onDidHide(() => quickPick.dispose());
quickPick.show();
-
选择立即躺平
- 弹出输入框,填入躺平时间 >> 通过showInputBox实现输入躺平时间
与此同时还要记录当前选择的模式以及躺平的状态,还有计时器。计时器的清理很重要,需要在计时结束和取消躺平时终止并清理计时器,这一逻辑目前还存在一些问题,我后续会进行优化。
ts// src/basicInput.ts window.showInputBox({ valueSelection: [1, 120], placeHolder: '请输入躺平时间(分钟数:1~120)', validateInput: text => { if (text === '') { return '躺平时间不得为空'; } const minute = Number.parseInt(text); if (Number.isNaN(minute)) { return '请输入数字'; } if (minute > 120 || minute < 1) { return '请输入 1~120 之间的数字'; } return null; }, });
- 展示躺平阶段倒计时 >> 通过dayjs实现时间转换,通过Progress实现躺平倒计时展示
ts// src/command.ts function doProgress(lyingState: boolean, handler: ProgressHandler, workTime?: number, lyingTime?: number) { lying = lyingState; window.withProgress({ location: ProgressLocation.Notification, title: lyingState ? '躺平阶段' : '工作阶段', cancellable: lyingState }, (progress, token) => handler(progress, token, workTime, lyingTime)); } const lyingHandler = (progress: any, token: any, workTime?: number, lyingTime?: number) => { const totalSecond = lyingTime! * 60; let second = totalSecond; token.onCancellationRequested(() => { finished('躺平大业,中道崩阻,你是真卷啊!', 'warn'); }); progress.report({ message: '开始躺平' }); interval = setInterval(() => { const time = formatSeconds(second - 1); const percent = (totalSecond - second - 1) / totalSecond * 10; progress.report({ message: `还有 ${time}` }); second--; }, 1000); const p = new Promise<void>(resolve => { setTimeout(() => { if (interval) { if (mode === 0) { finished('躺平结束,继续愉快地Coding~'); return; } if (repeat > 0) { repeat--; // 番茄钟循环次数减1 doProgress(false, workHandler, workTime, lyingTime); } else { finished('番茄钟结束了,继续愉快地Coding吧!', 'warn'); } } resolve(); }, second * 1000); }); return p; }; // 调用方法doProgress方法,传入lyingHandler即可展示躺平倒计时
- 监听Tab切换和编辑区输入 >> 通过onDidChangeActiveTextEditor和onDidChangeTextDocument实现监听
ts// src/extension.ts context.subscriptions.push(window.onDidChangeActiveTextEditor(event => { lyingAlert(event); })); context.subscriptions.push(workspace.onDidChangeTextDocument((event) => { lyingAlert(event); }));
- 躺平模式下禁止Coding,弹窗提醒 >> 通过showErrorMessage弹出提醒
ts// src/command.ts export function lyingAlert(event: any) { lying && event && window.showErrorMessage('现在是躺平时间,去休息吧!', { modal: true }); }
- 如果有需要,可以取消当前躺平进度 >> 通过withProgress的token.onCancellationRequested实现
ts// src/command.ts token.onCancellationRequested(() => { finished('躺平大业,中道崩阻,你是真卷啊!', 'warn'); }); export function finished(message: string | undefined = undefined, type: string = 'info') { interval && clearInterval(interval), interval = undefined; lying = false; if (message) { switch (type) { case 'error': window.showWarningMessage(message, { modal: true }); break; case 'warn': window.showWarningMessage(message, { modal: true }); break; default: window.showInformationMessage(message, { modal: true }); break; } } }
- 同时防止通过快捷键和命令多次触发躺平操作;
- 记录当前执行状态和计时器
- 如果当前处于执行状态且计时器正在执行,则不可再次触发
- jym反馈的多次开启和关闭"躺平"插件存在的问题就是这个逻辑存在漏洞造成的
- 弹出输入框,填入躺平时间 >> 通过showInputBox实现输入躺平时间
-
选择番茄钟
- 选择工作阶段时间 >> 通过showQuickPick实现选择工作时间
ts// src/multiStepInput.ts // 输入工作时间的方法与输入躺平时间的方法共用,根据当前步骤进行判断 // 第1步是选择工作时间,第2步是选择躺平时间 async function pickWorkTime(input: MultiStepInput, state: Partial<State>) { const first = !state.step; const step = first ? 1 : 2; const pick: PickItemWithMinute = await input.showQuickPick({ title, step: step, totalSteps: 3, placeholder: first ? '请选择工作阶段时长' : '请选择躺平时长', items: first ? workGroups : lyingGroups, shouldResume: shouldResume }); if (first) { state.workTime = pick; } else { state.lyingTime = pick; } state.step = step; if (state.step === 2) { // 如果当前步骤是第2步,下一步则输入循环次数 return (input: MultiStepInput) => inputRepeat(input, state); } return pickWorkTime(input, state); }
- 选择躺平阶段时间 >> 继续执行pickWorkTime
- 填写番茄钟循环次数 >> 通过showInputBox实现填写循环次数
- 番茄钟循环指定次数后自动停止
- 留空代表无限循环,直到用户取消躺平或关闭VSCode
ts// src/multiStepInput.ts async function inputRepeat(input: MultiStepInput, state: Partial<State>) { const repeatCount = await input.showInputBox({ title, step: 3, totalSteps: 3, value: '', prompt: '请输入循环次数,留空代表不限次数', validate: validateNameIsUnique, shouldResume: shouldResume }); state.repeatCount = repeatCount ? parseInt(repeatCount) : -1; }
- 展示工作阶段倒计时 >> 通过dayjs实现时间转换,通过Progress实现躺平倒计时展示
- 工作倒计时和躺平时间倒计时的逻辑可以复用
- 注意不同状态的切换和记录更新
- 工作阶段结束后展示躺平阶段倒计时
- 切换状态
- 展示躺平倒计时
- 躺平模式下禁止Coding,弹窗提醒
- 躺平模式结束后判断是否继续工作模式
- 开启模式为番茄钟,且循环次数
- 开启模式为番茄钟,且循环次数未到0
ts// src/command.ts // mode - 0:躺平 1:番茄钟 if (mode === 0) { finished('躺平结束,继续愉快地Coding~'); return; } if (repeat > 0) { repeat--; // 番茄钟循环次数减1 doProgress(false, workHandler, workTime, lyingTime); } else { finished('番茄钟结束了,继续愉快地Coding吧!', 'warn'); }
- 如果有需要,可以取消当前躺平(或番茄钟)进度
- 同时防止通过快捷键和命令多次触发躺平操作
OK,逻辑梳理完毕,相关的API和使用到插件功能也已经列出来了,具体逻辑请结合源码理解,如有问题可以评论区留言。
"躺平"插件Github代码仓库
发布插件
发布插件的教程官方已经整理出来了 发布插件教程
我今天只把关键的步骤写出来,如有疑问欢迎留言或者去官网查看详细资料。
创建组织
安装vsce
终端执行命令 npm install -g vsce
创建Access token
打开 dev.azure.com/vscode 进入安全(Security)页面。
举例:我创建的组织(evenzhu)安全页面地址 dev.azure.com/evenzhu/_us...
选择Personal Access Token,点击New Token创建一个新的 Personal Access Token
创建发行方(publisher) - 在插件商店的发布人名称
补充信息
一定要在package.json中配置
- icon - 插件图标
- publisher - 创建的publisher名称
完成 README.md
和 CHANGELOG.md
- README.md - 插件介绍、使用方法,宣传图文等
- CHANGELOG.md - 会展示在插件商店的更改日志中
发布插件
在终端的项目根目录下执行命令 vsce publish -p <token>
进行发布
确认发布结果
- 查看VSCode插件商店上插件是否已经发布成功
- 插件的名称、版本、描述和其他信息是否显示正确
The end!
总结
- 累,真累
- 累,是因为
- 两天的时间完成了VSCode插件开知识学习和插件开发
- 以及发布、迭代和BUG修复
- 又在这个过程中发布了插件创意思路和开发、发布过程
- 值,真值
- 值,是因为这个过程中学会了很多
- VSCode插件开发技术
- 插件运行的基本原理
- 对产品设计的思考
- 尝试理解用户思维
- 推广营销的重要性
- 也发现了自身的不足和增长点
- 写作过程中发现很多知识想要说清楚太难了,必须有取舍
- 很多知识在应用时还需要查阅文档和资料,说明并未烂熟于心
- 时间安排过紧,文章的结构和内容无法进行有效的沉淀和优化
- 希望这样热血的阶段,不是昙花一现,要持之以恒
PS:如果有需要补充的内容,请在评论区留言
转载请注明"来自掘金-EvenZhu"