Пошаговое руководство. Отображение завершения операторов
Вы можете реализовать завершение инструкции на основе языка, определив идентификаторы, для которых требуется предоставить завершение, а затем активировав сеанс завершения. Вы можете определить завершение инструкции в контексте языковой службы, определить собственное расширение имени файла и тип контента, а затем отобразить завершение только для этого типа. Кроме того, можно активировать завершение для существующего типа контента, например "обычный текст". В этом пошаговом руководстве показано, как активировать завершение инструкции для типа контента "обычный текст", который является типом контента текстовых файлов. Тип контента "text" является предком всех других типов контента, включая код и XML-файлы.
Завершение инструкции обычно активируется путем ввода определенных символов, например путем ввода начала идентификатора, например using. Обычно оно закрывается путем нажатия клавиши ПРОБЕЛ, ВКЛАДКА или ВВОД , чтобы зафиксировать выделение. Вы можете реализовать функции IntelliSense, которые активируются при вводе символа с помощью обработчика команд для нажатий клавиш ( IOleCommandTarget интерфейса) и поставщика обработчика, реализующего IVsTextViewCreationListener интерфейс. Чтобы создать источник завершения, который представляет собой список идентификаторов, участвующих в завершении, реализуйте ICompletionSource интерфейс и поставщик источника завершения ( ICompletionSourceProvider интерфейс). Поставщики являются компонентами компонента Managed Extensibility Framework (MEF). Они отвечают за экспорт классов исходного и контроллера и импорта служб и брокеров, например ITextStructureNavigatorSelectorService, навигации в текстовом буфере, а также ICompletionBrokerактивации сеанса завершения.
В этом пошаговом руководстве показано, как реализовать завершение инструкции для жестко закодированного набора идентификаторов. В полных реализациях языковая служба и языковая документация отвечают за предоставление этого содержимого.
Создание проекта MEF
Создание проекта MEF
Создайте проект VSIX на C#. (В Диалоговое окно "Новый проект" , выберите Visual C# / Расширяемость, а затем ПРОЕКТ VSIX.) Назовите решение
CompletionTest
.Добавьте в проект шаблон элемента классификатора редактора. Дополнительные сведения: Создание расширения с помощью шаблона элемента редактора.
Удалите файлы существующих классов.
Добавьте следующие ссылки в проект и убедитесь, что copyLocal имеет значение
false
:Microsoft.VisualStudio.Editor
Microsoft.VisualStudio.Language.Intellisense
Microsoft.VisualStudio.OLE.Interop
Microsoft.VisualStudio.Shell.15.0
Microsoft.VisualStudio.Shell.Immutable.10.0
Microsoft.VisualStudio.TextManager.Interop
Реализация источника завершения
Источник завершения отвечает за сбор набора идентификаторов и добавление содержимого в окно завершения, когда пользователь вводит триггер завершения, например первые буквы идентификатора. В этом примере идентификаторы и их описания жестко закодируются в методе AugmentCompletionSession . В большинстве реальных версий используется средство синтаксического анализа языка, чтобы получить маркеры для заполнения списка завершения.
Реализация источника завершения
Добавьте файл класса с именем
TestCompletionSource
.Добавьте следующие импорты:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.Composition; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities;
Измените объявление класса таким
TestCompletionSource
образом, чтобы он реализовал ICompletionSource:Добавьте частные поля для исходного поставщика, текстового буфера Completion и списка объектов (которые соответствуют идентификаторам, которые будут участвовать в сеансе завершения):
Добавьте конструктор, который задает исходный поставщик и буфер. Класс
TestCompletionSourceProvider
определяется в последующих шагах:AugmentCompletionSession Реализуйте метод, добавив набор завершения, содержащий завершения, которые необходимо предоставить в контексте. Каждый набор завершения содержит набор завершений Completion и соответствует вкладке окна завершения. (В проектах Visual Basic вкладки окна завершения называются Common and All.) Метод
FindTokenSpanAtPosition
определен на следующем шаге.void ICompletionSource.AugmentCompletionSession(ICompletionSession session, IList<CompletionSet> completionSets) { List<string> strList = new List<string>(); strList.Add("addition"); strList.Add("adaptation"); strList.Add("subtraction"); strList.Add("summation"); m_compList = new List<Completion>(); foreach (string str in strList) m_compList.Add(new Completion(str, str, str, null, null)); completionSets.Add(new CompletionSet( "Tokens", //the non-localized title of the tab "Tokens", //the display title of the tab FindTokenSpanAtPosition(session.GetTriggerPoint(m_textBuffer), session), m_compList, null)); }
Следующий метод используется для поиска текущего слова из позиции курсора:
private ITrackingSpan FindTokenSpanAtPosition(ITrackingPoint point, ICompletionSession session) { SnapshotPoint currentPoint = (session.TextView.Caret.Position.BufferPosition) - 1; ITextStructureNavigator navigator = m_sourceProvider.NavigatorService.GetTextStructureNavigator(m_textBuffer); TextExtent extent = navigator.GetExtentOfWord(currentPoint); return currentPoint.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive); }
Dispose()
Реализуйте метод:
Реализация поставщика источника завершения
Поставщик источника завершения — это часть компонента MEF, которая создает экземпляр источника завершения.
Реализация поставщика источника завершения
Добавьте класс с именем
TestCompletionSourceProvider
, который реализует ICompletionSourceProvider. Экспортируйте этот класс с ContentTypeAttribute помощью обычного текста и NameAttribute завершения теста.ITextStructureNavigatorSelectorServiceИмпортируйте слово, которое находит текущее слово в источнике завершения.
TryCreateCompletionSource Реализуйте метод для создания экземпляра источника завершения.
Реализация поставщика обработчика команд завершения
Поставщик обработчика команд завершения является производным от IVsTextViewCreationListenerсобытия создания текстового представления и преобразует представление из
Реализация поставщика обработчика команд завершения
Добавьте файл с именем
TestCompletionCommandHandler
.Добавьте следующие директивы using:
using System; using System.ComponentModel.Composition; using System.Runtime.InteropServices; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Utilities;
Добавьте класс с именем
TestCompletionHandlerProvider
, который реализует IVsTextViewCreationListener. Экспортируйте этот класс с NameAttribute помощью обработчика завершения маркера, ContentTypeAttribute открытого текста и а TextViewRoleAttribute Editable.IVsEditorAdaptersFactoryServiceИмпортируйте , который позволяет преобразовать из a IVsTextView ITextView, a, a ICompletionBrokerи aSVsServiceProvider, что обеспечивает доступ к стандартным службам Visual Studio.
VsTextViewCreated Реализуйте метод для создания экземпляра обработчика команд.
public void VsTextViewCreated(IVsTextView textViewAdapter) { ITextView textView = AdapterService.GetWpfTextView(textViewAdapter); if (textView == null) return; Func<TestCompletionCommandHandler> createCommandHandler = delegate() { return new TestCompletionCommandHandler(textViewAdapter, textView, this); }; textView.Properties.GetOrCreateSingletonProperty(createCommandHandler); }
Реализация обработчика команд завершения
Так как завершение инструкции активируется нажатием клавиш, необходимо реализовать IOleCommandTarget интерфейс для получения и обработки нажатий клавиш, которые активируют, фиксируют и закрывает сеанс завершения.
Реализация обработчика команд завершения
Добавьте класс с именем
TestCompletionCommandHandler
, реализующим IOleCommandTarget:Добавьте частные поля для следующего обработчика команд (в которое передается команда), текстовое представление, поставщик обработчика команд (который обеспечивает доступ к различным службам) и сеанс завершения:
Добавьте конструктор, который задает текстовое представление и поля поставщика, и добавляет команду в цепочку команд:
Реализуйте метод, передав команду следующим способом QueryStatus :
Реализуйте метод Exec. Когда этот метод получает нажатие клавиши, он должен выполнить одно из следующих действий:
Разрешить запись символа в буфер, а затем активировать или отфильтровать его. (Печать символов делает это.)
Зафиксируйте завершение, но не разрешайте запись символа в буфер. (Пробелы, Вкладка и ввод выполняются при отображении сеанса завершения.)
Разрешить передаче команды следующему обработчику. (Все другие команды.)
Так как этот метод может отображать пользовательский интерфейс, вызов IsInAutomationFunction , чтобы убедиться, что он не вызывается в контексте автоматизации:
public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { if (VsShellUtilities.IsInAutomationFunction(m_provider.ServiceProvider)) { return m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); } //make a copy of this so we can look at it after forwarding some commands uint commandID = nCmdID; char typedChar = char.MinValue; //make sure the input is a char before getting it if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR) { typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn); } //check for a commit character if (nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN || nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB || (char.IsWhiteSpace(typedChar) || char.IsPunctuation(typedChar))) { //check for a selection if (m_session != null && !m_session.IsDismissed) { //if the selection is fully selected, commit the current session if (m_session.SelectedCompletionSet.SelectionStatus.IsSelected) { m_session.Commit(); //also, don't add the character to the buffer return VSConstants.S_OK; } else { //if there is no selection, dismiss the session m_session.Dismiss(); } } } //pass along the command so the char is added to the buffer int retVal = m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); bool handled = false; if (!typedChar.Equals(char.MinValue) && char.IsLetterOrDigit(typedChar)) { if (m_session == null || m_session.IsDismissed) // If there is no active session, bring up completion { this.TriggerCompletion(); m_session.Filter(); } else //the completion session is already active, so just filter { m_session.Filter(); } handled = true; } else if (commandID == (uint)VSConstants.VSStd2KCmdID.BACKSPACE //redo the filter if there is a deletion || commandID == (uint)VSConstants.VSStd2KCmdID.DELETE) { if (m_session != null && !m_session.IsDismissed) m_session.Filter(); handled = true; } if (handled) return VSConstants.S_OK; return retVal; }
Этот код является частным методом, который активирует сеанс завершения:
private bool TriggerCompletion() { //the caret must be in a non-projection location SnapshotPoint? caretPoint = m_textView.Caret.Position.Point.GetPoint( textBuffer => (!textBuffer.ContentType.IsOfType("projection")), PositionAffinity.Predecessor); if (!caretPoint.HasValue) { return false; } m_session = m_provider.CompletionBroker.CreateCompletionSession (m_textView, caretPoint.Value.Snapshot.CreateTrackingPoint(caretPoint.Value.Position, PointTrackingMode.Positive), true); //subscribe to the Dismissed event on the session m_session.Dismissed += this.OnSessionDismissed; m_session.Start(); return true; }
Следующий пример — частный метод, который отменяет подписку Dismissed из события:
Сборка и проверка кода
Чтобы проверить этот код, создайте решение CompletionTest и запустите его в экспериментальном экземпляре.
Создание и тестирование решения CompletionTest
Постройте решение.
При запуске этого проекта в отладчике запускается второй экземпляр Visual Studio.
Создайте текстовый файл и введите текст, содержащий слово "добавить".
При вводе первого "a" и "d" должен появиться список, содержащий "дополнение" и "адаптация". Обратите внимание, что выбрано добавление. При вводе другого "d" список должен содержать только "дополнение", которое теперь выбрано. Можно зафиксировать "дополнение", нажав клавишу ПРОБЕЛ, ВКЛАДКА или ВВОД , или закрыть список, введя клавишу ESC или любой другой ключ.