Tutorial: Mostrar sugerencias de bombilla

Las bombillas son iconos en el editor de Visual Studio que se expanden para mostrar un conjunto de acciones, por ejemplo, correcciones de problemas identificados por los analizadores de código integrados o la refactorización de código.

En los editores de Visual C# y Visual Basic, también puede usar la Plataforma del compilador de .NET ("Roslyn") para escribir y empaquetar sus propios analizadores de código con acciones que muestran bombillas automáticamente. Para más información, vea:

  • Cómo: Escribir un diagnóstico de C# y una corrección de código

  • Cómo: Escribir una corrección de código y diagnóstico de Visual Basic

    Otros lenguajes como C++ también proporcionan bombillas para algunas acciones rápidas, como una sugerencia para crear una implementación de código auxiliar de esa función.

    Este es el aspecto de una bombilla. En un proyecto de Visual Basic o Visual C#, aparece un subrayado ondulado rojo bajo un nombre de variable cuando no es válido. Si pasa el mouse sobre el identificador no válido, aparece una bombilla cerca del cursor.

    light bulb

    Si hace clic en la flecha hacia abajo por la bombilla, aparece un conjunto de acciones sugeridas, junto con una vista previa de la acción seleccionada. En este caso, muestra los cambios realizados en el código si ejecuta la acción.

    light bulb preview

    Puede usar bombillas para proporcionar sus propias acciones sugeridas. Por ejemplo, podría proporcionar acciones para mover llaves de apertura a una nueva línea o moverlas al final de la línea anterior. En el siguiente tutorial se muestra cómo crear una bombilla que aparece en la palabra actual y tiene dos acciones sugeridas: Convertir a mayúsculas y Convertir en minúsculas.

Creación de un proyecto de Managed Extensibility Framework (MEF)

  1. Cree un proyecto VSIX de C#. (En Cuadro de diálogo Nuevo proyecto , seleccione Visual C# / Extensibilidad y, después , Proyecto VSIX). Asigne un nombre a la solución LightBulbTest.

  2. Agregue una plantilla de elemento clasificador del editor al proyecto. Para obtener más información, vea Creación de una extensión con una plantilla de elemento de editor.

  3. Elimine los archivos de clase existentes.

  4. Agregue la siguiente referencia al proyecto y establezca Copiar local en False:

    Microsoft.VisualStudio.Language.Intellisense

  5. Agregue un nuevo archivo de clase y asígnele el nombre LightBulbTest.

  6. Agregue lo siguiente mediante directivas:

    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;
    
    

Implementación del proveedor de origen de bombilla

  1. En el archivo de clase LightBulbTest.cs , elimine la clase LightBulbTest. Agregue una clase denominada TestSuggestedActionsSourceProvider que implemente ISuggestedActionsSourceProvider. Expórtelo con un nombre de las acciones sugeridas de prueba y un ContentTypeAttribute de "texto".

    [Export(typeof(ISuggestedActionsSourceProvider))]
    [Name("Test Suggested Actions")]
    [ContentType("text")]
    internal class TestSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider
    
  2. Dentro de la clase de proveedor de origen, importe ITextStructureNavigatorSelectorService y agréguelo como una propiedad.

    [Import(typeof(ITextStructureNavigatorSelectorService))]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Implemente el CreateSuggestedActionsSource método para devolver un ISuggestedActionsSource objeto . El origen se describe en la sección siguiente.

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

Implementación de ISuggestedActionSource

