灯泡是 Visual Studio 编辑器中的图标,可展开以显示一组操作,例如,修复了内置代码分析器或代码重构所标识的问题。
在 Visual C# 和 Visual Basic 编辑器中,还可以使用 .NET 编译器平台(“Roslyn”)编写和打包自己的代码分析器,这些分析器配有可自动显示灯泡的操作。 有关详细信息,请参阅:
-
其他语言(如 C++)还为某些快速操作(例如建议创建该函数的存根实现)提供灯泡。
下面是灯泡的外观。 在 Visual Basic 或 Visual C# 项目中,当变量名称无效时,下方会出现红色波浪曲线。 如果将鼠标悬停在无效标识符上,光标附近会显示一个灯泡。
如果通过灯泡单击向下箭头,将显示一组建议的操作,以及所选操作的预览。 在这种情况下,它会显示执行操作时对代码所做的更改。
可以使用灯泡来提供自己的建议操作。 例如,可以建议将左大括号移动到新行或将其移到上一行的末尾。 以下演示展示了如何创建一个在当前单词上显示的灯泡,并包含两个建议的操作:转换为大写,转换为小写。
创建 Managed Extensibility Framework (MEF) 项目
创建 C# VSIX 项目。 (在 新建项目 对话框中,选择 Visual C# /扩展性,然后 VSIX 项目。)将解决方案命名为
LightBulbTest
。向项目添加 编辑器分类器 项模板。 有关详细信息,请参阅 使用编辑器项模板创建扩展。
删除现有类文件。
将以下引用添加到项目,并将“Copy Local”设置为
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。 导出它,并将名称设为“测试建议的操作”,将“text”设为 ContentTypeAttribute。
[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
参数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()
应显示一些操作。 在许多情况下,HasSuggestedActionsAsync()
在GetSuggestedActions()
之前调用,但情况并不总是如此。 例如,如果用户通过按 (CTRL+ .) 调用灯泡操作,则只调用GetSuggestedActions()
。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; }
别忘了也对
LowerCaseSuggestedAction
做同样的事情,将显示文本更改为“将 '{0}' 转换为小写”,并将调用 ToUpper 更改为 ToLower。
生成并测试代码
若要测试此代码,请生成 LightBulbTest 解决方案并在实验实例中运行它。
生成解决方案。
在调试器中运行此项目时,将启动 Visual Studio 的第二个实例。
创建文本文件并键入某些文本。 文本左侧应会显示一个灯泡。
指向灯泡。 您应该会看到一个向下箭头。
单击灯泡时,应显示两个建议的操作以及所选操作的预览。
如果单击第一个操作,则当前单词中的所有文本都应转换为大写。 如果单击第二个操作,所有文本都应转换为小写。