Exemplarische Vorgehensweise: Anzeigen übereinstimmender Klammern
Implementieren Sie sprachbasierte Features, z. B. den Klammerabgleich, indem Sie die geschweiften Klammern definieren, die Sie abgleichen möchten, und fügen Sie der übereinstimmenden geschweiften Klammer ein Markierungstag hinzu, wenn sich das Caretzeichen auf einer der geschweiften Klammern befindet. Sie können geschweifte Klammern im Kontext einer Sprache definieren, ihre eigene Dateinamenerweiterung und den Inhaltstyp definieren und die Tags nur auf diesen Typ anwenden oder die Tags auf einen vorhandenen Inhaltstyp anwenden (z. B. "Text"). Die folgende exemplarische Vorgehensweise zeigt, wie Sie Klammernvergleichstags auf den Inhaltstyp "Text" anwenden.
Erstellen eines Projekts für verwaltetes Erweiterbarkeitsframework (MEF)
So erstellen Sie ein MEF-Projekt
Erstellen Sie ein Editorklassifiziererprojekt. Nennen Sie die Projektmappe
BraceMatchingTest
.Fügen Sie dem Projekt eine Elementvorlage für Editorklassifizierer hinzu. Weitere Informationen finden Sie unter Erstellen einer Erweiterung mit einer Editorelementvorlage.
Löschen Sie die vorhandenen Klassendateien.
Implementieren eines Klammerabgleichs-Taggers
Um einen Klammer-Hervorhebungseffekt zu erhalten, der dem in Visual Studio verwendeten Effekt ähnelt, können Sie einen Tagger vom Typ TextMarkerTagimplementieren. Der folgende Code zeigt, wie Sie die Tagger für geschweifte Klammerpaare auf einer beliebigen Schachtelungsebene definieren. In diesem Beispiel werden die geschweiften Klammerpaare von [] im {} Tagger-Konstruktor definiert, aber in einer vollständigen Sprachimplementierung würden die relevanten Klammerpaare in der Sprachspezifikation definiert.
So implementieren Sie einen tagger mit geschweiften Klammernabgleich
Fügen Sie eine Klassendatei hinzu, und nennen Sie sie "BraceMatching".
Importieren Sie die folgenden Namespaces.
Definieren Sie eine Klasse
BraceMatchingTagger
, die vom ITagger<T> Typ TextMarkerTagerbt.Fügen Sie Eigenschaften für die Textansicht, den Quellpuffer, den aktuellen Momentaufnahme Punkt und auch eine Reihe von Klammerpaaren hinzu.
Legen Sie im Tagger-Konstruktor die Eigenschaften fest, und abonnieren Sie die Ansichtsänderungsereignisse PositionChanged und LayoutChanged. In diesem Beispiel werden für veranschauliche Zwecke auch die übereinstimmenden Paare im Konstruktor definiert.
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; }
Deklarieren Sie als Teil der ITagger<T> Implementierung ein TagsChanged-Ereignis.
Die Ereignishandler aktualisieren die aktuelle Caretposition der
CurrentChar
Eigenschaft und lösen das TagsChanged-Ereignis aus.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))); }
Implementieren Sie die GetTags Methode, um geschweifte Klammern abzugleichen, entweder wenn es sich bei dem aktuellen Zeichen um eine geöffnete geschweifte Klammer handelt oder wenn das vorherige Zeichen eine enge geschweifte Klammer ist, wie in Visual Studio. Wenn die Übereinstimmung gefunden wird, instanziiert diese Methode zwei Tags, eine für die geöffnete Klammer und eine für die schließende Klammer.
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")); } } }
Die folgenden privaten Methoden finden die übereinstimmende geschweifte Klammer auf einer beliebigen Schachtelungsebene. Die erste Methode findet das Schließen-Zeichen, das dem geöffneten Zeichen entspricht:
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; }
Die folgende Hilfsmethode findet das geöffnete Zeichen, das einem schließenden Zeichen entspricht:
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; }
Implementieren eines Klammerabgleichs-Taggeranbieters
Zusätzlich zur Implementierung eines Taggers müssen Sie auch einen Tagger-Anbieter implementieren und exportieren. In diesem Fall lautet der Inhaltstyp des Anbieters "Text". Der Klammerabgleich wird also in allen Arten von Textdateien angezeigt, aber eine umfassendere Implementierung wendet geschweifte Übereinstimmungen nur auf einen bestimmten Inhaltstyp an.
So implementieren Sie einen Klammerabgleich-Taggeranbieter
Deklarieren Sie einen Tagger-Anbieter, der von IViewTaggerProvider, nennen Sie ihn BraceMatchingTaggerProvider, und exportieren Sie ihn mit einem ContentTypeAttribute "Text" und einem TagTypeAttribute von TextMarkerTag.
Implementieren Sie die CreateTagger Methode, um einen BraceMatchingTagger zu instanziieren.
Erstellen und Testen des Codes
Um diesen Code zu testen, erstellen Sie die BraceMatchingTest-Lösung, und führen Sie sie in der experimentellen Instanz aus.
So erstellen und testen Sie die BraceMatchingTest-Lösung
Erstellen Sie die Projektmappe.
Wenn Sie dieses Projekt im Debugger ausführen, wird eine zweite Instanz von Visual Studio gestartet.
Erstellen Sie eine Textdatei, und geben Sie Text ein, der übereinstimmende geschweifte Klammern enthält.
hello { goodbye} {} {hello}
Wenn Sie das Caret vor einer geöffneten geschweiften Klammer positionieren, sollte sowohl diese geschweifte als auch die entsprechende schließende Klammer hervorgehoben werden. Wenn Sie den Cursor direkt nach der schließenden Klammer positionieren, sollten sowohl diese geschweifte als auch die entsprechende geöffnete Klammer hervorgehoben werden.