El origen de la acción sugerida es responsable de recopilar el conjunto de acciones sugeridas y agregarlas en el contexto correcto. En este caso, el contexto es la palabra actual y las acciones sugeridas son UpperCaseSuggestedAction y LowerCaseSuggestedAction, que se describe en la sección siguiente.

  1. Agregue una clase TestSuggestedActionsSource que implemente ISuggestedActionsSource.

    internal class TestSuggestedActionsSource : ISuggestedActionsSource
    
  2. Agregue campos privados y de solo lectura para el proveedor de origen de acción sugerido, el búfer de texto y la vista de texto.

    private readonly TestSuggestedActionsSourceProvider m_factory;
    private readonly ITextBuffer m_textBuffer;
    private readonly ITextView m_textView;
    
  3. Agregue un constructor que establezca los campos privados.

    public TestSuggestedActionsSource(TestSuggestedActionsSourceProvider testSuggestedActionsSourceProvider, ITextView textView, ITextBuffer textBuffer)
    {
        m_factory = testSuggestedActionsSourceProvider;
        m_textBuffer = textBuffer;
        m_textView = textView;
    }
    
  4. Agregue un método privado que devuelva la palabra que está actualmente bajo el cursor. El método siguiente examina la ubicación actual del cursor y solicita al navegador de la estructura de texto la extensión de la palabra. Si el cursor está en una palabra, TextExtent se devuelve en el parámetro out; de lo contrario, el out parámetro es null y el método devuelve 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 el método HasSuggestedActionsAsync. El editor llama a este método para averiguar si se va a mostrar la bombilla. Esta llamada se realiza a menudo, por ejemplo, cada vez que el cursor se mueve de una línea a otra, o cuando el mouse mantiene el puntero sobre un subrayado ondulado de error. Es asincrónico para permitir que otras operaciones de interfaz de usuario se lleven a cabo mientras este método funciona. En la mayoría de los casos, este método debe realizar algunos análisis y análisis de la línea actual, por lo que el procesamiento puede tardar algún tiempo.

    En esta implementación, obtiene de forma asincrónica y TextExtent determina si la extensión es significativa, como en, si tiene algún texto distinto del espacio en blanco.

    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 el GetSuggestedActions método , que devuelve una matriz de SuggestedActionSet objetos que contienen los distintos ISuggestedAction objetos. Se llama a este método cuando se expande la bombilla.

    Advertencia

    Debe asegurarse de que las implementaciones de HasSuggestedActionsAsync() y GetSuggestedActions() son coherentes; es decir, si HasSuggestedActionsAsync() devuelve true, GetSuggestedActions() debe tener algunas acciones para mostrar. En muchos casos, HasSuggestedActionsAsync() se llama justo antes GetSuggestedActions()de , pero esto no siempre es el caso. Por ejemplo, si el usuario invoca las acciones de bombilla presionando (CTRL+ ).) solo GetSuggestedActions() se llama a .

    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 un SuggestedActionsChanged evento.

    public event EventHandler<EventArgs> SuggestedActionsChanged;
    
  8. Para completar la implementación, agregue implementaciones para los Dispose() métodos y TryGetTelemetryId() . No quiere realizar telemetría, por lo que solo tiene que devolver false y establecer el GUID en 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 acciones de bombilla

  1. En el proyecto, agregue una referencia a Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll y establezca Copiar local en False.

  2. Cree dos clases, la primera llamada UpperCaseSuggestedAction y la segunda llamada LowerCaseSuggestedAction. Ambas clases implementan ISuggestedAction.

    internal class UpperCaseSuggestedAction : ISuggestedAction
    internal class LowerCaseSuggestedAction : ISuggestedAction
    

    Ambas clases son iguales, salvo que una llama a ToUpper y la otra llama a ToLower. Los siguientes pasos abarcan solo la clase de acción de mayúsculas, pero debe implementar ambas clases. Siga los pasos para implementar la acción de mayúsculas como un modelo para implementar la acción de minúsculas.

  3. Agregue las siguientes directivas using para estas clases:

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

    private ITrackingSpan m_span;
    private string m_upper;
    private string m_display;
    private ITextSnapshot m_snapshot;
    
  5. Agregue un constructor que establezca los 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 el GetPreviewAsync método para que muestre la vista previa de la acción.

    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 el GetActionSetsAsync método para que devuelva una enumeración vacía SuggestedActionSet .

    public Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult<IEnumerable<SuggestedActionSet>>(null);
    }
    
  8. Implemente las propiedades de la siguiente manera:

    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 el método Invoke reemplazando el texto en el intervalo por su equivalente en mayúsculas.

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

    Advertencia

    No se espera que el método Invoke de la acción de bombilla muestre la interfaz de usuario. Si la acción abre una nueva interfaz de usuario (por ejemplo, un cuadro de diálogo de vista previa o selección), no muestre la interfaz de usuario directamente desde el método Invoke , sino que programe para mostrar la interfaz de usuario después de volver de Invoke.

  10. Para completar la implementación, agregue los Dispose() métodos y 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. No olvide hacer lo mismo para cambiar el texto para LowerCaseSuggestedAction mostrar a "Convertir '{0}' en minúsculas" y la llamada ToUpper a ToLower.

Compilación y prueba del código

Para probar este código, compile la solución LightBulbTest y ejecútelo en la instancia experimental.

  1. Compile la solución.

  2. Al ejecutar este proyecto en el depurador, se inicia una segunda instancia de Visual Studio.

  3. Cree un archivo de texto y escriba algún texto. Debería ver una bombilla a la izquierda del texto.

    test the light bulb

  4. Apunte a la bombilla. Debería ver una flecha abajo.

  5. Al hacer clic en la bombilla, se deben mostrar dos acciones sugeridas, junto con la vista previa de la acción seleccionada.

    test light bulb, expanded

  6. Si hace clic en la primera acción, todo el texto de la palabra actual debe convertirse en mayúsculas. Si hace clic en la segunda acción, todo el texto se debe convertir en minúsculas.