演练:显示灯泡建议
灯泡是 Visual Studio 编辑器中的图标,可展开以显示一组操作,例如,修复了内置代码分析器或代码重构所标识的问题。
在 Visual C# 和 Visual Basic 编辑器中,还可以使用 .NET 编译器平台(“Roslyn”)编写和打包自己的代码分析器,并自动显示灯泡的操作。 有关详细信息,请参阅:
-
其他语言(如 C++)还为某些快速操作(例如,创建该函数的存根实现的建议)提供灯泡。
下面是灯泡的外观。 在 Visual Basic 或 Visual C# 项目中,当变量名称无效时,会出现红色波浪线。 如果将鼠标悬停在无效标识符上,光标附近会显示一个灯泡。
如果通过灯泡单击向下箭头,将显示一组建议的操作,以及所选操作的预览。 在这种情况下,它会显示执行操作时对代码所做的更改。
可以使用灯泡提供自己的建议操作。 例如,可以提供操作以将左大括号移动到新行或将它们移到上一行的末尾。 以下演练演示如何创建显示在当前单词上的灯泡,并具有两个建议的操作:转换为大写和转换为小写。
创建托管扩展性框架 (MEF) 项目
创建 C# VSIX 项目。 (在 “新建项目 ”对话框,选择 Visual C# /扩展性,然后选择 VSIX Project。)将解决方案
LightBulbTest
命名为 。向 项目添加编辑器分类器 项模板。 有关详细信息,请参阅使用编辑器项模板创建扩展。
删除现有的类文件。
添加对项目的以下引用,并将“复制本地”设置为:
False
Microsoft.VisualStudio.Language.Intellisense
添加新的类文件并将其命名为 LightBulbTest。
添加以下 using 指令:
using System; using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; using System.ComponentModel.Composition; using System.Threading;
实现灯泡源提供程序
在 LightBulbTest.cs 类文件中,删除 LightBulbTest 类。 添加一个名为 TestSuggestedActionsSourceProvider 的类,该类实现 ISuggestedActionsSourceProvider。 使用测试建议操作的名称和ContentTypeAttribute“text”导出它。
[Export(typeof(ISuggestedActionsSourceProvider))] [Name("Test Suggested Actions")] [ContentType("text")] internal class TestSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider
在源提供程序类中,导入 ITextStructureNavigatorSelectorService 并将其添加为属性。
[Import(typeof(ITextStructureNavigatorSelectorService))] internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
CreateSuggestedActionsSource实现返回对象ISuggestedActionsSource的方法。 下一部分将讨论源。
public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) { if (textBuffer == null || textView == null) { return null; } return new TestSuggestedActionsSource(this, textView, textBuffer); }
实现 ISuggestedActionSource
建议的操作源负责收集建议的操作集并将其添加到正确的上下文中。 在这种情况下,上下文是当前单词,建议的操作是 UpperCaseSuggestedAction 和 LowerCaseSuggestedAction,下一部分对此进行了讨论。
添加实现ISuggestedActionsSource的 TestSuggestedActionsSource 类。
internal class TestSuggestedActionsSource : ISuggestedActionsSource
为建议的操作源提供程序、文本缓冲区和文本视图添加专用只读字段。
private readonly TestSuggestedActionsSourceProvider m_factory; private readonly ITextBuffer m_textBuffer; private readonly ITextView m_textView;
添加设置专用字段的构造函数。
public TestSuggestedActionsSource(TestSuggestedActionsSourceProvider testSuggestedActionsSourceProvider, ITextView textView, ITextBuffer textBuffer) { m_factory = testSuggestedActionsSourceProvider; m_textBuffer = textBuffer; m_textView = textView; }
添加一个私有方法,该方法返回当前位于光标下的单词。 以下方法查看光标的当前位置,并询问文本结构导航器单词的范围。 如果光标位于单词上, TextExtent 则会在 out 参数中返回该游标;否则,该
out
参数为null
该方法返回false
。private bool TryGetWordUnderCaret(out TextExtent wordExtent) { ITextCaret caret = m_textView.Caret; SnapshotPoint point; if (caret.Position.BufferPosition > 0) { point = caret.Position.BufferPosition - 1; } else { wordExtent = default(TextExtent); return false; } ITextStructureNavigator navigator = m_factory.NavigatorService.GetTextStructureNavigator(m_textBuffer); wordExtent = navigator.GetExtentOfWord(point); return true; }
实现 HasSuggestedActionsAsync 方法。 编辑器调用此方法来确定是否显示灯泡。 例如,每当光标从一行移动到另一行时,或者鼠标悬停在错误波形曲线上时,通常会进行此调用。 这是异步的,以便允许其他 UI 操作在此方法工作时继续。 在大多数情况下,此方法需要对当前行执行一些分析和分析,因此处理可能需要一些时间。
在此实现中,它以异步方式获取 TextExtent 并确定该范围是否重要,如中所示,它是否具有空格以外的一些文本。
public Task<bool> HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { return Task.Factory.StartNew(() => { TextExtent extent; if (TryGetWordUnderCaret(out extent)) { // don't display the action if the extent has whitespace return extent.IsSignificant; } return false; }); }
实现该方法GetSuggestedActions,该方法返回包含不同ISuggestedAction对象的对象的数组SuggestedActionSet。 扩展灯泡时调用此方法。
警告
应确保
HasSuggestedActionsAsync()
实现并且GetSuggestedActions()
是一致的;也就是说,如果HasSuggestedActionsAsync()
返回true
,则应GetSuggestedActions()
显示一些操作。 在许多情况下,在前面GetSuggestedActions()
调用,HasSuggestedActionsAsync()
但情况并不总是如此。 例如,如果用户仅GetSuggestedActions()
通过按 (Ctrl+ .) 调用灯泡操作。public IEnumerable<SuggestedActionSet> GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { TextExtent extent; if (TryGetWordUnderCaret(out extent) && extent.IsSignificant) { ITrackingSpan trackingSpan = range.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive); var upperAction = new UpperCaseSuggestedAction(trackingSpan); var lowerAction = new LowerCaseSuggestedAction(trackingSpan); return new SuggestedActionSet[] { new SuggestedActionSet(new ISuggestedAction[] { upperAction, lowerAction }) }; } return Enumerable.Empty<SuggestedActionSet>(); }
定义事件
SuggestedActionsChanged
。public event EventHandler<EventArgs> SuggestedActionsChanged;
若要完成实现,请为
Dispose()
和TryGetTelemetryId()
方法添加实现。 你不想执行遥测,因此只需返回false
GUID 并将其设置为Empty
。public void Dispose() { } public bool TryGetTelemetryId(out Guid telemetryId) { // This is a sample provider and doesn't participate in LightBulb telemetry telemetryId = Guid.Empty; return false; }
实现灯泡操作
在项目中,添加对 Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll 的引用,并将“复制本地”
False
设置为 。创建两个类,第一个名为
UpperCaseSuggestedAction
,第二个名为LowerCaseSuggestedAction
。 两个类都实现 ISuggestedAction。internal class UpperCaseSuggestedAction : ISuggestedAction internal class LowerCaseSuggestedAction : ISuggestedAction
这两个类相似,只不过其中一个调用 ToUpper,另一个调用 ToLower。 以下步骤仅说明大写操作类,但你必须实现这两个类。 将实现大写操作的步骤用作实现小写操作的模式。
为这些类添加以下 using 指令:
using Microsoft.VisualStudio.Imaging.Interop; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media;
声明一组私有字段。
private ITrackingSpan m_span; private string m_upper; private string m_display; private ITextSnapshot m_snapshot;
添加设置该字段的构造函数。
public UpperCaseSuggestedAction(ITrackingSpan span) { m_span = span; m_snapshot = span.TextBuffer.CurrentSnapshot; m_upper = span.GetText(m_snapshot).ToUpper(); m_display = string.Format("Convert '{0}' to upper case", span.GetText(m_snapshot)); }
实现该方法 GetPreviewAsync ,使其显示操作预览。
public Task<object> GetPreviewAsync(CancellationToken cancellationToken) { var textBlock = new TextBlock(); textBlock.Padding = new Thickness(5); textBlock.Inlines.Add(new Run() { Text = m_upper }); return Task.FromResult<object>(textBlock); }
实现该方法 GetActionSetsAsync ,使其返回空 SuggestedActionSet 枚举。
public Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken) { return Task.FromResult<IEnumerable<SuggestedActionSet>>(null); }
如下所示实现属性。
public bool HasActionSets { get { return false; } } public string DisplayText { get { return m_display; } } public ImageMoniker IconMoniker { get { return default(ImageMoniker); } } public string IconAutomationText { get { return null; } } public string InputGestureText { get { return null; } } public bool HasPreview { get { return true; } }
通过将范围中的文本替换为其大写形式来实现 Invoke 方法。
public void Invoke(CancellationToken cancellationToken) { m_span.TextBuffer.Replace(m_span.GetSpan(m_snapshot), m_upper); }
警告
灯泡操作 Invoke 方法不应显示 UI。 如果操作确实显示新的 UI(例如预览或选择对话框),请不要直接从 Invoke 方法中显示 UI,而是计划在从 Invoke 返回后显示 UI。
若要完成实现,请添加
Dispose()
和TryGetTelemetryId()
方法。public void Dispose() { } public bool TryGetTelemetryId(out Guid telemetryId) { // This is a sample action and doesn't participate in LightBulb telemetry telemetryId = Guid.Empty; return false; }
不要忘记对将显示文本更改为“将'{0}'转换为小写”和调用ToLowerToUpper执行相同操作
LowerCaseSuggestedAction
。
生成并测试代码
若要测试此代码,请生成 LightBulbTest 解决方案并在实验实例中运行它。
生成解决方案。
在调试器中运行此项目时,将启动 Visual Studio 的第二个实例。
创建一个文本文件并键入一些文本。 文本左侧应会显示一个灯泡。
指向灯泡。 应会看到向下箭头。
单击灯泡时,应显示两个建议的操作以及所选操作的预览。
如果单击第一个操作,则当前单词中的所有文本都应转换为大写。 如果单击第二个操作,所有文本都应转换为小写。