Bagikan melalui


Panduan: Menampilkan kurung kurawal yang cocok

Terapkan fitur berbasis bahasa, seperti, pencocokan kurung kurawal dengan menentukan kurung kurawal yang ingin Anda cocokkan, dan tambahkan tag penanda teks ke kurung kurawal yang cocok saat tanda sisipan berada di salah satu tanda kurung kurawal. Anda dapat menentukan kurung kurawal dalam konteks bahasa, menentukan ekstensi nama file dan jenis konten Anda sendiri, dan menerapkan tag hanya untuk jenis tersebut atau menerapkan tag ke jenis konten yang ada (seperti "teks"). Panduan berikut menunjukkan cara menerapkan tag pencocokan kurung kurawal ke tipe konten "teks".

Membuat proyek Managed Extensibility Framework (MEF)

Untuk membuat proyek MEF

  1. Membuat proyek Pengklasifikasi Editor. Beri nama solusi BraceMatchingTest.

  2. Tambahkan templat item Pengklasifikasi Editor ke proyek. Untuk informasi selengkapnya, lihat Membuat ekstensi dengan templat item editor.

  3. Hapus file kelas yang ada.

Menerapkan tagger pencocokan kurung kurawal

Untuk mendapatkan efek penyorotan kurung kurawal yang menyerupai yang digunakan di Visual Studio, Anda dapat menerapkan tagger jenis TextMarkerTag. Kode berikut menunjukkan cara menentukan tagger untuk pasangan kurung kurawal pada tingkat bersarang apa pun. Dalam contoh ini, pasangan kurung kurawal [] dan {} didefinisikan dalam konstruktor tagger, tetapi dalam implementasi bahasa lengkap, pasangan kurung kurawal yang relevan akan didefinisikan dalam spesifikasi bahasa.

Untuk mengimplementasikan tagger pencocokan kurung kurawal

  1. Tambahkan file kelas dan beri nama BraceMatching.

  2. Impor namespace berikut.

    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. Tentukan kelas BraceMatchingTagger yang mewarisi dari ITagger<T> jenis TextMarkerTag.

    internal class BraceMatchingTagger : ITagger<TextMarkerTag>
    
  4. Tambahkan properti untuk tampilan teks, buffer sumber, titik rekam jepret saat ini, dan juga sekumpulan pasangan kurung kurawal.

    ITextView View { get; set; }
    ITextBuffer SourceBuffer { get; set; }
    SnapshotPoint? CurrentChar { get; set; }
    private Dictionary<char, char> m_braceList;
    
  5. Di konstruktor tagger, atur properti dan berlangganan tampilan mengubah peristiwa PositionChanged dan LayoutChanged. Dalam contoh ini, untuk tujuan ilustrasi, pasangan yang cocok juga didefinisikan dalam konstruktor.

    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. Sebagai bagian ITagger<T> dari implementasi, deklarasikan peristiwa TagsChanged.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  7. Penanganan aktivitas memperbarui posisi CurrentChar tanda sisipan properti saat ini dan menaikkan peristiwa 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)));
    }
    
  8. Terapkan GetTags metode untuk mencocokkan kurung kurawal baik ketika karakter saat ini adalah kurung kurawal terbuka atau ketika karakter sebelumnya adalah kurung kurawal dekat, seperti di Visual Studio. Ketika kecocokan ditemukan, metode ini membuat instans dua tag, satu untuk kurung kurawal terbuka dan satu untuk kurung kurawal dekat.

    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. Metode privat berikut menemukan kurung kurawal yang cocok di tingkat berlapis apa pun. Metode pertama menemukan karakter dekat yang cocok dengan karakter terbuka:

    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. Metode pembantu berikut menemukan karakter terbuka yang cocok dengan karakter dekat:

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

Menerapkan penyedia tagger pencocokan kurung kurawal

Selain menerapkan tagger, Anda juga harus menerapkan dan mengekspor penyedia tagger. Dalam hal ini, jenis konten penyedia adalah "teks". Jadi, pencocokan kurung kurawal akan muncul di semua jenis file teks, tetapi implementasi yang lebih lengkap menerapkan pencocokan kurung kurawal hanya untuk jenis konten tertentu.

Untuk mengimplementasikan penyedia tagger pencocokan kurung kurawal

  1. Deklarasikan penyedia tagger yang mewarisi dari IViewTaggerProvider, beri nama BraceMatchingTaggerProvider, dan ekspor dengan ContentTypeAttribute "teks" dan TagTypeAttribute dari TextMarkerTag.

    [Export(typeof(IViewTaggerProvider))]
    [ContentType("text")]
    [TagType(typeof(TextMarkerTag))]
    internal class BraceMatchingTaggerProvider : IViewTaggerProvider
    
  2. Terapkan CreateTagger metode untuk membuat instans 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>;
    }
    

Membangun dan menguji kode

Untuk menguji kode ini, buat solusi BraceMatchingTest dan jalankan dalam instans eksperimental.

Untuk membangun dan menguji solusi BraceMatchingTest

  1. Bangun solusinya.

  2. Saat Anda menjalankan proyek ini di debugger, instans kedua Visual Studio dimulai.

  3. Buat file teks dan ketik beberapa teks yang menyertakan kurung kurawal yang cocok.

    hello {
    goodbye}
    
    {}
    
    {hello}
    
  4. Ketika Anda memposisikan tanda sisipan sebelum kurung kurawal terbuka, kurung kurawal dan kurung dekat yang cocok harus disorot. Ketika Anda memposisikan kursor tepat setelah kurung kurawal dekat, kurung kurawal itu dan kurung kurawal terbuka yang cocok harus disorot.