在 “创建第一个扩展”中,你学习了如何使用 VisualStudio.Extensibility 项目模板创建扩展项目,并学习了如何在 Visual Studio 的实验实例中生成和调试它。
本教程介绍如何使用在 Visual Studio 编辑器中执行某些操作的简单命令创建扩展。 在这种情况下,它将插入新生成的 GUID。 你还将了解如何告诉 Visual Studio 已启用 GUID 扩展的文件类型,以及如何使新命令显示为工具栏或菜单项。
可在此处找到本教程的完整示例。
本教程包含以下步骤:
配置命令
在此步骤中,你将了解用于配置和放置命令的选项。 托管命令的目的是以某种方式向用户公开它,例如添加菜单项或命令栏按钮。
在创建第一个扩展教程中创建的项目模板或示例包含已包含类的Command
单个 C# 文件。 可以就地更新该更新。
将
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
本地化。使用显示名称
InsertGuidCommand
进行更新.vsextension/string-resources.json
。{ "InsertGuidCommand.DisplayName": "Insert new guid" }
添加
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
表示显示图标和彼此旁边的显示名称。添加属性
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
创建一个ITextViewSnapshot
(textView
此处的对象)。 传递取消令牌以保留取消异步请求的功能,但此示例中未演示此部分。 如果未成功获取文本视图,我们会写入日志并终止,而无需执行任何其他操作。
现在,我们已准备好调用向 Visual Studio 编辑器提交编辑请求的异步方法。 我们想要的方法为 EditAsync
. 这是类的成员 EditorExtensibility
,它允许与 IDE 中正在运行的 Visual Studio 编辑器进行交互。 你自己的Command
InsertGuidCommand
类继承的类型具有一个提供对象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
模式可用于在“在此处指定所需更改”注释后指定修改,从而一般修改文本。