从零开始编写VSCODE插件2-命令篇

390 阅读1分钟

命令

命令会触发VS Code中注册的行为,命令也是插件将功能暴露给用户的地方,它绑定了VS Code UI中的行为,并在内部处理了相关逻辑。

使用命令

VS Code内部含有大量和编辑器交互、控制UI、后台操作的内置命令。许多插件将它们的核心功能暴露为命令的形式供用户或者其他插件使用。

程序性执行一个命令

vscode.commands.executeCommandAPI可以程序性调用一个命令,你可以通过它将VS Code的内置函数构建在你的插件中,比如VS Code内置的Git和Markdown插件中的东西。

我们看个例子🌰:editor.action.addCommentLine命令可以将当前选中行变成注释(你可以偷偷把这个功能地集成到你自己的插件中哦):

import * as vscode from 'vscode';function commentLine() {    vscode.commands.executeCommand('editor.action.addCommentLine');}

有些命令可以接收改变行为的参数,有些会有返回结果。形如vscode.executeDefinitionProvider的API,它要求传入一个document的URI地址和position作为参数,并返回一个包含定义列表的promise:

import * as vscode from 'vscode';async function printDefinitionsForActiveEditor() {    const activeEditor = vscode.window.activeTextEditor;    if (!activeEditor) {        return;    }    const definitions = await vscode.commands.executeCommand<vscode.Location[]>(        'vscode.executeDefinitionProvider',        activeEditor.document.uri,        activeEditor.selection.active    );    for (const definition of definitions) {        console.log(definition);    }}

更多命令详见:

新建命令

注册一个命令

vscode.commands.registerCommand会把命令ID绑定到你插件的函数上:

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
    const command = 'myExtension.sayHello';
    const commandHandler = (name?: string = 'world') => {
        console.log(`Hello ${name}!!!`);
    };
    context.subscriptions.push(vscode.commands.registerCommand(command, commandHandler));
}

只要myExtension.sayHello命令执行,就会调用对应的处理函数,你可以通过命令面板、菜单项、快捷键等方式触发命令。

创建面向用户的命令

vscode.commands.registerCommand仅仅是将命令id绑定到了处理函数上,如果想让用户从命令面板中找到你的命令,你还需要在package.json中配置对应的命令配置项(contribution)

