vscode插件动态小按钮

2,590 阅读3分钟

这个是上一篇的按钮功能(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我这先改成下载依赖了

使用带参数的命令

比如需要接收一个手动输入的参数后执行。

image.png

这个只是一个引子,个人感觉功能很强,就看你心有多大,比如一键删库跑路 [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);
    });
}

执行后就可以看到成功渲染出了按钮

image.png

这边有个比较巧妙的关联使用到命令的传参,这个也是看文档找了好久才发现的,怎么实现在多个按钮绑定在一个事件中。后面可以通过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来接收参数

相关代码仓库,还有很多细枝末节的小知识点没说。

github.com/MrYZhou/dy-…

本来想藏一手,等后面有活动发,但是分享的心再也无法等待。好了,大家快去玩吧,记住心有多大,只要敢想。

插件市场的搜索图,从不玩虚的,很多写文的一没图,全是代码,看的都晕。二没有可运行的完整示例,仓库都不给一个,片片段段的谁知道你是不是在瞎写。三没有一个运行效果图,根本不知道做的啥东西。