Пошаговое руководство: Отображение связывание квадратные скобки
Можно реализовать язык-основанные функции, такие как фигурная скобка соответствующий, указав скобки, который необходимо сопоставить, а затем добавить тег текстовой метки в соответствующий расчалкам, когда курсор на одну из фигурных скобок. Можно указать скобки в контексте языка или можно определить собственные расширения имени файла и тип содержимого и применение тегов к этому типу или просто можно применить теги в существующий тип содержимого (например "текст"). В следующем пошаговом руководстве демонстрируется применение тегов фигурные скобки соответствующего с типом содержимого "text".
Обязательные компоненты
Чтобы выполнить это пошаговое руководство, необходимо устанавливать SDK для Visual Studio 2010.
Примечание
Дополнительные сведения о пакете SDK для Visual Studio см. в разделе интеграция SDK Visual Studio.Чтобы узнать, как загрузить пакет SDK для Visual Studio см. в разделе Центр разработчиков расширяемости Visual Studio на веб-сайте MSDN.
Создание управляемой расширяемости проекта .NET Framework (платформа MEF)
Создание проекта MEF
Создайте проект классификатора редактора. Назовите решение BraceMatchingTest.
Откройте файл source.extension.vsixmanifest в редакторе манифеста VSIX.
Убедитесь, что Content заголовок содержит тип содержимого и компонент MEF Path имеет значение BraceMatchingTest.dll.
Сохранить и закрыть Source.extension.vsixmanifest.
Удалите существующие файлы классов.
Реализация средство создания тегов для фигурные скобки соответствующего
Для получения расчалку выбирая эффект, который похож на метод, который используется в Visual Studio можно реализовать средство создания тегов для типа TextMarkerTag. В следующем коде показано, как задать средство создания тегов для пары фигурных скобок, на любом уровне вложенности. В этом примере пара фигурных скобок []. [], {} и укажите в конструкторе средство создания тегов для полной реализации языка, но в соответствующие пары фигурных скобок, определенные в спецификации языка.
Реализовать средство создания тегов для фигурные скобки соответствующего
Добавьте файл классов и назовите его BraceMatching.
Импортировать следующие пространства имен.
Imports System.ComponentModel.Composition Imports Microsoft.VisualStudio.Text Imports Microsoft.VisualStudio.Text.Editor Imports Microsoft.VisualStudio.Text.Tagging Imports Microsoft.VisualStudio.Utilities
using System; using System.Linq; using System.Collections.Generic; using System.ComponentModel.Composition; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities;
Определите класс BraceMatchingTagger он наследует ITagger type TextMarkerTag.
Friend Class BraceMatchingTagger Implements ITagger(Of TextMarkerTag)
internal class BraceMatchingTagger : ITagger<TextMarkerTag>
Добавьте свойства для представления текста буфера источника и текущего точки снимка, а также набора пар скобок.
Private _View As ITextView Private Property View() As ITextView Get Return _View End Get Set(ByVal value As ITextView) _View = value End Set End Property Private _SourceBuffer As ITextBuffer Private Property SourceBuffer() As ITextBuffer Get Return _SourceBuffer End Get Set(ByVal value As ITextBuffer) _SourceBuffer = value End Set End Property Private _CurrentChar As System.Nullable(Of SnapshotPoint) Private Property CurrentChar() As System.Nullable(Of SnapshotPoint) Get Return _CurrentChar End Get Set(ByVal value As System.Nullable(Of SnapshotPoint)) _CurrentChar = value End Set End Property Private m_braceList As Dictionary(Of Char, Char)
ITextView View { get; set; } ITextBuffer SourceBuffer { get; set; } SnapshotPoint? CurrentChar { get; set; } private Dictionary<char, char> m_braceList;
В конструкторе средство создания тегов для задайте свойства и подписаться на события изменения представления PositionChanged и LayoutChanged. В этом примере для иллюстративных целях, совпадающие пары также определяются в конструкторе.
Friend Sub New(ByVal view As ITextView, ByVal sourceBuffer As ITextBuffer) 'here the keys are the open braces, and the values are the close braces m_braceList = New Dictionary(Of Char, Char)() m_braceList.Add("{"c, "}"c) m_braceList.Add("["c, "]"c) m_braceList.Add("("c, ")"c) Me.View = view Me.SourceBuffer = sourceBuffer Me.CurrentChar = Nothing AddHandler Me.View.Caret.PositionChanged, AddressOf Me.CaretPositionChanged AddHandler Me.View.LayoutChanged, AddressOf Me.ViewLayoutChanged End Sub
internal BraceMatchingTagger(ITextView view, ITextBuffer sourceBuffer) { //here the keys are the open braces, and the values are the close braces m_braceList = new Dictionary<char, char>(); m_braceList.Add('{', '}'); m_braceList.Add('[', ']'); m_braceList.Add('(', ')'); this.View = view; this.SourceBuffer = sourceBuffer; this.CurrentChar = null; this.View.Caret.PositionChanged += CaretPositionChanged; this.View.LayoutChanged += ViewLayoutChanged; }
Как часть ITagger реализация объявляет событие TagsChanged.
Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) _ Implements ITagger(Of TextMarkerTag).TagsChanged
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
Обработчики событий текущая позиция курсора refresh CurrentChar свойство и вызывает события TagsChanged.
Private Sub ViewLayoutChanged(ByVal sender As Object, ByVal e As TextViewLayoutChangedEventArgs) If e.NewSnapshot IsNot e.OldSnapshot Then 'make sure that there has really been a change UpdateAtCaretPosition(View.Caret.Position) End If End Sub Private Sub CaretPositionChanged(ByVal sender As Object, ByVal e As CaretPositionChangedEventArgs) UpdateAtCaretPosition(e.NewPosition) End Sub Private Sub UpdateAtCaretPosition(ByVal caretPosition As CaretPosition) CurrentChar = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity) If Not CurrentChar.HasValue Then Exit Sub End If RaiseEvent TagsChanged(Me, New SnapshotSpanEventArgs(New SnapshotSpan(SourceBuffer.CurrentSnapshot, 0, SourceBuffer.CurrentSnapshot.Length))) End Sub
void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) { if (e.NewSnapshot != e.OldSnapshot) //make sure that there has really been a change { UpdateAtCaretPosition(View.Caret.Position); } } void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e) { UpdateAtCaretPosition(e.NewPosition); } void UpdateAtCaretPosition(CaretPosition caretPosition) { CurrentChar = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity); if (!CurrentChar.HasValue) return; var tempEvent = TagsChanged; if (tempEvent != null) tempEvent(this, new SnapshotSpanEventArgs(new SnapshotSpan(SourceBuffer.CurrentSnapshot, 0, SourceBuffer.CurrentSnapshot.Length))); }
Реализуйте GetTags метод к совпадению взаимодействует, если текущий символ открытая фигурная скобка или если предыдущий символ близкая фигурная скобка, как в Visual Studio. При совпадении, этот метод создает 2 тегов, один для открытой фигурные скобки и один для ближайшей фигурные скобки.
Public Function GetTags(ByVal spans As NormalizedSnapshotSpanCollection) As IEnumerable(Of ITagSpan(Of TextMarkerTag)) Implements ITagger(Of Microsoft.VisualStudio.Text.Tagging.TextMarkerTag).GetTags If spans.Count = 0 Then 'there is no content in the buffer Exit Function End If 'don't do anything if the current SnapshotPoint is not initialized or at the end of the buffer If Not CurrentChar.HasValue OrElse CurrentChar.Value.Position >= CurrentChar.Value.Snapshot.Length Then Exit Function End If 'hold on to a snapshot of the current character Dim currentChar__1 As SnapshotPoint = CurrentChar.Value 'if the requested snapshot isn't the same as the one the brace is on, translate our spans to the expected snapshot If spans(0).Snapshot IsNot currentChar__1.Snapshot Then currentChar__1 = currentChar__1.TranslateTo(spans(0).Snapshot, PointTrackingMode.Positive) End If 'get the current char and the previous char Dim currentText As Char = currentChar__1.GetChar() Dim lastChar As SnapshotPoint = If(CInt(currentChar__1) = 0, currentChar__1, currentChar__1 - 1) 'if currentChar is 0 (beginning of buffer), don't move it back Dim lastText As Char = lastChar.GetChar() Dim pairSpan As New SnapshotSpan() If m_braceList.ContainsKey(currentText) Then 'the key is the open brace Dim closeChar As Char m_braceList.TryGetValue(currentText, closeChar) If BraceMatchingTagger.FindMatchingCloseChar(currentChar__1, currentText, closeChar, View.TextViewLines.Count, pairSpan) = True Then Exit Function End If ElseIf m_braceList.ContainsValue(lastText) Then 'the value is the close brace, which is the *previous* character Dim open = From n In m_braceList _ Where n.Value.Equals(lastText) _ Select n.Key If BraceMatchingTagger.FindMatchingOpenChar(lastChar, CChar(open.ElementAt(0)), lastText, View.TextViewLines.Count, pairSpan) = True Then Exit Function End If End If End Function
public IEnumerable<ITagSpan<TextMarkerTag>> GetTags(NormalizedSnapshotSpanCollection spans) { if (spans.Count == 0) //there is no content in the buffer yield break; //don't do anything if the current SnapshotPoint is not initialized or at the end of the buffer if (!CurrentChar.HasValue || CurrentChar.Value.Position >= CurrentChar.Value.Snapshot.Length) yield break; //hold on to a snapshot of the current character SnapshotPoint currentChar = CurrentChar.Value; //if the requested snapshot isn't the same as the one the brace is on, translate our spans to the expected snapshot if (spans[0].Snapshot != currentChar.Snapshot) { currentChar = currentChar.TranslateTo(spans[0].Snapshot, PointTrackingMode.Positive); } //get the current char and the previous char char currentText = currentChar.GetChar(); SnapshotPoint lastChar = currentChar == 0 ? currentChar : currentChar - 1; //if currentChar is 0 (beginning of buffer), don't move it back char lastText = lastChar.GetChar(); SnapshotSpan pairSpan = new SnapshotSpan(); if (m_braceList.ContainsKey(currentText)) //the key is the open brace { char closeChar; m_braceList.TryGetValue(currentText, out closeChar); if (BraceMatchingTagger.FindMatchingCloseChar(currentChar, currentText, closeChar, View.TextViewLines.Count, out pairSpan) == true) { yield return new TagSpan<TextMarkerTag>(new SnapshotSpan(currentChar, 1), new TextMarkerTag("blue")); yield return new TagSpan<TextMarkerTag>(pairSpan, new TextMarkerTag("blue")); } } else if (m_braceList.ContainsValue(lastText)) //the value is the close brace, which is the *previous* character { var open = from n in m_braceList where n.Value.Equals(lastText) select n.Key; if (BraceMatchingTagger.FindMatchingOpenChar(lastChar, (char)open.ElementAt<char>(0), lastText, View.TextViewLines.Count, out pairSpan) == true) { yield return new TagSpan<TextMarkerTag>(new SnapshotSpan(lastChar, 1), new TextMarkerTag("blue")); yield return new TagSpan<TextMarkerTag>(pairSpan, new TextMarkerTag("blue")); } } }
Следующие закрытые методы поиска соответствующего фигурная скобка на любом уровне вложенности. Первый метод находит ближайшее символ, соответствующий открытый знак:
Private Shared Function FindMatchingCloseChar(ByVal startPoint As SnapshotPoint, ByVal open As Char, ByVal close As Char, ByVal maxLines As Integer, ByRef pairSpan As SnapshotSpan) As Boolean pairSpan = New SnapshotSpan(startPoint.Snapshot, 1, 1) Dim line As ITextSnapshotLine = startPoint.GetContainingLine() Dim lineText As String = line.GetText() Dim lineNumber As Integer = line.LineNumber Dim offset As Integer = startPoint.Position - line.Start.Position + 1 Dim stopLineNumber As Integer = startPoint.Snapshot.LineCount - 1 If maxLines > 0 Then stopLineNumber = Math.Min(stopLineNumber, lineNumber + maxLines) End If Dim openCount As Integer = 0 While True 'walk the entire line While offset < line.Length Dim currentChar As Char = lineText(offset) If currentChar = close Then 'found the close character If openCount > 0 Then openCount -= 1 Else 'found the matching close pairSpan = New SnapshotSpan(startPoint.Snapshot, line.Start + offset, 1) Return True End If ElseIf currentChar = open Then ' this is another open openCount += 1 End If offset += 1 End While 'move on to the next line If System.Threading.Interlocked.Increment(lineNumber) > stopLineNumber Then Exit While End If line = line.Snapshot.GetLineFromLineNumber(lineNumber) lineText = line.GetText() offset = 0 End While Return False End Function
private static bool FindMatchingCloseChar(SnapshotPoint startPoint, char open, char close, int maxLines, out SnapshotSpan pairSpan) { pairSpan = new SnapshotSpan(startPoint.Snapshot, 1, 1); ITextSnapshotLine line = startPoint.GetContainingLine(); string lineText = line.GetText(); int lineNumber = line.LineNumber; int offset = startPoint.Position - line.Start.Position + 1; int stopLineNumber = startPoint.Snapshot.LineCount - 1; if (maxLines > 0) stopLineNumber = Math.Min(stopLineNumber, lineNumber + maxLines); int openCount = 0; while (true) { //walk the entire line while (offset < line.Length) { char currentChar = lineText[offset]; if (currentChar == close) //found the close character { if (openCount > 0) { openCount--; } else //found the matching close { pairSpan = new SnapshotSpan(startPoint.Snapshot, line.Start + offset, 1); return true; } } else if (currentChar == open) // this is another open { openCount++; } offset++; } //move on to the next line if (++lineNumber > stopLineNumber) break; line = line.Snapshot.GetLineFromLineNumber(lineNumber); lineText = line.GetText(); offset = 0; } return false; }
Следующий вспомогательный метод находит открыть это символ, соответствующий близкому знак:
Private Shared Function FindMatchingOpenChar(ByVal startPoint As SnapshotPoint, ByVal open As Char, ByVal close As Char, ByVal maxLines As Integer, ByRef pairSpan As SnapshotSpan) As Boolean pairSpan = New SnapshotSpan(startPoint, startPoint) Dim line As ITextSnapshotLine = startPoint.GetContainingLine() Dim lineNumber As Integer = line.LineNumber Dim offset As Integer = startPoint - line.Start - 1 'move the offset to the character before this one 'if the offset is negative, move to the previous line If offset < 0 Then line = line.Snapshot.GetLineFromLineNumber(System.Threading.Interlocked.Decrement(lineNumber)) offset = line.Length - 1 End If Dim lineText As String = line.GetText() Dim stopLineNumber As Integer = 0 If maxLines > 0 Then stopLineNumber = Math.Max(stopLineNumber, lineNumber - maxLines) End If Dim closeCount As Integer = 0 While True ' Walk the entire line While offset >= 0 Dim currentChar As Char = lineText(offset) If currentChar = open Then If closeCount > 0 Then closeCount -= 1 Else ' We've found the open character pairSpan = New SnapshotSpan(line.Start + offset, 1) 'we just want the character itself Return True End If ElseIf currentChar = close Then closeCount += 1 End If offset -= 1 End While ' Move to the previous line If System.Threading.Interlocked.Decrement(lineNumber) < stopLineNumber Then Exit While End If line = line.Snapshot.GetLineFromLineNumber(lineNumber) lineText = line.GetText() offset = line.Length - 1 End While Return False End Function
private static bool FindMatchingOpenChar(SnapshotPoint startPoint, char open, char close, int maxLines, out SnapshotSpan pairSpan) { pairSpan = new SnapshotSpan(startPoint, startPoint); ITextSnapshotLine line = startPoint.GetContainingLine(); int lineNumber = line.LineNumber; int offset = startPoint - line.Start - 1; //move the offset to the character before this one //if the offset is negative, move to the previous line if (offset < 0) { line = line.Snapshot.GetLineFromLineNumber(--lineNumber); offset = line.Length - 1; } string lineText = line.GetText(); int stopLineNumber = 0; if (maxLines > 0) stopLineNumber = Math.Max(stopLineNumber, lineNumber - maxLines); int closeCount = 0; while (true) { // Walk the entire line while (offset >= 0) { char currentChar = lineText[offset]; if (currentChar == open) { if (closeCount > 0) { closeCount--; } else // We've found the open character { pairSpan = new SnapshotSpan(line.Start + offset, 1); //we just want the character itself return true; } } else if (currentChar == close) { closeCount++; } offset--; } // Move to the previous line if (--lineNumber < stopLineNumber) break; line = line.Snapshot.GetLineFromLineNumber(lineNumber); lineText = line.GetText(); offset = line.Length - 1; } return false; }
Реализация поставщика средство создания тегов для фигурные скобки соответствующего
Помимо средство создания тегов для необходимо также реализовать и экспортировать средство создания тегов для поставщика. В этом случае поставщика с типом содержимого "text". Это означает, что проверка парности фигурных скобок появится в все типы текстовых файлов, но более сложную реализацию применитьTfа бы фигурная скобка соответствующий только к определенному типу содержимого.
Средство создания тегов для реализации поставщика фигурные скобки соответствующего
Объявите средство создания тегов для которого наследуется от поставщика IViewTaggerProviderназовите его BraceMatchingTaggerProvider и экспортировать его a ContentTypeAttribute "text" и " a " TagTypeAttributeTextMarkerTag.
<Export(GetType(IViewTaggerProvider))> _ <ContentType("text")> _ <TagType(GetType(TextMarkerTag))> _ Friend Class BraceMatchingTaggerProvider Implements IViewTaggerProvider
[Export(typeof(IViewTaggerProvider))] [ContentType("text")] [TagType(typeof(TextMarkerTag))] internal class BraceMatchingTaggerProvider : IViewTaggerProvider
Реализуйте CreateTagger``1 метод создания экземпляра BraceMatchingTagger.
Public Function CreateTagger(Of T As ITag)(ByVal textView As ITextView, ByVal buffer As ITextBuffer) As ITagger(Of T) Implements IViewTaggerProvider.CreateTagger If textView Is Nothing Then Return Nothing End If 'provide highlighting only on the top-level buffer If textView.TextBuffer IsNot buffer Then Return Nothing End If Return TryCast(New BraceMatchingTagger(textView, buffer), ITagger(Of T)) End Function
public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag { if (textView == null) return null; //provide highlighting only on the top-level buffer if (textView.TextBuffer != buffer) return null; return new BraceMatchingTagger(textView, buffer) as ITagger<T>; }
Построение и тестирование кода
Для тестирования этого кода выполните построение решения BraceMatchingTest и запустите его в экспериментальном экземпляре.
Построение и тестирование решение BraceMatchingTest
Выполните построение решения.
Если запустить этот проект в отладчике, второй экземпляр Visual Studio создается.
Создайте текстовый файл и введите текст, который включает несколько соответствующих фигурные скобки.
hello { goodbye} {} {hello}
Если располагаете курсора до того, как открытая фигурная скобка, оба, взаимодействующие и соответствующие близкая фигурная скобка должна быть выделена. Если курсор находится сразу после располагаете близкая фигурная скобка, оба, взаимодействующие и соответствующие открытая фигурная скобка должна быть выделена.
См. также
Задачи
Пошаговое руководство: Связывание тип контента в расширение имени файла