Пошаговое руководство. Отображение предложений лампочки
Лампочки — это значки в редакторе Visual Studio, которые расширяются для отображения набора действий, например исправлений проблем, определенных встроенными анализаторами кода или рефакторингом кода.
В редакторах Visual C# и Visual Basic можно также использовать платформу компилятора .NET ("Roslyn") для записи и упаковки собственных анализаторов кода с действиями, которые автоматически отображают лампочки. Дополнительные сведения см. в разделе:
Практическое руководство. Написание диагностики и исправления кода C#
Практическое руководство. Написание исправления диагностики и кода Visual Basic
Другие языки, такие как C++, также предоставляют лампочки для некоторых быстрых действий, таких как предложение создать заглушку реализации этой функции.
Вот как выглядит лампочка. В проекте Visual Basic или Visual C# красный волнистый элемент отображается под именем переменной, когда он недопустим. Если вы наведите указатель мыши на недопустимый идентификатор, лампочка появится рядом с курсором.
Если щелкнуть стрелку вниз лампочкой, появится набор предлагаемых действий, а также предварительный просмотр выбранного действия. В этом случае отображаются изменения, внесенные в код при выполнении действия.
Вы можете использовать лампочки для предоставления собственных предлагаемых действий. Например, можно предоставить действия для перемещения открывающих фигурных скобок в новую строку или переместить их в конец предыдущей строки. В следующем пошаговом руководстве показано, как создать лампочку, которая отображается в текущем слове и имеет два предлагаемых действия: Преобразование в верхний регистр и преобразование в нижний регистр.
Создание проекта Managed Extensibility Framework (MEF)
Создайте проект VSIX на C#. (В Диалоговое окно "Новый проект" , выберите Visual C# / Расширяемость, а затем ПРОЕКТ VSIX.) Назовите решение
LightBulbTest
.Добавьте в проект шаблон элемента классификатора редактора. Дополнительные сведения: Создание расширения с помощью шаблона элемента редактора.
Удалите файлы существующих классов.
Добавьте следующую ссылку в проект и задайте для свойства Copy Local значение
False
:Microsoft.VisualStudio.Language.Intellisense
Добавьте новый файл класса и назовите его LightBulbTest.
Добавьте следующие директивы 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;
Реализация поставщика источника лампочки
В файле класса LightBulbTest.cs удалите класс LightBulbTest. Добавьте класс с именем TestSuggestedActionsSourceProvider , реализующий ISuggestedActionsSourceProvider. Экспортируйте его с именем предлагаемых действий теста и текстом ContentTypeAttribute .
[Export(typeof(ISuggestedActionsSourceProvider))] [Name("Test Suggested Actions")] [ContentType("text")] internal class TestSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider
В классе поставщика источника импортируйте ITextStructureNavigatorSelectorService и добавьте его в качестве свойства.
[Import(typeof(ITextStructureNavigatorSelectorService))] internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
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, которые рассматриваются в следующем разделе.
Добавьте класс TestSuggestedActionsSource , реализующий ISuggestedActionsSource.
internal class TestSuggestedActionsSource : ISuggestedActionsSource
Добавьте частные, доступные только для чтения поля для предлагаемого поставщика источника действий, текстового буфера и текстового представления.
private readonly TestSuggestedActionsSourceProvider m_factory; private readonly ITextBuffer m_textBuffer; private readonly ITextView m_textView;
Добавьте конструктор, который задает частные поля.
public TestSuggestedActionsSource(TestSuggestedActionsSourceProvider testSuggestedActionsSourceProvider, ITextView textView, ITextBuffer textBuffer) { m_factory = testSuggestedActionsSourceProvider; m_textBuffer = textBuffer; m_textView = textView; }
Добавьте закрытый метод, который возвращает слово, которое в настоящее время находится под курсором. Следующий метод проверяет текущее расположение курсора и запрашивает навигатор структуры текста для степени слова. Если курсор находится в слове, 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; }
Реализуйте метод 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; }); }
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>(); }
SuggestedActionsChanged
Определение события.public event EventHandler<EventArgs> SuggestedActionsChanged;
Чтобы завершить реализацию, добавьте реализации для
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; }
Реализация действий лампочки
В проекте добавьте ссылку на Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll и задайте значение Copy Local
False
.Создайте два класса с именами
UpperCaseSuggestedAction
иLowerCaseSuggestedAction
. В обоих классах реализован интерфейс ISuggestedAction.internal class UpperCaseSuggestedAction : ISuggestedAction internal class LowerCaseSuggestedAction : ISuggestedAction
Эти два класса похожи за тем исключением, что один из них вызывает ToUpper, а другой вызывает ToLower. В дальнейших шагах рассматривается создание класса для действия преобразования в верхний регистр, но вам необходимо реализовать оба класса. Используйте инструкции по реализации действия преобразования в верхний регистр в качестве шаблона для реализации действия преобразования в нижний регистр.
Добавьте следующие директивы using для этих классов:
using Microsoft.VisualStudio.Imaging.Interop; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media;
Объявите набор закрытых полей.
private ITrackingSpan m_span; private string m_upper; private string m_display; private ITextSnapshot m_snapshot;
Добавьте конструктор, который задает поля.
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)); }
Реализуйте метод таким образом, чтобы он отображал предварительный 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); }
GetActionSetsAsync Реализуйте метод таким образом, чтобы он возвращал пустое SuggestedActionSet перечисление.
public Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken) { return Task.FromResult<IEnumerable<SuggestedActionSet>>(null); }
Реализуйте свойства указанным ниже образом.
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; } }
Реализуйте метод Invoke, заменив текст в диапазоне на эквивалентный текст в верхнем регистре.
public void Invoke(CancellationToken cancellationToken) { m_span.TextBuffer.Replace(m_span.GetSpan(m_snapshot), m_upper); }
Предупреждение
Ожидается, что метод Invoke действия лампочки не отображает пользовательский интерфейс. Если действие открывает новый пользовательский интерфейс (например, диалоговое окно предварительного просмотра или выбора), не отображайте пользовательский интерфейс непосредственно из метода Invoke , а вместо этого запланируйте отображение пользовательского интерфейса после возвращения из Invoke.
Чтобы завершить реализацию, добавьте
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; }
Не забудьте сделать то же самое для
LowerCaseSuggestedAction
изменения отображаемого текста на "Преобразовать "{0}в нижний регистр" и вызова ToUpper ToLower.
Сборка и проверка кода
Чтобы протестировать этот код, создайте решение LightBulbTest и запустите его в экспериментальном экземпляре.
Постройте решение.
При запуске этого проекта в отладчике запускается второй экземпляр Visual Studio.
Создайте текстовый файл и введите любой текст. Вы увидите лампочку слева от текста.
Наведите указатель на лампочку. Вы увидите стрелку вниз.
Щелкнув лампочку, необходимо отобразить два предложенных действия, а также предварительный просмотр выбранного действия.
Если щелкнуть первое действие, все текст в текущем слове следует преобразовать в верхний регистр. Если щелкнуть второе действие, весь текст должен быть преобразован в нижний регистр.