Bagikan melalui


Panduan: Menguraikan

Siapkan fitur berbasis bahasa seperti menguraikan dengan menentukan jenis wilayah teks yang ingin Anda perluas atau ciutkan. Anda dapat menentukan wilayah dalam konteks layanan bahasa, atau menentukan ekstensi nama file dan jenis konten Anda sendiri dan menerapkan definisi wilayah hanya ke jenis tersebut, atau menerapkan definisi wilayah ke jenis konten yang ada (seperti "teks"). Panduan ini menunjukkan cara menentukan dan menampilkan wilayah kerangka.

Membuat proyek Managed Extensibility Framework (MEF)

Untuk membuat proyek MEF

  1. Buat proyek VSIX. Beri nama solusi OutlineRegionTest.

  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 kerangka

Wilayah kerangka ditandai oleh semacam tag (OutliningRegionTag). Tag ini menyediakan perilaku kerangka standar. Wilayah yang diuraikan dapat diperluas atau diciutkan. Wilayah yang diuraikan ditandai dengan tanda Plus (+) jika diciutkan atau tanda Minus (-) jika diperluas, dan wilayah yang diperluas didemarasi oleh garis vertikal.

Langkah-langkah berikut menunjukkan cara menentukan tagger yang membuat wilayah kerangka untuk semua wilayah yang dibatasi oleh tanda kurung siku ([,]).

