共用方式為


逐步解說︰顯示燈泡建議

燈泡是 Visual Studio 編輯器中的圖示,展開後會顯示一組動作,例如,內建程式碼分析器識別到的問題修復或程式碼重構。

您也可以在 Visual C# 和 Visual Basic 編輯器中使用 .NET Compiler Platform ("Roslyn") 來寫入和封裝您自己的程式碼分析器,並包含自動顯示燈泡的動作。 如需詳細資訊,請參閱

  • 如何:寫入 C# 診斷和程式碼修正

  • 如何:寫入 Visual Basic 診斷和程式碼修正

    C++ 等其他語言也會提供適用於一些快速動作的燈泡,例如,建立該函式的虛設常式實作建議。

    燈泡的外觀如下。 在 Visual Basic 或 Visual C# 專案中,無效的變數名稱下方會顯示紅色波形曲線。 如果將滑鼠游標暫留在無效的識別碼上,游標附近會出現燈泡。

    light bulb

    如果按一下燈泡的向下箭號,就會顯示一組建議的動作,以及選取動作的預覽。 在此情況下,如果執行動作,它就會顯示對您的程式碼所做的變更。

    light bulb preview

    您可以使用燈泡來提供自己的建議動作。 例如,您可以提供動作,將左大括號移到新行,或移至前一行的結尾。 下列逐步解說示範如何建立顯示在目前單字上的燈泡,且有兩個建議動作:轉換為大寫轉換為小寫

建立 Managed Extensibility Framework (MEF) 專案

  1. 建立 C# VXIS 專案。 (在新增專案對話框中,選取 Visual C# /擴充性,然後選取 VSIX 專案)。命名解決方案 LightBulbTest

  2. 編輯器分類器項目範本新增至專案。 如需詳細資訊,請參閱 使用編輯器項目範本建立擴充功能

  3. 刪除現有類別檔案。

  4. 將下列參考加入專案中,並將複製到本機設定為 False

    Microsoft.VisualStudio.Language.Intellisense

  5. 新增類別檔案,並將其命名為 LightBulbTest

  6. 新增以下 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;
    
    

實作燈泡來源提供者

  1. 刪除 LightBulbTest.cs 類別檔案中的 LightBulbTest 類別。 加入實作 ISuggestedActionsSourceProvider,名為 TestSuggestedActionsSourceProvider 的類別。 使用測試建議動作的名稱和 "text" 的 ContentTypeAttribute 將其匯出。

    [Export(typeof(ISuggestedActionsSourceProvider))]
    [Name("Test Suggested Actions")]
    [ContentType("text")]
    internal class TestSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider
    
  2. 在來源提供者類別內,匯入 ITextStructureNavigatorSelectorService 並將其新增為屬性。

    [Import(typeof(ITextStructureNavigatorSelectorService))]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. 實作 CreateSuggestedActionsSource 方法以傳回 ISuggestedActionsSource 物件。 下一節中會討論來源。

    public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer)
    {
        if (textBuffer == null || textView == null)
        {
            return null;
        }
        return new TestSuggestedActionsSource(this, textView, textBuffer);
    }
    

實作 ISuggestedActionSource

建議的動作來源負責收集一組建議的動作,並將其新增至正確的內容。 在此情況下,內容是目前的文字而建議的動作則是 UpperCaseSuggestedActionLowerCaseSuggestedAction,將在下一節討論。

  1. 新增實作 ISuggestedActionsSourceTestSuggestedActionsSource 類別。

    internal class TestSuggestedActionsSource : ISuggestedActionsSource
    
  2. 為建議的動作來源提供者、文字緩衝區和文字檢視,新增私用、唯讀欄位。

    private readonly TestSuggestedActionsSourceProvider m_factory;
    private readonly ITextBuffer m_textBuffer;
    private readonly ITextView m_textView;
    
  3. 新增設定私用欄位的建構函式。

    public TestSuggestedActionsSource(TestSuggestedActionsSourceProvider testSuggestedActionsSourceProvider, ITextView textView, ITextBuffer textBuffer)
    {
        m_factory = testSuggestedActionsSourceProvider;
        m_textBuffer = textBuffer;
        m_textView = textView;
    }
    
  4. 新增會將目前位於游標下的單字傳回的私用方法。 下列方法會查看游標的目前位置,並請求文字結構導覽器提供該單字的延伸區。 如果游標位於單字上,則 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;
    }
    
  5. 實作 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;
        });
    }
    
  6. 實作 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>();
    }
    
  7. 定義 SuggestedActionsChanged 事件。

    public event EventHandler<EventArgs> SuggestedActionsChanged;
    
  8. 若要完成實作,請新增 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;
    }
    

實作燈泡動作

  1. 在專案中,新增 Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll 的參考,並將複製本機設定為 False

  2. 建立兩個類別:第一個命名為 UpperCaseSuggestedAction ,第二個則命名為 LowerCaseSuggestedAction。 這兩個類別都會實作 ISuggestedAction

    internal class UpperCaseSuggestedAction : ISuggestedAction
    internal class LowerCaseSuggestedAction : ISuggestedAction
    

    這兩個類別相同,差別在於其中一個會呼叫 ToUpper,另一個會呼叫 ToLower。 下列步驟僅涵蓋大寫動作類別,但您必須實作這兩個類別。 使用實作大寫動作的步驟,作為實作小寫動作的模式。

  3. 為這些類別新增下列 using 指令:

    using Microsoft.VisualStudio.Imaging.Interop;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Media;
    
    
  4. 宣告一組私用欄位。

    private ITrackingSpan m_span;
    private string m_upper;
    private string m_display;
    private ITextSnapshot m_snapshot;
    
  5. 加入可設定欄位的建構函式。

    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));
    }
    
  6. 實作 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);
    }
    
  7. 實作 GetActionSetsAsync 方法,使其傳回空白 SuggestedActionSet 列舉。

    public Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult<IEnumerable<SuggestedActionSet>>(null);
    }
    
  8. 依照下列所示來實作屬性。

    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; }
    }
    
  9. 將範圍中的文字取代為其大寫對等項目,以實作 Invoke 方法。

    public void Invoke(CancellationToken cancellationToken)
    {
        m_span.TextBuffer.Replace(m_span.GetSpan(m_snapshot), m_upper);
    }
    

    警告

    燈泡動作叫用方法不應該顯示 UI。 如果您的動作確實顯示新的 UI (例如預覽或選取對話框),則不會直接從叫用方法內顯示 UI,而是排程在從叫用傳回後顯示 UI。

  10. 若要完成實作,請新增 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;
    }
    
  11. 請記得對 LowerCaseSuggestedAction 執行相同的程序,將顯示文字變更為 "轉換 [{0}] 成小寫",並將呼叫從 ToUpper 更改為 ToLower

建置並測試程式碼

若要測試此程式碼,請建置 LightBulbTest 方案,並在實驗執行個體中執行它。

  1. 建置方案。

  2. 當您在偵錯工具中執行這個專案時,會啟動第二個 Visual Studio 執行個體。

  3. 建立文字檔,並輸入一些文字。 您應該會看到文字左邊的燈泡。

    test the light bulb

  4. 指向燈泡。 您應該會看到向下箭號。

  5. 當您按一下燈泡時,應該會顯示兩個建議的動作,以及所選動作的預覽。

    test light bulb, expanded

  6. 如果您按一下第一個動作,則應該會將目前字組中的所有文字都轉換為大寫。 如果您按一下第二個動作,則應該會將所有文字都轉換為小寫。