Passo a passo: Exibir sugestões de lâmpadas

Lâmpadas são ícones no editor do Visual Studio que se expandem para exibir um conjunto de ações, por exemplo, correções para problemas identificados pelos analisadores de código internos ou refatoração de código.

Nos editores Visual C# e Visual Basic, você também pode usar a plataforma de compilador .NET ("Roslyn") para escrever e empacotar seus próprios analisadores de código com ações que exibem lâmpadas automaticamente. Para saber mais, veja:

  • Como: Escrever um diagnóstico C# e correção de código

  • Como: Escrever um diagnóstico do Visual Basic e correção de código

    Outras linguagens como C++ também fornecem lâmpadas para algumas ações rápidas, como, por exemplo, uma sugestão para criar uma implementação de stub dessa função.

    Veja como é uma lâmpada. Em um projeto Visual Basic ou Visual C#, um rabisco vermelho aparece sob um nome de variável quando é inválido. Se você passar o mouse sobre o identificador inválido, uma lâmpada aparecerá perto do cursor.

    light bulb

    Se você clicar na seta para baixo ao lado da lâmpada, um conjunto de ações sugeridas será exibido, juntamente com uma visualização da ação selecionada. Nesse caso, ele mostra as alterações feitas no código se você executar a ação.

    light bulb preview

    Você pode usar lâmpadas para fornecer suas próprias ações sugeridas. Por exemplo, você pode fornecer ações para mover chaves de abertura para uma nova linha ou movê-las para o final da linha anterior. O passo a passo a seguir mostra como criar uma lâmpada que aparece na palavra atual e tem duas ações sugeridas: Converter em maiúsculas e Converter em minúsculas.

Criar um projeto MEF (Managed Extensibility Framework)

  1. Crie um projeto C# VSIX. (No Caixa de diálogo Novo Projeto, selecione Visual C# / Extensibilidade e, em seguida, Projeto VSIX.) Nomeie a solução LightBulbTest.

  2. Adicione um modelo de item Editor Classificador ao projeto. Para obter mais informações, consulte Criar uma extensão com um modelo de item do editor.

  3. Exclua os arquivos de classe existentes.

  4. Adicione a seguinte referência ao projeto e defina Copy Local como False:

    Microsoft.VisualStudio.Language.Intellisense

  5. Adicione um novo arquivo de classe e nomeie-o LightBulbTest.

  6. Adicione o seguinte usando as orientações:

    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;
    
    

Implementar o provedor de fonte de lâmpada

  1. No arquivo de classe LightBulbTest.cs, exclua a classe LightBulbTest. Adicione uma classe chamada TestSuggestedActionsSourceProvider que implementa ISuggestedActionsSourceProvidero . Exporte-o com um Nome de Ações Sugeridas de Teste e um ContentTypeAttribute de "texto".

    [Export(typeof(ISuggestedActionsSourceProvider))]
    [Name("Test Suggested Actions")]
    [ContentType("text")]
    internal class TestSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider
    
  2. Dentro da classe do provedor de origem, importe o e adicione-o ITextStructureNavigatorSelectorService como uma propriedade.

    [Import(typeof(ITextStructureNavigatorSelectorService))]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Implemente o CreateSuggestedActionsSource método para retornar um ISuggestedActionsSource objeto. A fonte é discutida na próxima seção.

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

Implementar o ISuggestedActionSource

