共用方式為


操作指南:顯示燈泡建議

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

在 Visual C# 和 Visual Basic 編輯器中,您也可以使用 .NET 編譯程式平臺 (“Roslyn”) 撰寫並封裝您自己的程式代碼分析器,並自動顯示燈泡的動作。 如需詳細資訊,請參閱:

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

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

    C++等其他語言也提供燈泡,以供某些快速動作使用,例如,建議建立該函式的存根實作。

    以下是燈泡的外觀。 在 Visual Basic 或 Visual C# 專案中,當變數名稱無效時,紅色波浪線會出現在變數名稱下。 如果您將滑鼠停留在無效的標識碼上,游標附近會出現燈泡。

    燈泡

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

    燈泡預覽

    您可以使用燈泡來提供您自己的建議動作。 例如,您可以提供動作,將左大括弧移到新行,或將它們移至前一行的結尾。 下列逐步解說展示如何建立一個會出現在當前單字上的燈泡,並提供兩個建議動作:轉換成大寫轉換成小寫

建立受控擴充性架構 (MEF) 專案

  1. 建立 C# VSIX 專案。 (在 [新增專案] 對話框中,選取 [Visual C# / Extensibility],然後 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 類別。 新增名為 TestSuggestedActionsSourceProvider 的類別, 實作 ISuggestedActionsSourceProvider。 將其以名稱為 的測試建議動作ContentTypeAttribute 的“text” 導出。

    [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. 中新增一個 Class,並實作 ISuggestedActionsSource的 TestSuggestedActionsSource。

    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. 新增私用方法,這個方法會傳回目前位於游標下的單字。 下列方法會查看游標的目前位置,並詢問文字結構導覽器文字的範圍。 如果游標位於單字上,則會在 out 參數中傳回 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;
    }
    
  5. 實作 HasSuggestedActionsAsync 方法。 編輯器會呼叫此方法,以找出是否要顯示燈泡。 例如,每當游標從一行移到另一行,或滑鼠停留在錯誤波浪線上方時,通常會進行此呼叫。 這是非同步的,以便在此方法運作時,允許其他使用者介面操作繼續執行。 在大部分情況下,此方法必須執行目前行的一些剖析和分析,因此處理可能需要一些時間。

    在此實作中,它會以異步方式取得 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() 應該要顯示一些動作。 在許多情況下,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>();
    }
    
  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 方法,將 span 中的文字替換為其大寫形式。

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

    警告

    燈泡動作 Invoke 方法預期不會顯示使用者介面。 如果您的動作會顯示新的使用者介面(例如預覽或選擇對話框),請勿直接從 Invoke 方法內呈現介面,而應在從 Invoke返回後安排顯示介面的時機。

  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. 建立文字文件並輸入文字。 您應該會看到文字左邊的燈泡。

    測試燈泡

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

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

    測試燈泡,展開

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