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 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
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
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)
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
.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.
Exclua os arquivos de classe existentes.
Adicione a seguinte referência ao projeto e configure Copy Local para
False
:Microsoft.VisualStudio.Language.Intellisense
Adicione um novo arquivo de classe e nomeie-o LightBulbTest.
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
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
Dentro da classe de provedor de origem, importe o ITextStructureNavigatorSelectorService e adicione-o como uma propriedade.
[Import(typeof(ITextStructureNavigatorSelectorService))] internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
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.
Adicione uma classe TestSuggestedActionsSource que implementa ISuggestedActionsSource.
internal class TestSuggestedActionsSource : ISuggestedActionsSource
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;
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; }
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 retornafalse
.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; }
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; }); }
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()
eGetSuggestedActions()
sejam consistentes; ou seja, seHasSuggestedActionsAsync()
retornartrue
,GetSuggestedActions()
deverá ter algumas ações a serem exibidas. Em muitos casos,HasSuggestedActionsAsync()
é chamado pouco antes deGetSuggestedActions()
, mas nem sempre é assim. Por exemplo, se o usuário invocar as ações de lâmpada pressionando (CTRL+ .) apenasGetSuggestedActions()
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>(); }
Defina um evento
SuggestedActionsChanged
.public event EventHandler<EventArgs> SuggestedActionsChanged;
Para concluir a implementação, adicione implementações para os métodos
Dispose()
eTryGetTelemetryId()
. Você não deseja fazer telemetria, portanto, basta retornarfalse
e definir o GUID comoEmpty
.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
No projeto, adicione uma referência a Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll e defina Copy Local para
False
.Crie duas classes, a primeira
UpperCaseSuggestedAction
nomeada e a segunda chamadaLowerCaseSuggestedAction
. 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.
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;
Declare um conjunto de campos privados.
private ITrackingSpan m_span; private string m_upper; private string m_display; private ITextSnapshot m_snapshot;
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)); }
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); }
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); }
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; } }
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.
Para concluir a implementação, adicione os métodos
Dispose()
eTryGetTelemetryId()
.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; }
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.
Crie a solução.
Quando você executa esse projeto no depurador, uma segunda instância do Visual Studio é iniciada.
Crie um arquivo de texto e digite um texto. Você deve ver uma lâmpada à esquerda do texto.
TestLIghtBulb
Aponte para a lâmpada. Você deve ver uma seta para baixo.
Quando você clica na lâmpada, duas ações sugeridas devem ser exibidas, juntamente com a visualização da ação selecionada.
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.