A fonte de ação sugerida é responsável por coletar o conjunto de ações sugeridas e adicioná-las no contexto correto. Nesse caso, o contexto é a palavra atual e as ações sugeridas são UpperCaseSuggestedAction e LowerCaseSuggestedAction, que é discutido na seção a seguir.

  1. Adicione uma classe TestSuggestedActionsSource que implementa ISuggestedActionsSourceo .

    internal class TestSuggestedActionsSource : ISuggestedActionsSource
    
  2. Adicione campos particulares somente leitura para o provedor de origem de ação sugerido, o buffer de texto e o modo de exibição de texto.

    private readonly TestSuggestedActionsSourceProvider m_factory;
    private readonly ITextBuffer m_textBuffer;
    private readonly ITextView m_textView;
    
  3. Adicione um construtor que defina os campos privados.

    public TestSuggestedActionsSource(TestSuggestedActionsSourceProvider testSuggestedActionsSourceProvider, ITextView textView, ITextBuffer textBuffer)
    {
        m_factory = testSuggestedActionsSourceProvider;
        m_textBuffer = textBuffer;
        m_textView = textView;
    }
    
  4. Adicione um método private que retorna a palavra que está atualmente sob o cursor. O método a seguir examina o local atual do cursor e pergunta ao navegador de estrutura de texto a extensão da palavra. Se o cursor estiver em uma palavra, o é retornado no parâmetro out, caso contrário, o parâmetro é e o TextExtentout método retorna false.null

    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. Implementar o método de HasSuggestedActionsAsync . O editor chama esse método para descobrir se deseja exibir a lâmpada. Essa chamada é feita com frequência, por exemplo, sempre que o cursor se move de uma linha para outra, ou quando o mouse passa o mouse sobre um rabisco de erro. Ele é assíncrono para permitir que outras operações da interface do usuário continuem enquanto esse método está funcionando. Na maioria dos casos, esse método precisa executar alguma análise e análise da linha atual, portanto, o processamento pode levar algum tempo.

    Nessa implementação, ele obtém de forma assíncrona e determina se a extensão é significativa, como em, se ele tem algum texto diferente de TextExtent espaço em branco.

    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. Implemente o GetSuggestedActions método, que retorna uma matriz de SuggestedActionSet objetos que contêm os objetos diferentes ISuggestedAction . Este método é chamado quando a lâmpada é expandida.

    Aviso

    Você deve se certificar de que as implementações de HasSuggestedActionsAsync() e GetSuggestedActions() são consistentes, ou seja, se HasSuggestedActionsAsync() retorna, trueentão GetSuggestedActions() deve ter algumas ações para exibir. Em muitos casos, é chamado pouco antesGetSuggestedActions(), HasSuggestedActionsAsync() mas nem sempre é o caso. Por exemplo, se o usuário invocar as ações da lâmpada pressionando (CTRL+ ) somente GetSuggestedActions() será chamado.

    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. Defina um SuggestedActionsChanged evento.

    public event EventHandler<EventArgs> SuggestedActionsChanged;
    
  8. Para concluir a implementação, adicione implementações para os Dispose() métodos e TryGetTelemetryId() . Você não quer fazer telemetria, então basta retornar false e definir o GUID como 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;
    }
    

Implementar ações de lâmpadas

  1. No projeto, adicione uma referência a Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll e defina Copy Local como False.

  2. Crie duas classes, a primeira nomeada e a segunda nomeada UpperCaseSuggestedActionLowerCaseSuggestedAction. Ambas as classes implementam ISuggestedActiono .

    internal class UpperCaseSuggestedAction : ISuggestedAction
    internal class LowerCaseSuggestedAction : ISuggestedAction
    

    Ambas as classes são iguais, exceto que uma chama e a outra chama ToUpperToLower. As etapas a seguir abrangem apenas a classe de ação maiúscula, mas você deve implementar ambas as classes. Use as etapas para implementar a ação maiúscula como um padrão para implementar a ação em minúsculas.

  3. Adicione as seguintes diretivas de uso para essas classes:

    using Microsoft.VisualStudio.Imaging.Interop;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Media;
    
    
  4. Declare um conjunto de campos privados.

    private ITrackingSpan m_span;
    private string m_upper;
    private string m_display;
    private ITextSnapshot m_snapshot;
    
  5. Adicione um construtor que defina os campos.

    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. Implemente o GetPreviewAsync método para que ele exiba a visualização da ação.

    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. Implemente o GetActionSetsAsync método para que ele retorne uma enumeração vazia SuggestedActionSet .

    public Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult<IEnumerable<SuggestedActionSet>>(null);
    }
    
  8. Implemente as propriedades da seguinte maneira.

    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. Implemente o método substituindo o Invoke texto na extensão por seu equivalente em maiúsculas.

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

    Aviso

    Não se espera que o método Invoke da ação de lâmpada mostre a interface do usuário. Se sua ação exibir uma nova interface do usuário (por exemplo, uma caixa de diálogo de visualização ou seleção), não exiba a interface do usuário diretamente do método Invoke, mas agende para exibir sua interface do usuário depois de retornar de Invoke.

  10. Para concluir a implementação, adicione os Dispose() métodos e 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. Não se esqueça de fazer a mesma coisa para alterar o texto de exibição para "Converter '' em minúsculas" e a chamada ToUpper para LowerCaseSuggestedActionToLower.{0}

Compilar e testar o código

Para testar esse código, compile a solução LightBulbTest e execute-a na instância Experimental.

  1. Compile a solução.

  2. Quando você executa esse projeto no depurador, uma segunda instância do Visual Studio é iniciada.

  3. Crie um arquivo de texto e digite algum texto. Você deve ver uma lâmpada à esquerda do texto.

    test the light bulb

  4. Aponte para a lâmpada. Você deve ver uma seta para baixo.

  5. Quando você clica na lâmpada, duas ações sugeridas devem ser exibidas, juntamente com a visualização da ação selecionada.

    test light bulb, expanded

  6. Se você clicar na primeira ação, todo o texto na palavra atual deverá ser convertido em maiúsculas. Se você clicar na segunda ação, todo o texto deverá ser convertido em minúsculas.