{
    "contributes": {
        "commands": [
            {
                "command": "myExtension.sayHello",`
                "title": "Say Hello"
            }
        ]
    }
}

commands配置告诉VS Code你的插件提供了一个命令,而且允许你控制命令在UI中的显示。现在,我们的命令终于出现在命令面板中了:

命令面板

我们依旧需要使用registerCommand将真实的命令id绑定到函数上。也就是说,如果我们的插件没有激活,那么用户从命令面板中选择myExtension.sayHello也不会有任何效果。为了避免这种事,插件必须注册一个面向全部用户场景的命令onCommand activiationEvent

{    
    "activationEvents": ["onCommand:myExtension.sayHello"]
}

现在当用户第一次调用myExtension.sayHello时,插件就会自动激活,registerCommand会将myExtension.sayHello绑定到正确的处理函数上。

对于内部命令你不需要使用onCommand,但是下面的场景中你必须定义好激活事件:

  • 需要使用命令面板调用
  • 需要快捷键调用
  • 需要通过VS Code UI调用,比如在编辑器标题栏上触发
  • 意在供其他插件使用时

activationEvents激活事件

activationEvents 是 VS Code 插件 package.json 文件中的一个字段,用于指定插件激活(即加载和执行)的条件。插件不会在 VS Code 启动时自动激活,除非在 activationEvents 中定义了特定的事件,当这些事件发生时,插件才会被激活。这样做可以提高 VS Code 的启动速度和运行效率,因为只有在需要时才加载插件。 以下是所有可用激活事件的列表:

  • onLanguage
  • onCommand
  • onDebug
  • onDebugInitialConfigurations
  • onDebugResolve
  • workspaceContains
  • onFileSystem
  • onView
  • onUri
  • onWebviewPanel
  • onCustomEditor
  • onAuthenticationRequest
  • onStartupFinished

VSCODE还提供了 package.json 扩展清单中所有字段的参考 extension manifest

onLanguage

onLanguage 当打开解析为某种语言的文件时,将发出此激活事件,并且感兴趣的扩展将被激活。

"activationEvents": [
    "onLanguage:python"
]

onLanguage 事件接受一个语言标识符值。

可以通过在 activationEvents 数组中声明多个 onLanguage 条目来声明多种语言。

    "activationEvents": [
        "onLanguage:json",
        "onLanguage:markdown",
        "onLanguage:typescript"
    ]

此外,如果您的扩展需要在使用任何语言之前被激活,您可以使用通用的 onLanguage 激活事件来确保这一点:

"activationEvents": [
    "onLanguage"
]

注意:最佳实践是仅在用户需要您的扩展时激活。如果您的扩展适用于一部分语言,最好列出那部分语言,而不是在所有语言上激活。

onCommand

当一个命令被调用时,将发出此激活事件,并且感兴趣的扩展将被激活:

"activationEvents": [
    "onCommand:extension.sayHello"
]

还有其它的许多激活事件,不一一介绍。

控制命令出现在命令面板的时机

默认情况下,所有命令面板中出现的命令都可以在package.jsoncommands部分中配置。不过,有些命令是场景相关的,比如在特定的语言的编辑器中,或者只有用户设置了某些选项时才展示。

menus.commandPalette发布内容配置运行你限制命令出现在命令面板的时机。你需要配置命令ID和一条when语句

    {
        "contributes": {
            "menus": {
                "commandPalette": [
                    {
                        "command": "myExtension.sayHello",
                        "when": "editorLangId == markdown"
                    }
                ]
            }
        }
    }

现在myExtension.sayHello命令只会出现在用户的Markdown文件中了。

如果你想命令出现在右键菜单上,配置editor/context

    {
        "contributes": {
            "menus": {
                "editor/context": [
                    ...
                ]
            }
        }
    }

命令的URLs

命令URI是执行注册命令的链接。它们可被用于悬停文本上的可点击链接,代码补全提示中的细节信息,甚至可以出现在webview中。

命令URI使用command作为协议头,然后紧跟着命令名称。比如:editor.action.addCommentLine的命令URI是:command:editor.action.addCommentLine

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
  vscode.languages.registerHoverProvider(
    'javascript',
    new class implements vscode.HoverProvider {
      provideHover(
        _document: vscode.TextDocument,
        _position: vscode.Position,
        _token: vscode.CancellationToken
      ): vscode.ProviderResult<vscode.Hover> {
        const commentCommandUri = vscode.Uri.parse('command:editor.action.addCommentLine');
        const contents = new vscode.MarkdownString(`[Add comment](${commentCommandUri})`);

        // command URIs如果想在Markdown 内容中生效, 你必须设置isTrusted。
        // 当创建可信的Markdown 字符, 请合理地清理所有的输入内容
        // 以便你期望的命令command URIs生效
        contents.isTrusted = true;

        return new vscode.Hover(contents);
      }
    }()
  );
}

命令上的参数列表会从JSON数组变成URI格式:下面的例子使用了git.stage命令创建一个悬停操作——将当前文件进行git暂存:

import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
    vscode.languages.registerHoverProvider(
        'javascript',
        new class implements vscode.HoverProvider {
            provideHover(
                document: vscode.TextDocument,
                _position: vscode.Position,
                _token: vscode.CancellationToken
            ): vscode.ProviderResult<vscode.Hover> {
                const args = [{ resourceUri: document.uri }];
                const commentCommandUri = vscode.Uri.parse(
                    `command:git.stage?${encodeURIComponent(JSON.stringify(args))}`
                );
                const contents = new vscode.MarkdownString(`[Stage file](${commentCommandUri})`);
                contents.isTrusted = true;
                return new vscode.Hover(contents);
            }
        }()
    );
}