Compartilhar via


Passo a passo: exibir sugestões para 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 pela refatoração de código.

Nos editores do Visual C# e do Visual Basic, você também pode usar a Plataforma do Compilador .NET ("Roslyn") para gravar e empacotar seus próprios analisadores de código com ações que exibem lâmpadas automaticamente. Para obter mais informações, consulte:

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

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

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

    Aqui está a aparência de uma lâmpada. Em um projeto do Visual Basic ou do Visual C#, textos sublinhados em vermelho aparecem em um nome de variável quando ele é inválido. Se você passar o mouse sobre o identificador inválido, uma lâmpada aparecerá perto do cursor.

    lâmpada lâmpada

    Se você clicar na seta para baixo pela 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.

    visualização da lâmpada visualização da lâmpada LightBulbPreview

    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 um ícone de lâmpada que aparece na palavra atual e inclui duas ações sugeridas: Converter em maiúsculas e Converter em minúsculas.

Criar um projeto do MEF (Managed Extensibility Framework)

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

  2. Adicione um modelo de item Classificador de Editor 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 configure Copy Local para False:

    Microsoft.VisualStudio.Language.Intellisense

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

  6. Adicione o seguinte usando diretivas:

    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 origem da lâmpada

  1. No arquivo de classe LightBulbTest.cs, exclua a classe LightBulbTest. Adicione uma classe chamada TestSuggestedActionsSourceProvider que implementa ISuggestedActionsSourceProvider. Exporte-o com o nome Testar ações sugeridas e um ContentTypeAttribute de "text".

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

    [Import(typeof(ITextStructureNavigatorSelectorService))]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Implemente o método CreateSuggestedActionsSource para retornar um objeto ISuggestedActionsSource. A origem é 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 origem da ação sugerida é responsável por coletar o conjunto de ações sugeridas e adicioná-las no contexto certo. Nesse caso, o contexto é a palavra atual e as ações sugeridas são UpperCaseSuggestedAction e LowerCaseSuggestedAction, que são discutidas na seção a seguir.

  1. Adicione uma classe TestSuggestedActionsSource que implementa ISuggestedActionsSource.

    internal class TestSuggestedActionsSource : ISuggestedActionsSource
    
  2. Adicione campos privados e de somente leitura para o provedor de origem da ação sugerida, o buffer de texto e a visão de texto.

    private readonly TestSuggestedActionsSourceProvider m_factory;
    private readonly ITextBuffer m_textBuffer;
    private readonly ITextView m_textView;
    
  3. Adicione um construtor que define 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 privado que retorna a palavra que está atualmente sob o cursor. O método a seguir examina o local atual do cursor e solicita ao navegador da estrutura de texto a extensão da palavra. Se o cursor estiver em uma palavra, o TextExtent será retornado no parâmetro out; caso contrário, o parâmetro out é null e o método retorna 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. Implemente o método HasSuggestedActionsAsync. O editor chama esse método para descobrir se a lâmpada deve ser exibida. Essa chamada é feita com frequência, por exemplo, sempre que o cursor se move de uma linha para outra ou quando o usuário passa o mouse sobre textos sublinhados de erro. É assíncrono para permitir que outras operações de interface do usuário continuem enquanto esse método está funcionando. Na maioria das vezes, esse método precisa executar algumas análises e interpretações da linha atual, portanto, o processamento pode levar algum tempo.

    Nesta implementação, ele obtém assíncronamente o TextExtent e determina se a extensão é significativa, como em, se ele tem algum texto diferente do 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 método GetSuggestedActions, que retorna uma matriz de objetos SuggestedActionSet que contêm os diferentes objetos ISuggestedAction. Esse método é chamado quando a lâmpada é expandida.

    Aviso

    Você deve garantir que as implementações de HasSuggestedActionsAsync() e GetSuggestedActions() sejam consistentes; ou seja, se HasSuggestedActionsAsync() retornar true, GetSuggestedActions() deverá ter algumas ações a serem exibidas. Em muitos casos, HasSuggestedActionsAsync() é chamado pouco antes de GetSuggestedActions(), mas nem sempre é assim. Por exemplo, se o usuário invocar as ações de lâmpada pressionando (CTRL+ .) apenas 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 evento SuggestedActionsChanged.

    public event EventHandler<EventArgs> SuggestedActionsChanged;
    
  8. Para concluir a implementação, adicione implementações para os métodos Dispose() e TryGetTelemetryId(). Você não deseja fazer telemetria, portanto, 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 as ações da lâmpada

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

  2. Crie duas classes, a primeira UpperCaseSuggestedAction nomeada e a segunda chamada LowerCaseSuggestedAction. Ambas as classes implementam ISuggestedAction.

    internal class UpperCaseSuggestedAction : ISuggestedAction
    internal class LowerCaseSuggestedAction : ISuggestedAction
    

    Ambas as classes são iguais, exceto que uma chama ToUpper e a outra chama ToLower. 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 de maiúsculas como um padrão para implementar a ação de minúsculas.

  3. Adicione as seguintes instruções de uso para estas 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 define 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 método GetPreviewAsync 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 método GetActionSetsAsync para que ele retorne uma enumeração de SuggestedActionSet vazia.

    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 Invoke substituindo o texto no intervalo 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 da lâmpada mostre a interface do usuário. Se a ação abrir uma nova interface do usuário (por exemplo, uma visualização ou uma caixa de diálogo de seleção), não exiba a interface do usuário diretamente de dentro do método Invoke, mas agende para exibir sua interface do usuário depois de retornar do Invoke.

  10. Para concluir a implementação, adicione os métodos Dispose() 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 LowerCaseSuggestedAction, alterando o texto de exibição para "Converter '{0}' em letras minúsculas" e a chamada ToUpper para ToLower.

Compilar e testar o código

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

  1. Crie 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 um texto. Você deve ver uma lâmpada à esquerda do texto.

    testar a lâmpada TestLIghtBulb

  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.

    lâmpada de teste, expandida

  6. Se você clicar na primeira opçã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.