Untuk mengimplementasikan tagger kerangka

  1. Tambahkan file kelas dan beri nama OutliningTagger.

  2. Impor namespace berikut.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Text.Outlining;
    using Microsoft.VisualStudio.Text.Tagging;
    using Microsoft.VisualStudio.Utilities;
    using Microsoft.VisualStudio.Text;
    
  3. Buat kelas bernama OutliningTagger, dan terapkan ITagger<T>:

    internal sealed class OutliningTagger : ITagger<IOutliningRegionTag>
    
  4. Tambahkan beberapa bidang untuk melacak buffer teks dan rekam jepret dan untuk mengakumulasi kumpulan baris yang harus ditandai sebagai wilayah kerangka. Kode ini mencakup daftar objek Wilayah (akan ditentukan nanti) yang mewakili wilayah kerangka.

    string startHide = "[";     //the characters that start the outlining region
    string endHide = "]";       //the characters that end the outlining region
    string ellipsis = "...";    //the characters that are displayed when the region is collapsed
    string hoverText = "hover text"; //the contents of the tooltip for the collapsed span
    ITextBuffer buffer;
    ITextSnapshot snapshot;
    List<Region> regions;
    
  5. Tambahkan konstruktor tagger yang menginisialisasi bidang, mengurai buffer, dan menambahkan penanganan aktivitas ke Changed peristiwa.

    public OutliningTagger(ITextBuffer buffer)
    {
        this.buffer = buffer;
        this.snapshot = buffer.CurrentSnapshot;
        this.regions = new List<Region>();
        this.ReParse();
        this.buffer.Changed += BufferChanged;
    }
    
  6. Terapkan GetTags metode , yang membuat instans rentang tag. Contoh ini mengasumsikan bahwa rentang dalam yang diteruskan NormalizedSpanCollection ke metode berdekatan, meskipun mungkin tidak selalu terjadi. Metode ini membuat instans rentang tag baru untuk setiap wilayah kerangka.

    public IEnumerable<ITagSpan<IOutliningRegionTag>> GetTags(NormalizedSnapshotSpanCollection spans)
    {
        if (spans.Count == 0)
            yield break;
        List<Region> currentRegions = this.regions;
        ITextSnapshot currentSnapshot = this.snapshot;
        SnapshotSpan entire = new SnapshotSpan(spans[0].Start, spans[spans.Count - 1].End).TranslateTo(currentSnapshot, SpanTrackingMode.EdgeExclusive);
        int startLineNumber = entire.Start.GetContainingLine().LineNumber;
        int endLineNumber = entire.End.GetContainingLine().LineNumber;
        foreach (var region in currentRegions)
        {
            if (region.StartLine <= endLineNumber &&
                region.EndLine >= startLineNumber)
            {
                var startLine = currentSnapshot.GetLineFromLineNumber(region.StartLine);
                var endLine = currentSnapshot.GetLineFromLineNumber(region.EndLine);
    
                //the region starts at the beginning of the "[", and goes until the *end* of the line that contains the "]".
                yield return new TagSpan<IOutliningRegionTag>(
                    new SnapshotSpan(startLine.Start + region.StartOffset,
                    endLine.End),
                    new OutliningRegionTag(false, false, ellipsis, hoverText));
            }
        }
    }
    
  7. Mendeklarasikan TagsChanged penanganan aktivitas.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  8. BufferChanged Tambahkan penanganan aktivitas yang merespons Changed peristiwa dengan mengurai buffer teks.

    void BufferChanged(object sender, TextContentChangedEventArgs e)
    {
        // If this isn't the most up-to-date version of the buffer, then ignore it for now (we'll eventually get another change event).
        if (e.After != buffer.CurrentSnapshot)
            return;
        this.ReParse();
    }
    
  9. Tambahkan metode yang mengurai buffer. Contoh yang diberikan di sini hanya untuk ilustrasi. Ini secara sinkron menguraikan buffer ke wilayah kerangka berlapis.

    void ReParse()
    {
        ITextSnapshot newSnapshot = buffer.CurrentSnapshot;
        List<Region> newRegions = new List<Region>();
    
        //keep the current (deepest) partial region, which will have
        // references to any parent partial regions.
        PartialRegion currentRegion = null;
    
        foreach (var line in newSnapshot.Lines)
        {
            int regionStart = -1;
            string text = line.GetText();
    
            //lines that contain a "[" denote the start of a new region.
            if ((regionStart = text.IndexOf(startHide, StringComparison.Ordinal)) != -1)
            {
                int currentLevel = (currentRegion != null) ? currentRegion.Level : 1;
                int newLevel;
                if (!TryGetLevel(text, regionStart, out newLevel))
                    newLevel = currentLevel + 1;
    
                //levels are the same and we have an existing region;
                //end the current region and start the next
                if (currentLevel == newLevel && currentRegion != null)
                {
                    newRegions.Add(new Region()
                    {
                        Level = currentRegion.Level,
                        StartLine = currentRegion.StartLine,
                        StartOffset = currentRegion.StartOffset,
                        EndLine = line.LineNumber
                    });
    
                    currentRegion = new PartialRegion()
                    {
                        Level = newLevel,
                        StartLine = line.LineNumber,
                        StartOffset = regionStart,
                        PartialParent = currentRegion.PartialParent
                    };
                }
                //this is a new (sub)region
                else
                {
                    currentRegion = new PartialRegion()
                    {
                        Level = newLevel,
                        StartLine = line.LineNumber,
                        StartOffset = regionStart,
                        PartialParent = currentRegion
                    };
                }
            }
            //lines that contain "]" denote the end of a region
            else if ((regionStart = text.IndexOf(endHide, StringComparison.Ordinal)) != -1)
            {
                int currentLevel = (currentRegion != null) ? currentRegion.Level : 1;
                int closingLevel;
                if (!TryGetLevel(text, regionStart, out closingLevel))
                    closingLevel = currentLevel;
    
                //the regions match
                if (currentRegion != null &&
                    currentLevel == closingLevel)
                {
                    newRegions.Add(new Region()
                    {
                        Level = currentLevel,
                        StartLine = currentRegion.StartLine,
                        StartOffset = currentRegion.StartOffset,
                        EndLine = line.LineNumber
                    });
    
                    currentRegion = currentRegion.PartialParent;
                }
            }
        }
    
        //determine the changed span, and send a changed event with the new spans
        List<Span> oldSpans =
            new List<Span>(this.regions.Select(r => AsSnapshotSpan(r, this.snapshot)
                .TranslateTo(newSnapshot, SpanTrackingMode.EdgeExclusive)
                .Span));
        List<Span> newSpans =
                new List<Span>(newRegions.Select(r => AsSnapshotSpan(r, newSnapshot).Span));
    
        NormalizedSpanCollection oldSpanCollection = new NormalizedSpanCollection(oldSpans);
        NormalizedSpanCollection newSpanCollection = new NormalizedSpanCollection(newSpans);
    
        //the changed regions are regions that appear in one set or the other, but not both.
        NormalizedSpanCollection removed =
        NormalizedSpanCollection.Difference(oldSpanCollection, newSpanCollection);
    
        int changeStart = int.MaxValue;
        int changeEnd = -1;
    
        if (removed.Count > 0)
        {
            changeStart = removed[0].Start;
            changeEnd = removed[removed.Count - 1].End;
        }
    
        if (newSpans.Count > 0)
        {
            changeStart = Math.Min(changeStart, newSpans[0].Start);
            changeEnd = Math.Max(changeEnd, newSpans[newSpans.Count - 1].End);
        }
    
        this.snapshot = newSnapshot;
        this.regions = newRegions;
    
        if (changeStart <= changeEnd)
        {
            ITextSnapshot snap = this.snapshot;
            if (this.TagsChanged != null)
                this.TagsChanged(this, new SnapshotSpanEventArgs(
                    new SnapshotSpan(this.snapshot, Span.FromBounds(changeStart, changeEnd))));
        }
    }
    
  10. Metode pembantu berikut mendapatkan bilangan bulat yang mewakili tingkat kerangka, sehingga 1 adalah pasangan kurung kurawal paling kiri.

    static bool TryGetLevel(string text, int startIndex, out int level)
    {
        level = -1;
        if (text.Length > startIndex + 3)
        {
            if (int.TryParse(text.Substring(startIndex + 1), out level))
                return true;
        }
    
        return false;
    }
    
  11. Metode pembantu berikut menerjemahkan Wilayah (didefinisikan nanti dalam artikel ini) ke dalam SnapshotSpan.

    static SnapshotSpan AsSnapshotSpan(Region region, ITextSnapshot snapshot)
    {
        var startLine = snapshot.GetLineFromLineNumber(region.StartLine);
        var endLine = (region.StartLine == region.EndLine) ? startLine
             : snapshot.GetLineFromLineNumber(region.EndLine);
        return new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End);
    }
    
  12. Kode berikut hanya untuk ilustrasi. Ini mendefinisikan kelas PartialRegion yang berisi nomor baris dan offset awal wilayah kerangka, dan referensi ke wilayah induk (jika ada). Kode ini memungkinkan pengurai untuk menyiapkan wilayah kerangka berlapis. Kelas Wilayah turunan berisi referensi ke nomor baris akhir wilayah kerangka.

    class PartialRegion
    {
        public int StartLine { get; set; }
        public int StartOffset { get; set; }
        public int Level { get; set; }
        public PartialRegion PartialParent { get; set; }
    }
    
    class Region : PartialRegion
    {
        public int EndLine { get; set; }
    }
    

