vscode扩展开发(一): hello world, 命令, 通知和状态栏

2,001 阅读3分钟

如标题所言, 废话不多说先来个祖传的 hello world

hello world

先安装项目生成器

npm install -g yo generator-code

然后生成项目

yo code

先选一下模板的类型(这里以TypeScript为例)

image.png

然后是填一下项目名, 项目描述, 扩展标识符, 是否初始化git, 选择包管理器等信息(嫌麻烦可以指定项目名然后一路回车即可)

image.png

然后就会创建一些文件并下载依赖, 安装完依赖会出现如下提示, 问要不要用vscode打开这个项目, 回车即可

image.png

打开vscode后按F5启动调试, 稍等会打开一个扩展开发宿主的新窗口, 按F1输入hello world然后回车, 会看到有下角的信息提示, 如下所示:

image.png

image.png

如果发现F1没有hello world命令, 可以检查一下package.json中的engines.vscode字段指定的版本是不是于你使用的vscode的版本, 该字段是配置兼容vscode的最低版本

项目结构

生成的项目基本结构如下:

.
│  .eslintrc.json
├─ .git
    │  ...
│  .gitignore
│  .vscodeignore
│  CHANGELOG.md // 更新日志
│  package-lock.json
│  package.json // 配置文件
│  README.md
│  tsconfig.json
│  vsc-extension-quickstart.md // 快速上手
├─.vscode
    │  ...
└─src
    │  extension.ts // 扩展的主文件
    └─test
        │  runTest.ts
        └─suite
                extension.test.ts
                index.ts

目录结构中的文件都是项目中常见的, 有两个文件是比较重要的那就是package.jsonsrc/extension.ts, 如下:

  • package.json: 不用说, 本身就很重要, 不过在vscode的扩展开发中, 该文件还可以指定相关的配置, 比如: 声明扩展激活的事件集合, 声明扩展的贡献点(Contribution Points)等等
  • src/extension.ts: 这个文件是扩展的入口文件, 默认会暴露两个函数activatedeactivate, 前者是当扩展被激活时触发而后者是当扩展被停用时触发

命令

有关命令的可以先看一下hello world的例子, 先来看一下package.json命令相关的配置, 如下:

{
  // ...
  "activationEvents": [ // 声明扩展的事件集合
     // onCommand:xxx 表示接受到指定的命令时激活事件, 下面的就是 learn-vscode-extends.helloWorld
    "onCommand:learn-vscode-extends.helloWorld" 
  ],
  "contributes": { // 贡献点配置
    "commands": [ // 命令配置(出现在`F1`的命令栏中)
      {
        "command": "learn-vscode-extends.helloWorld", // 当此命令执行是触发对应命令id的事件
        "title": "Hello World" // 显示的文本, 会在`F1`命令栏显示(可以是中文)
      }
    ]
  }
  // ...
}

再来看看src/extension.ts文件内容, 如下:

// vscode 模块包含 vscode 扩展的相关 API
import * as vscode from "vscode";

// 当扩展被激活时触发
export function activate(context: vscode.ExtensionContext) {
    // 此代码行仅在激活扩展时执行一次
    console.log("active 函数运行了");

    // 注册一个 learn-vscode-extends.helloWorld 事件
    const disposable = vscode.commands.registerCommand("learn-vscode-extends.helloWorld", () => {
            // 触发此事件会显示一段 Info 类型的 message
            vscode.window.showInformationMessage("这是我的第一个 vscode 插件");
    });

    // 将注册的事件添加到上下文对象中
    context.subscriptions.push(disposable);
}

// 当扩展被停用时触发
export function deactivate() {}

