Пошаговое руководство: Отображение подсказок в виде лампочки
Лампочки — это значки в редакторе 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
.Добавьте в проект шаблон элемента для классификатора редактора. Дополнительные сведения см. в статье Создание расширения с помощью шаблона элемента редактора.
Удалите существующие файлы классов.
Добавьте следующую ссылку в проект и установите для значение "Копировать локально" на
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 "text".
[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
является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. Этот метод вызывается при расширении лампочки.
Предупреждение
Необходимо убедиться, что реализации
HasSuggestedActionsAsync()
иGetSuggestedActions()
были согласованы; Иными словами, если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.
Создайте текстовый файл и введите текст. Вы увидите лампочку слева от текста.
Наведите указатель на лампочку. Вы увидите стрелку вниз.
При щелчке на лампочку, должны отображаться два предложенных действия, а также предварительный просмотр выбранного действия.
Если щелкнуть первое действие, весь текст в текущем слове будет преобразован в верхний регистр. Если щелкнуть второе действие, весь текст должен быть преобразован в нижний регистр.