逐步解說︰顯示燈泡建議
燈泡是 Visual Studio 編輯器中的圖示,展開後會顯示一組動作,例如,內建程式碼分析器識別到的問題修復或程式碼重構。
您也可以在 Visual C# 和 Visual Basic 編輯器中使用 .NET Compiler Platform ("Roslyn") 來寫入和封裝您自己的程式碼分析器,並包含自動顯示燈泡的動作。 如需詳細資訊,請參閱
-
C++ 等其他語言也會提供適用於一些快速動作的燈泡,例如,建立該函式的虛設常式實作建議。
燈泡的外觀如下。 在 Visual Basic 或 Visual C# 專案中,無效的變數名稱下方會顯示紅色波形曲線。 如果將滑鼠游標暫留在無效的識別碼上,游標附近會出現燈泡。
如果按一下燈泡的向下箭號,就會顯示一組建議的動作,以及選取動作的預覽。 在此情況下,如果執行動作,它就會顯示對您的程式碼所做的變更。
您可以使用燈泡來提供自己的建議動作。 例如,您可以提供動作,將左大括號移到新行,或移至前一行的結尾。 下列逐步解說示範如何建立顯示在目前單字上的燈泡,且有兩個建議動作:轉換為大寫和轉換為小寫。
建立 Managed Extensibility Framework (MEF) 專案
建立 C# VXIS 專案。 (在新增專案對話框中,選取 Visual C# /擴充性,然後選取 VSIX 專案)。命名解決方案
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 類別。 加入實作 ISuggestedActionsSourceProvider,名為 TestSuggestedActionsSourceProvider 的類別。 使用測試建議動作的名稱和 "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 參數中傳回,否則
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()
,但並不總是如此。 例如,如果使用者按 (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); }
警告
燈泡動作叫用方法不應該顯示 UI。 如果您的動作確實顯示新的 UI (例如預覽或選取對話框),則不會直接從叫用方法內顯示 UI,而是排程在從叫用傳回後顯示 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 執行個體。
建立文字檔,並輸入一些文字。 您應該會看到文字左邊的燈泡。
指向燈泡。 您應該會看到向下箭號。
當您按一下燈泡時,應該會顯示兩個建議的動作,以及所選動作的預覽。
如果您按一下第一個動作,則應該會將目前字組中的所有文字都轉換為大寫。 如果您按一下第二個動作,則應該會將所有文字都轉換為小寫。