梳理一下相关的配置: package.jsoncontributes.commands中声明的是在F1命令栏中出现的命令, 当指定的命令被执行时, 就会去触发对应命令id的事件, 也就是在activationEvents集合中声明为命令相关的onCommand:xxx配置, 然后需要为vscode注册相关的命令回调, 也就是src/extension.ts里的vscode.commands.registerCommand("命令id", 回调函数)对应的回调函数(注意这些三者注册或配置的命令id(标识符)必须是要保持一致的不然就会报找不到对应命令的错误)

image.png

命令的其他配置

命令还可以添加分组和icon, 如下:

"contributes": {
  "commands": [
    {
      "command": "learn-vscode-extends.helloWorld1", // 绑定的命令id
      "title": "Hello World",
      "category": "分组1", // 指定分组
      // "icon": "$(book)" // 指定 icon, 这个icon会在该命令绑定给编辑器标题菜单栏时显示
    },
    {
      "command": "learn-vscode-extends.helloWorld2", 
      "title": "Hello World",
      "category": "分组2"
    }
  ]
}

对应的命令栏ui如下:

image.png

icon的显示需要用到menu配置, 这个之后会介绍

执行内置命令

使用vscode.commands.executeCommand可以执行一些内置的命令, 修改hello的例子如下:

import * as vscode from "vscode";

export function activate(context: vscode.ExtensionContext) {
    const disposable = vscode.commands.registerCommand("learn-vscode-extends.helloWorld", () => {
        // 执行vscode内置的命令, 查看发行说明
        vscode.commands.executeCommand("update.showCurrentReleaseNotes");
    });
    context.subscriptions.push(disposable);
}

export function deactivate() {}

F5然后F1输入hello world就可以看到vscode的发行说明页了

具体的命令id获取可以从键盘快捷方式中查看, 如下:

image.png

image.png

也可以通过vscode.commands.getCommands来获取所有的命令

// 获取所有命令
vscode.commands.getCommands().then(allCommands => {
    console.log("所有的命令", allCommands);
});

注意: 有一些比较复杂的内置命令是需要传递参数, 具体的可以查看官方的指南

通知和状态栏

通知

vscode提供的通知分别是info, warning, error三种, 基本的使用如下:

import * as vscode from "vscode";

export function activate(context: vscode.ExtensionContext) {
    // 注册一个 learn-vscode-extends.message 命令
    const disposable = vscode.commands.registerCommand("learn-vscode-extends.message", () => {
        // 右下角三种不同的通知提示
        vscode.window.showInformationMessage("info message");
        vscode.window.showWarningMessage("warning message");
        vscode.window.showErrorMessage("error message");
    });
    context.subscriptions.push(disposable);
}

export function deactivate() { }

别忘了在package.json中注册对应的命令

"activationEvents": [
    "onCommand:learn-vscode-extends.message"
],
"contributes": {
    "commands": [
        {
            "command": "learn-vscode-extends.message",
            "title": "测试通知"
        }
    ]
},

效果如下:

image.png

image.png

带按钮的通知

如果需要通知有对应的按钮只需要, 再传递对应的按钮文本参数即可, 如下所示:

// 第一个参数是信息, 后续的参数会生成对应的按钮, then 回调收到点击的按钮文本
vscode.window.showInformationMessage("是否要打开文件选择", "是", "否", "不再提示")
    .then(result => {
        switch (result) {
            case "是":
                vscode.window.showOpenDialog()
                    .then((value: vscode.Uri[] | undefined) => {
                        if (value) {
                            // 选中的文件路径(默认单选)
                            const filePath = value[0].fsPath;
                            vscode.window.showInformationMessage(filePath);
                        }
                    });
                break;
            case "不再提示":
                // ...
                break;
            default:
                break;
        }
    });

效果如下:

image.png

带进度的通知

使用vscode.window.withProgress即可实现带通知的回调, 如下:

import * as vscode from "vscode";

