Пошаговое руководство. Отображение сопоставленных фигурных скобок
Реализуйте функции на основе языка, такие как сопоставление фигурных скобок, определив фигурные скобки, которые вы хотите сопоставить, и добавьте тег текстового маркера в соответствующие скобки, когда курсор находится на одном из фигурных скобок. Фигурные скобки можно определить в контексте языка, определить собственное расширение имени файла и тип контента, а также применить теги только к такому типу или применить теги к существующему типу контента (например, "текст"). В следующем пошаговом руководстве показано, как применить теги сопоставления фигурных скобок к типу контента "text".
Создание проекта Managed Extensibility Framework (MEF)
Создание проекта MEF
Создайте проект классификатора редактора. Назовите решение
BraceMatchingTest
.Добавьте в проект шаблон элемента классификатора редактора. Дополнительные сведения: Создание расширения с помощью шаблона элемента редактора.
Удалите файлы существующих классов.
Реализация тега сопоставления фигурных фигурных скобок
Чтобы получить эффект выделения фигурных скобок, похожий на тот, который используется в Visual Studio, можно реализовать тег типа TextMarkerTag. В следующем коде показано, как определить теггер для пар фигурных скобок на любом уровне вложения. В этом примере пары фигурных скобок [] и {} определены в конструкторе тегов, но в полной реализации языка соответствующие пары фигурных скобок будут определены в спецификации языка.
Реализация тега сопоставления фигурных
Добавьте файл класса и назовите его BraceMatching.
Импортируйте следующие пространства имен.
Определите класс
BraceMatchingTagger
, наследующий от ITagger<T> типа TextMarkerTag.Добавьте свойства для текстового представления, исходного буфера, текущей точки моментального снимка, а также набора пар фигурных скобок.
В конструкторе тегов задайте свойства и подпишитесь на события PositionChanged изменения представления и LayoutChanged. В этом примере для иллюстрирующих целей пары сопоставления также определяются в конструкторе.
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<T> событие TagsChanged.
Обработчики событий обновляют текущее положение
CurrentChar
свойства и вызывают событие TagsChanged.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. При обнаружении совпадения этот метод создает экземпляр двух тегов, один для открытой фигурной скобки и один для закрывающей фигурной скобки.
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 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 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. Таким образом, сопоставление фигурных скобок будет отображаться во всех типах текстовых файлов, но полная реализация применяет фигурные скобки только к конкретному типу контента.
Реализация поставщика тегов сопоставления фигурных скобок
Объявите поставщик тегов, наследующий от IViewTaggerProvider, присвойте ему имя BraceMatchingTaggerProvider и экспортируйте его с текстом ContentTypeAttribute и элементом TagTypeAttribute TextMarkerTag.
CreateTagger Реализуйте метод для создания экземпляра BraceMatchingTagger.
Сборка и проверка кода
Чтобы протестировать этот код, создайте решение BraceMatchingTest и запустите его в экспериментальном экземпляре.
Создание и тестирование решения BraceMatchingTest
Постройте решение.
При запуске этого проекта в отладчике запускается второй экземпляр Visual Studio.
Создайте текстовый файл и введите текст, включающий соответствующие фигурные скобки.
hello { goodbye} {} {hello}
При расположении курсора перед открытой фигурной скобкой следует выделить как фигурную скобку, так и соответствующую закрывающую фигурную скобку. При расположении курсора сразу после закрытия фигурной скобки следует выделить оба фигурных скобки и соответствующий открытый фигурный скобки.