Поделиться через


Пошаговое руководство. Отображение предложений лампочки

Лампочки — это значки в редакторе Visual Studio, которые расширяются для отображения набора действий, например исправлений проблем, определенных встроенными анализаторами кода или рефакторингом кода.

В редакторах Visual C# и Visual Basic можно также использовать платформу компилятора .NET ("Roslyn") для записи и упаковки собственных анализаторов кода с действиями, которые автоматически отображают лампочки. Дополнительные сведения см. в разделе:

  • Практическое руководство. Написание диагностики и исправления кода C#

  • Практическое руководство. Написание исправления диагностики и кода Visual Basic

    Другие языки, такие как C++, также предоставляют лампочки для некоторых быстрых действий, таких как предложение создать заглушку реализации этой функции.

    Вот как выглядит лампочка. В проекте Visual Basic или Visual C# красный волнистый элемент отображается под именем переменной, когда он недопустим. Если вы наведите указатель мыши на недопустимый идентификатор, лампочка появится рядом с курсором.

    light bulb

    Если щелкнуть стрелку вниз лампочкой, появится набор предлагаемых действий, а также предварительный просмотр выбранного действия. В этом случае отображаются изменения, внесенные в код при выполнении действия.

    light bulb preview

    Вы можете использовать лампочки для предоставления собственных предлагаемых действий. Например, можно предоставить действия для перемещения открывающих фигурных скобок в новую строку или переместить их в конец предыдущей строки. В следующем пошаговом руководстве показано, как создать лампочку, которая отображается в текущем слове и имеет два предлагаемых действия: Преобразование в верхний регистр и преобразование в нижний регистр.

Создание проекта Managed Extensibility Framework (MEF)

  1. Создайте проект VSIX на C#. (В Диалоговое окно "Новый проект" , выберите Visual C# / Расширяемость, а затем ПРОЕКТ 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. Экспортируйте его с именем предлагаемых действий теста и текстом 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

Предлагаемый источник действий отвечает за сбор набора предлагаемых действий и их добавление в правильный контекст. В этом случае контекстом является текущее слово, а предлагаемые действия — UpperCaseSuggestedAction и LowerCaseSuggestedAction, которые рассматриваются в следующем разделе.

  1. Добавьте класс TestSuggestedActionsSource , реализующий ISuggestedActionsSource.

    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. Добавьте закрытый метод, который возвращает слово, которое в настоящее время находится под курсором. Следующий метод проверяет текущее расположение курсора и запрашивает навигатор структуры текста для степени слова. Если курсор находится в слове, TextExtent возвращается в параметре out; в противном случае 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 Реализуйте метод, который возвращает массив SuggestedActionSet объектов, содержащих различные ISuggestedAction объекты. Этот метод вызывается при развертывании лампочки.

    Предупреждение

    Необходимо убедиться, что реализации и GetSuggestedActions() согласованыHasSuggestedActionsAsync(); то есть, если 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 и задайте значение Copy LocalFalse.

  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);
    }
    

    Предупреждение

    Ожидается, что метод Invoke действия лампочки не отображает пользовательский интерфейс. Если действие открывает новый пользовательский интерфейс (например, диалоговое окно предварительного просмотра или выбора), не отображайте пользовательский интерфейс непосредственно из метода Invoke , а вместо этого запланируйте отображение пользовательского интерфейса после возвращения из Invoke.

  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}в нижний регистр" и вызова ToUpperToLower.

Сборка и проверка кода

Чтобы протестировать этот код, создайте решение LightBulbTest и запустите его в экспериментальном экземпляре.

  1. Постройте решение.

  2. При запуске этого проекта в отладчике запускается второй экземпляр Visual Studio.

  3. Создайте текстовый файл и введите любой текст. Вы увидите лампочку слева от текста.

    test the light bulb

  4. Наведите указатель на лампочку. Вы увидите стрелку вниз.

  5. Щелкнув лампочку, необходимо отобразить два предложенных действия, а также предварительный просмотр выбранного действия.

    test light bulb, expanded

  6. Если щелкнуть первое действие, все текст в текущем слове следует преобразовать в верхний регистр. Если щелкнуть второе действие, весь текст должен быть преобразован в нижний регистр.