export function activate(context: vscode.ExtensionContext) {
    const disposable = vscode.commands.registerCommand("learn-vscode-extends.message", () => {
        vscode.window.withProgress({
            // 进度显示类型, Notification(右下角通知和进度) | Window(状态栏转圈) | SourceControl(源代码控制栏图标和进度)
            location: vscode.ProgressLocation.Notification,
            title: "获取网络资源", // 标题
            cancellable: true // 显示取消按钮
        }, (progress, token) => {

            // 取消按钮回调
            token.onCancellationRequested(() => {
                vscode.window.showInformationMessage("取消成功");
            });

            // 进度0%, 无文本
            progress.report({ increment: 0 });

            // 修改进度条到10%, 增加文本
            setTimeout(() => {
                progress.report({ increment: 10, message: "发送请求中.." });
            }, 1000);

            setTimeout(() => {
                progress.report({ increment: 30, message: "请求发送成功..." });
            }, 2000);

            setTimeout(() => {
                progress.report({ increment: 50, message: "请求已到达..." });
            }, 3000);

            // 4秒后关闭
            return new Promise<void>(resolve => {
                setTimeout(resolve, 4000);
            });
        });
    });
    context.subscriptions.push(disposable);
}

export function deactivate() { }

三种不同的通知效果如下:

vscode.ProgressLocation.Notification

image.png

vscode.ProgressLocation.Window

image.png

vscode.ProgressLocation.SourceControl

image.png

状态栏

状态栏就是类似上面的vscode.ProgressLocation.Window通知一样, 只不过自定义, 先看一下最简单状态栏使用vscode.window.setStatusBarMessage皆可创建

import * as vscode from "vscode";

export function activate(context: vscode.ExtensionContext) {
    const disposable = vscode.commands.registerCommand("learn-vscode-extends.status", () => {
        // 添加一个默认的状态栏
        const loading = vscode.window.setStatusBarMessage("loading...");

        setTimeout(() => {
            // 1.5s后释放
            loading.dispose();
        }, 1500);
    });

    context.subscriptions.push(disposable);
}

export function deactivate() { }

别忘了要在package.json里命令注册和声明learn-vscode-extends.status, 效果如下:

image.png

状态栏的文本是可以使用vscdoe提供的一些图标还有指定图标的旋转动画, 图标使用格式为:$(xxx), 动画使用格式为$(xxx~动画名称), 如下:

const loading = vscode.window.setStatusBarMessage("$(loading~spin) loading... $(book)");

效果为:

image.png

需要注意的是, 不是所有图标都支持动画只有这三个图标才支持 sync, loading, gear

自定义状态栏

vscode.window.setStatusBarMessage只能创建最基本的状态栏, 使用vscode.window.createStatusBarItem可以创建自定义的状态栏, 如下:

import * as vscode from "vscode";

export function activate(context: vscode.ExtensionContext) {
    const disposable = vscode.commands.registerCommand("learn-vscode-extends.status", () => {
        // 创建一个状态栏对象, 右对应, 优先级 999
        const statusBarItem: vscode.StatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 9999);
        statusBarItem.text = "$(loading~spin) loading..."; // 文本和图标(动画)
        statusBarItem.tooltip = "鼠标的悬浮提示"; // 鼠标的悬浮提示
        statusBarItem.backgroundColor = new vscode.ThemeColor("statusBarItem.warningBackground"); // 背景颜色(只支持 warningBackground | errorBackground)
        statusBarItem.command = "learn-vscode-extends.statusClick"; // 绑定点击的命令

        // 显示
        statusBarItem.show();

        setTimeout(() => {			
            // 隐藏
            statusBarItem.hide();
            // 注销
            // statusBarItem.dispose();
        }, 5000);
    });

    context.subscriptions.push(disposable);

    // 状态栏点击命令
    context.subscriptions.push(vscode.commands.registerCommand("learn-vscode-extends.statusClick", () => {
        vscode.window.showInformationMessage("状态栏的点击命令回调");
    }));
}

export function deactivate() { }

别忘了注册learn-vscode-extends.statusClick命令, 效果如下:

image.png