Návod: Zobrazení složených závorek

Implementujte funkce založené na jazyce, například párování závorek, definováním složených závorek, které chcete spárovat, a přidáním značky textové značky do shodných závorek, když je stříška na jedné závorkách. Složené závorky můžete definovat v kontextu jazyka, definovat vlastní příponu názvu souboru a typ obsahu a použít značky jenom na tento typ nebo použít značky u existujícího typu obsahu (například "text"). Následující návod ukazuje, jak použít složené závorky odpovídající značky pro typ obsahu "text".

Vytvoření projektu MEF (Managed Extensibility Framework)

Vytvoření projektu MEF

  1. Vytvořte projekt klasifikátoru editoru. Pojmenujte řešení BraceMatchingTest.

  2. Přidejte do projektu šablonu položky klasifikátoru editoru. Další informace najdete v tématu Vytvoření rozšíření pomocí šablony položky editoru.

  3. Odstraňte existující soubory třídy.

Implementace závorky odpovídajícího taggeru

Pokud chcete získat efekt zvýraznění závorek, který se podobá efektu, který se používá v sadě Visual Studio, můžete implementovat tagger typu TextMarkerTag. Následující kód ukazuje, jak definovat tagger pro páry závorek na libovolné úrovni vnoření. V tomto příkladu jsou dvojice závorek [] a {} jsou definovány v konstruktoru taggeru, ale v implementaci celého jazyka by se příslušné dvojice závorek definovaly ve specifikaci jazyka.

Implementace závorky odpovídajícího taggeru

  1. Přidejte soubor třídy a pojmenujte ho BraceMatching.

  2. Naimportujte následující obory názvů.

    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. Definujte třídu BraceMatchingTagger , která dědí z ITagger<T> typu TextMarkerTag.

    internal class BraceMatchingTagger : ITagger<TextMarkerTag>
    
  4. Přidejte vlastnosti pro textové zobrazení, zdrojovou vyrovnávací paměť, aktuální bod snímku a také sadu párů závorek.

    ITextView View { get; set; }
    ITextBuffer SourceBuffer { get; set; }
    SnapshotPoint? CurrentChar { get; set; }
    private Dictionary<char, char> m_braceList;
    
  5. V konstruktoru taggeru nastavte vlastnosti a přihlaste se k odběru událostí PositionChanged změny zobrazení a LayoutChanged. V tomto příkladu jsou pro ilustrativní účely definovány také odpovídající dvojice v konstruktoru.

    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. V rámci ITagger<T> implementace deklarujte událost TagsChanged.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  7. Obslužné rutiny událostí aktualizují aktuální pozici kurzoru CurrentChar vlastnosti a vyvolat TagsChanged událost.

    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. Implementujte metodu GetTags pro porovnání složených závorek buď v případě, že aktuální znak je otevřená složená závorka, nebo pokud je předchozí znak blízkou složenou závorkou, jako v sadě Visual Studio. Když se najde shoda, vytvoří tato metoda instanci dvou značek, jednu pro otevřenou složenou závorku a druhou pro blízkou složenou závorku.

    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. Následující privátní metody najdou odpovídající složenou závorku na libovolné úrovni vnoření. První metoda najde blízký znak, který odpovídá otevřenému znaku:

    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. Následující pomocná metoda najde otevřený znak, který odpovídá úzkému 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;
    }
    

Implementace závorky odpovídajícího poskytovateli taggeru

Kromě implementace taggeru musíte také implementovat a exportovat poskytovatele taggeru. V tomto případě je typ obsahu poskytovatele text. Porovnávání závorek se tedy zobrazí ve všech typech textových souborů, ale úplná implementace použije složenou závorku pouze na určitý typ obsahu.

Implementace závorky odpovídajícího poskytovateli taggeru

  1. Deklarujte zprostředkovatele taggeru, který dědí z IViewTaggerProvider, pojmenujte jej BraceMatchingTaggerProvider a exportujte ho s textem ContentTypeAttribute a TagTypeAttribute z TextMarkerTag.

    [Export(typeof(IViewTaggerProvider))]
    [ContentType("text")]
    [TagType(typeof(TextMarkerTag))]
    internal class BraceMatchingTaggerProvider : IViewTaggerProvider
    
  2. Implementujte metodu CreateTagger pro vytvoření instance BraceMatchingTagger.

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

Sestavení a otestování kódu

Pokud chcete tento kód otestovat, sestavte řešení BraceMatchingTest a spusťte ho v experimentální instanci.

Sestavení a testování řešení BraceMatchingTest

  1. Sestavte řešení.

  2. Když tento projekt spustíte v ladicím programu, spustí se druhá instance sady Visual Studio.

  3. Vytvořte textový soubor a zadejte nějaký text, který obsahuje odpovídající složené závorky.

    hello {
    goodbye}
    
    {}
    
    {hello}
    
  4. Když umístíte kurzor před otevřenou složenou závorku, měla by se zvýraznit obě složená závorka i odpovídající složená závorka. Když umístíte kurzor těsně za závorkou, zvýrazní se obě složené závorky i odpovídající otevřená složená závorka.