Menerapkan penyedia tagger

Ekspor penyedia tagger untuk tagger Anda. Penyedia tagger membuat OutliningTagger untuk buffer tipe konten "teks", atau mengembalikan OutliningTagger jika buffer sudah memilikinya.

Untuk mengimplementasikan penyedia tagger

  1. Buat kelas bernama OutliningTaggerProvider yang mengimplementasikan ITaggerProvider, dan ekspor dengan atribut ContentType dan TagType.

    [Export(typeof(ITaggerProvider))]
    [TagType(typeof(IOutliningRegionTag))]
    [ContentType("text")]
    internal sealed class OutliningTaggerProvider : ITaggerProvider
    
  2. Terapkan CreateTagger metode dengan menambahkan OutliningTagger ke properti buffer.

    public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
    {
        //create a single tagger for each buffer.
        Func<ITagger<T>> sc = delegate() { return new OutliningTagger(buffer) as ITagger<T>; };
        return buffer.Properties.GetOrCreateSingletonProperty<ITagger<T>>(sc);
    }
    

Membangun dan menguji kode

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

Untuk membangun dan menguji solusi OutlineRegionTest

  1. Bangun solusinya.

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

  3. Buat file teks. Ketik beberapa teks yang menyertakan tanda kurung siku pembuka dan tanda kurung siku penutup.

    [
       Hello
    ]
    
  4. Harus ada wilayah kerangka yang mencakup kedua tanda kurung siku. Anda harus dapat mengklik Tanda Minus di sebelah kiri tanda kurung buka untuk menciutkan wilayah kerangka. Ketika wilayah diciutkan, simbol elipsis (...) akan muncul di sebelah kiri wilayah yang diciutkan, dan popup yang berisi teks hover teks akan muncul saat Anda memindahkan penunjuk ke elipsis.