共用方式為


操作指南:顯示燈泡提示

燈泡是 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. 新增下列參考至專案,並將 [Copy Local] 設定為 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。 將它導出並命名為 測試建議動作,並設置類型為“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. 新增實作 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 的參考,並將 [複製本機 設定為

  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(例如預覽或選取對話框),不要直接從 Invoke 方法內顯示UI,而是應該在從 Invoke返回後安排顯示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. 建立文字文件並輸入文字。 您應該會看到文字左邊的燈泡。

    測試燈泡

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

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

    測試燈泡,展開

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