Przewodnik: wyświetlanie pasujących nawiasów klamrowych
Zaimplementuj funkcje oparte na języku, takie jak dopasowywanie nawiasów klamrowych, definiując nawiasy klamrowe, które chcesz dopasować, i dodając tag znacznika tekstu do pasujących nawiasów klamrowych, gdy daszek znajduje się na jednym z nawiasów klamrowych. Nawiasy klamrowe można zdefiniować w kontekście języka, zdefiniować rozszerzenie nazwy pliku i typ zawartości oraz zastosować tagi tylko do tego typu lub zastosować tagi do istniejącego typu zawartości (na przykład "text"). Poniższy przewodnik przedstawia sposób stosowania pasujących tagów nawiasów klamrowych do typu zawartości "text".
Tworzenie projektu zarządzanej struktury rozszerzalności (MEF)
Aby utworzyć projekt MEF
Utwórz projekt Klasyfikatora edytora. Nadaj rozwiązaniu
BraceMatchingTest
nazwę .Dodaj szablon elementu Klasyfikator edytora do projektu. Aby uzyskać więcej informacji, zobacz Tworzenie rozszerzenia za pomocą szablonu elementu edytora.
Usuń istniejące pliki klas.
Implementowanie pasującego nawiasu klamrowego
Aby uzyskać efekt wyróżniania nawiasów klamrowych przypominający ten, który jest używany w programie Visual Studio, możesz zaimplementować sztylet typu TextMarkerTag. Poniższy kod pokazuje, jak zdefiniować sztylet dla par nawiasów klamrowych na dowolnym poziomie zagnieżdżania. W tym przykładzie pary nawiasów klamrowych [] i {} są zdefiniowane w konstruktorze tagger, ale w pełnej implementacji języka odpowiednie pary nawiasów klamrowych zostaną zdefiniowane w specyfikacji języka.
Aby zaimplementować pasujący nawias klamrowy
Dodaj plik klasy i nadaj mu nazwę BraceMatching.
Zaimportuj następujące przestrzenie nazw.
Zdefiniuj klasę
BraceMatchingTagger
dziedziczą z ITagger<T> typu TextMarkerTag.Dodaj właściwości widoku tekstu, bufor źródłowy, bieżący punkt migawki, a także zestaw par nawiasów klamrowych.
W konstruktorze modułu tagger ustaw właściwości i zasubskrybuj zdarzenia PositionChanged zmiany widoku i LayoutChanged. W tym przykładzie dla celów ilustracyjnych pasujące pary są również zdefiniowane w konstruktorze.
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; }
W ramach implementacji ITagger<T> zadeklaruj zdarzenie TagsChanged.
Programy obsługi zdarzeń aktualizują bieżące położenie
CurrentChar
karetki właściwości i zgłaszają zdarzenie 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))); }
Zaimplementuj metodę GetTags , aby dopasować nawiasy klamrowe, gdy bieżący znak jest otwartym nawiasem klamrowym lub gdy poprzedni znak jest bliskim nawiasem klamrowym, tak jak w programie Visual Studio. Po znalezieniu dopasowania ta metoda tworzy wystąpienie dwóch tagów, jeden dla otwartego nawiasu klamrowego i jeden dla zamykanego nawiasu klamrowego.
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")); } } }
Poniższe metody prywatne znajdują pasujący nawias klamrowy na dowolnym poziomie zagnieżdżania. Pierwsza metoda znajduje znak zamknięcia zgodny z otwartym znakiem:
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; }
Następująca metoda pomocnika znajduje otwarty znak, który pasuje do bliskiego znaku:
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; }
Implementowanie dostawcy dopasowywania nawiasów klamrowych
Oprócz implementacji sztyletu należy również zaimplementować i wyeksportować dostawcę taggera. W takim przypadku typ zawartości dostawcy to "tekst". Dlatego dopasowanie nawiasów klamrowych będzie wyświetlane we wszystkich typach plików tekstowych, ale pełną implementację stosuje dopasowywanie nawiasów klamrowych tylko do określonego typu zawartości.
Aby zaimplementować dostawcę dopasowywania nawiasów klamrowych
Zadeklaruj dostawcę tagger, który dziedziczy z IViewTaggerProvider, nadaj mu nazwę BraceMatchingTaggerProvider i wyeksportuj go za pomocą ContentTypeAttribute ciągu "text" i elementu TagTypeAttribute TextMarkerTag.
Zaimplementuj metodę CreateTagger , aby utworzyć wystąpienie elementu BraceMatchingTagger.
Kompilowanie i testowanie kodu
Aby przetestować ten kod, skompiluj rozwiązanie BraceMatchingTest i uruchom je w wystąpieniu eksperymentalnym.
Aby skompilować i przetestować rozwiązanie BraceMatchingTest
Stwórz rozwiązanie.
Po uruchomieniu tego projektu w debugerze zostanie uruchomione drugie wystąpienie programu Visual Studio.
Utwórz plik tekstowy i wpisz tekst zawierający pasujące nawiasy klamrowe.
hello { goodbye} {} {hello}
Po ustawieniu karetki przed otwartym nawiasem klamrowym należy zaznaczyć zarówno ten nawias klamrowy, jak i pasujący nawias klamrowy zamykający. Po ustawieniu kursora tuż po zamknięciu nawiasu klamrowego należy zaznaczyć zarówno ten nawias klamrowy, jak i pasujący otwarty nawias klamrowy.