Freigeben über


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

  1. Erstellen Sie ein Editorklassifiziererprojekt. Nennen Sie die Projektmappe BraceMatchingTest.

  2. Fügen Sie dem Projekt eine Elementvorlage für Editorklassifizierer hinzu. Weitere Informationen finden Sie unter Erstellen einer Erweiterung mit einer Editorelementvorlage.

  3. 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

  1. Fügen Sie eine Klassendatei hinzu, und nennen Sie sie "BraceMatching".

  2. Importieren Sie die folgenden Namespaces.

    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;
    
  3. Definieren Sie eine Klasse BraceMatchingTagger , die vom ITagger<T> Typ TextMarkerTagerbt.

    internal class BraceMatchingTagger : ITagger<TextMarkerTag>
    
  4. Fügen Sie Eigenschaften für die Textansicht, den Quellpuffer, den aktuellen Momentaufnahme Punkt und auch eine Reihe von Klammerpaaren hinzu.

    ITextView View { get; set; }
    ITextBuffer SourceBuffer { get; set; }
    SnapshotPoint? CurrentChar { get; set; }
    private Dictionary<char, char> m_braceList;
    
  5. 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;
    }
    
  6. Deklarieren Sie als Teil der ITagger<T> Implementierung ein TagsChanged-Ereignis.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  7. 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)));
    }
    
  8. 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"));
            }
        }
    }
    
  9. 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;
    }
    
  10. 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

  1. 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.

    [Export(typeof(IViewTaggerProvider))]
    [ContentType("text")]
    [TagType(typeof(TextMarkerTag))]
    internal class BraceMatchingTaggerProvider : IViewTaggerProvider
    
  2. Implementieren Sie die CreateTagger Methode, um einen BraceMatchingTagger zu instanziieren.

    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>;
    }
    

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

  1. Erstellen Sie die Projektmappe.

  2. Wenn Sie dieses Projekt im Debugger ausführen, wird eine zweite Instanz von Visual Studio gestartet.

  3. Erstellen Sie eine Textdatei, und geben Sie Text ein, der übereinstimmende geschweifte Klammern enthält.

    hello {
    goodbye}
    
    {}
    
    {hello}
    
  4. 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.