创建简单的扩展

“创建第一个扩展”中,你学习了如何使用 VisualStudio.Extensibility 项目模板创建扩展项目,并学习了如何在 Visual Studio 的实验实例中生成和调试它。

本教程介绍如何使用在 Visual Studio 编辑器执行某些操作的简单命令创建扩展。 在这种情况下,它将插入新生成的 GUID。 你还将了解如何告诉 Visual Studio 已启用 GUID 扩展的文件类型,以及如何使新命令显示为工具栏或菜单项。

可在此处找到本教程的完整示例。

本教程包含以下步骤:

配置命令

在此步骤中,你将了解用于配置和放置命令的选项。 托管命令的目的是以某种方式向用户公开它,例如添加菜单项或命令栏按钮。

在创建第一个扩展教程中创建的项目模板或示例包含已包含类的Command单个 C# 文件。 可以就地更新该更新。

  1. Command1.cs 文件重命名为 InsertGuidCommand.cs,重命名类 InsertGuidCommand,更新 CommandConfiguration 属性。

    public override CommandConfiguration CommandConfiguration => new("%InsertGuidCommand.DisplayName%")
    {
        Placements = new[] { CommandPlacement.KnownPlacements.ExtensionsMenu },
    };
    

    Placements 指定命令应在 IDE 中出现的位置。 在本例中,命令放置在“扩展”菜单中,这是 Visual Studio 中顶级菜单之一。

    CommandConfiguration构造函数的参数是命令的显示名称,即菜单文本。 显示名称由 % 字符括起来,因为它引用字符串资源以支持 .vsextension/string-resources.json 本地化。

  2. 使用显示名称InsertGuidCommand进行更新.vsextension/string-resources.json

    {
      "InsertGuidCommand.DisplayName": "Insert new guid"
    }
    
  3. 添加 Icon 属性。

    public override CommandConfiguration CommandConfiguration => new("%InsertGuidCommand.DisplayName%")
    {
        Placements = new[] { CommandPlacement.KnownPlacements.ExtensionsMenu },
        Icon = new(ImageMoniker.KnownValues.OfficeWebExtension, IconSettings.IconAndText),
    };
    

    可以指定已知的内置图标(在本例OfficeWebExtension中),也可以上传图标的图像,如“添加 Visual Studio”命令中所述。 第二个参数是一个枚举,用于确定命令在工具栏中应如何显示(除了命令在菜单中的位置)。 该选项 IconSettings.IconAndText 表示显示图标和彼此旁边的显示名称。

  4. 添加属性 VisibleWhen ,该属性指定要向用户显示项目必须应用的条件。

    public override CommandConfiguration CommandConfiguration => new("%InsertGuidCommand.DisplayName%")
    {
        Placements = new[] { CommandPlacement.KnownPlacements.ExtensionsMenu },
        Icon = new(ImageMoniker.KnownValues.OfficeWebExtension, IconSettings.IconAndText),
        VisibleWhen = ActivationConstraint.ClientContext(ClientContextKey.Shell.ActiveEditorContentType, ".+"),
    };
    

有关详细信息,请参阅 使用基于规则的激活约束

创建执行方法

在此步骤中 ExecuteCommandAsync ,将实现命令的方法,该方法定义用户选择菜单项时发生的情况,或按命令工具栏中的项。

复制以下代码来实现该方法。

public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
    {
        Requires.NotNull(context, nameof(context));
        var newGuidString = Guid.NewGuid().ToString("N", CultureInfo.CurrentCulture);

        using var textView = await context.GetActiveTextViewAsync(cancellationToken);
        if (textView is null)
        {
            this.logger.TraceInformation("There was no active text view when command is executed.");
            return;
        }

        await this.Extensibility.Editor().EditAsync(
            batch =>
            {
                textView.Document.AsEditable(batch).Replace(textView.Selection.Extent, newGuidString);
            },
            cancellationToken);
    }

第一行验证参数,然后创建一个新 Guid 参数以供以后使用。

然后,我们通过调用异步方法GetActiveTextViewAsync创建一个ITextViewSnapshottextView此处的对象)。 传递取消令牌以保留取消异步请求的功能,但此示例中未演示此部分。 如果未成功获取文本视图,我们会写入日志并终止,而无需执行任何其他操作。

现在,我们已准备好调用向 Visual Studio 编辑器提交编辑请求的异步方法。 我们想要的方法为 EditAsync. 这是类的成员 EditorExtensibility ,它允许与 IDE 中正在运行的 Visual Studio 编辑器进行交互。 你自己的CommandInsertGuidCommand类继承的类型具有一个提供对象EditorExtensibility访问权限的成员Extensibility,因此我们可以通过调用this.Extensibility.Editor()来访问EditorExtensibility该类。

该方法 EditAsync 采用 Action<IEditBatch> 参数的形式。 调用此参数 editorSource

调用使用 EditAsync lambda 表达式。 若要对此进行一些分解,还可以按如下所示编写该调用:

await this.Extensibility.Editor().EditAsync(
    batch =>
    {
        var editor = textView.Document.AsEditable(batch);
        // specify the desired changes here:
        editor.Replace(textView.Selection.Extent, newGuidString);
    },
    cancellationToken);

可以将此调用视为指定要在 Visual Studio 编辑器进程中运行的代码。 lambda 表达式指定要在编辑器中更改的内容。 IEditBatch类型batch,这意味着此处定义的 lambda 表达式会进行一小组应作为单元完成的更改,而不是被用户或语言服务的其他编辑中断。 如果代码执行时间过长,这可能会导致无响应,因此必须限制此 lambda 表达式中的更改并了解可能导致延迟的任何内容。

AsEditable使用文档中的方法,可以获得可用于指定所需更改的临时编辑器对象。 将 lambda 表达式中的一切视为要求 Visual Studio 执行而不是实际执行的所有内容,因为如使用 Visual Studio 编辑器扩展性中所述,有一个特定的协议来处理来自扩展的这些异步编辑请求,并且可能会接受更改,例如,用户是否在创建冲突的同时进行更改。

EditAsync 模式可用于在“在此处指定所需更改”注释后指定修改,从而一般修改文本。