这个是上一篇的按钮功能(vscode插件开发之按钮 - 掘金 (juejin.cn))实现后引发的思考。就是发现这个按钮做完了,但是功能很单一。我想能不能有种动态的方式,可以通过配置来动态增加按钮呢?而按钮的动作可以写代码自定义,比如实现类似启动项目的功能,或者执行一些命令如npm run dev的操作。而这些都要能灵活的可拓展。最终经过不断的验证尝试,已经初步实现出来了。
设计思路
需求分析
需要一个动态添加按钮的配置页面 => 按钮需要可自定义图标和执行自定义的内容
插件执行流程
启动 - 读取按钮配置 - 显示按钮 -执行事件内容 -结束
使用方式
在系统配置打开配置页面配置如下。后面我在详细介绍。先演示效果。通过动态按钮来添加设置,下面是我用这个功能实现的代码示例
"dy-btn.list": [
{
"id": 1,
"name": "运行",
"icon": "$(debug-start)",
"action": "tool.runInTerminal(`go run .`)"
},
{
"id": 2,
"name": "下载依赖",
"icon": "$(arrow-down)",
"action": "tool.runInTerminal(`pnpm i`)"
},
{
"id": 3,
"name": "带参数的按钮测试",
"icon": "$(add)",
"action": "tool.showMessage(data.path);",
"data": [
{ "label": "执行脚本", "key": "path", "placeholder": "请输入脚本路径" }
]
}]
然后你就能看到下面的示例效果了
功能示例
实现类似启动项目的功能
我在github上向go插件团队反馈,能不能类似python项目加一个快速启动的小按钮,就是不加,说是可以通过go run .运行项目。好,我自己加。
实现自定义命令
前端启动项目竟然要打开命令行后,敲npm run dev才能运行,实在太麻烦了 [doge]。我准备用一个按钮点下。为了配合按钮 icon我这先改成下载依赖了
使用带参数的命令
比如需要接收一个手动输入的参数后执行。
这个只是一个引子,个人感觉功能很强,就看你心有多大,比如一键删库跑路 [doge]。
代码解析
贡献点
"contributes": {
"commands": [ // 命令
{
"command": "dy-btn.action",
"title": "add dynamic button"
}
],
"configuration": { // 配置了可以在setting.json配置增加提示
"type": "object",
"title": "Dynamic Button Configuration",
"properties": {
"dy-btn.list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "The ID of the button."
},
"name": {
"type": "string",
"description": "The name of the button."
},
"icon": {
"type": "string",
"markdownDescription": "The icon of the button.[all icon](https://code.visualstudio.com/api/references/icons-in-labels#icon-listing)"
},
"action": {
"type": "string",
"description": "The action to be executed when the button is clicked."
},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string",
"description": "Label for the input."
},
"key": {
"type": "string",
"description": "Key for the input data."
},
"placeholder": {
"type": "string",
"description": "Placeholder text for the input field."
}
},
"required": ["label", "key", "placeholder"]
},
"description": "Data for the prompt button."
}
},
"required": ["id", "name", "icon", "action"]
},
"description": "List of dynamic buttons configuration."
}
}
}
},
初始化按钮
根据上面的设计思路,先初始化按钮,代码逻辑从setting配置中拿。方便的同时,还能利用上vscode的配置同步功能
function initBtn() {
// 从配置读取
let config = vscode.workspace.getConfiguration('dy-btn',
vscode.ConfigurationTarget.Global as any);
let myButton;
config.list.forEach((item: Btn) => {
myButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
myButton.tooltip = item.name;
myButton.text = `${item.icon}`;
myButton.color = 'white';
myButton.command = {
title: '',
command: 'dy-btn.action',
arguments: [item.id]
};
myButton.show();
actionMap.set(item.id, item);
});
}
执行后就可以看到成功渲染出了按钮
这边有个比较巧妙的关联使用到命令的传参,这个也是看文档找了好久才发现的,怎么实现在多个按钮绑定在一个事件中。后面可以通过id关联拿到对应的执行内容
注册按钮事件
// 注册按钮事件
context.subscriptions.push(vscode.commands.registerCommand('dy-btn.action', async (key) => {
if (!key) {
return tool.showMessage('请前往配置文件配置');
}
// 通过id关联拿到对应的执行内容
let button = actionMap.get(key);
// 执行函数的逻辑
const fn = `async function dy(data, tool){${button.action}}`;
const func = new Function('data', 'tool', fn + '\ndy(data, tool);');
let data: any = tool.getConfig();
if (button?.data) {
// 收集参数
for (let index = 0; index < button?.data.length; index++) {
let item = button?.data[index];
let input = await vscode.window.showInputBox({
prompt: item.label,
placeHolder: item.placeHolder
});
if (input) {
data[item.key] = input;
}
}
}
func(data, tool);
}));
核心是需要解决如何执行字符方法。用到了function 的一些操作。这边不用eval,更加的安全。
其他函数api
获取配置
const getConfig = () => {
const editor = vscode.window.activeTextEditor as vscode.TextEditor;
const filePath = editor.document.uri.fsPath;
let workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath as string;
let config:Config = {
currentDir: path.dirname(filePath),
workspaceDir,
focusFilePath: filePath
};
return config;
};
得到常见的几个目录路径的方式。
获取到视图,创建执行窗口后执行命令
const runInTerminal = (command: string, sourceDir: string = '') => {
if(!sourceDir) {
sourceDir = getConfig().workspaceDir;
}
// 创建一个终端实例
const terminal = vscode.window.createTerminal({
name: 'Command Terminal',
cwd: sourceDir
});
// 显示终端
terminal.show();
// 发送命令到终端
terminal.sendText(command);
};
总结
本文你应该可以学习到以下知识点
1.创建终端视图,并执行命令
2.获取工作目录等路径的方式
3.动态执行方法的思路,命令的多对一关联方法
4.如何配置一个配置的贡献点,配置的获取
5.动态创建状态栏按钮
6.如何使用InputBox来接收参数
相关代码仓库,还有很多细枝末节的小知识点没说。
本来想藏一手,等后面有活动发,但是分享的心再也无法等待。好了,大家快去玩吧,记住心有多大,只要敢想。
插件市场的搜索图,从不玩虚的,很多写文的一没图,全是代码,看的都晕。二没有可运行的完整示例,仓库都不给一个,片片段段的谁知道你是不是在瞎写。三没有一个运行效果图,根本不知道做的啥东西。