Návod: Sbalení

Nastavte funkce založené na jazyce, jako je osnova, definováním typů textových oblastí, které chcete rozbalit nebo sbalit. Oblasti můžete definovat v kontextu služby jazyka nebo definovat vlastní příponu názvu souboru a typ obsahu a použít definici oblasti pouze na tento typ nebo použít definice oblasti u existujícího typu obsahu (například "text"). Tento názorný postup ukazuje, jak definovat a zobrazit oblasti osnovy.

Vytvoření projektu MEF (Managed Extensibility Framework)

Vytvoření projektu MEF

  1. Vytvořte projekt VSIX. Pojmenujte řešení OutlineRegionTest.

  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 osnovy taggeru

Oblasti osnovy jsou označené druhem značky (OutliningRegionTag). Tato značka poskytuje standardní chování osnovy. Obrysová oblast se dá rozbalit nebo sbalit. Obrysová oblast je označená symbolem plus (+), pokud je sbalená nebo znaménko minus (-), pokud je rozbalená, a rozbalená oblast je oddělená svislou čárou.

Následující kroky ukazují, jak definovat tagger, který vytvoří osnovu pro všechny oblasti oddělené hranatými závorkami ([,]).

Implementace osnovy taggeru

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

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

    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. Vytvořte třídu s názvem OutliningTaggera požádejte ji o implementaci ITagger<T>:

    internal sealed class OutliningTagger : ITagger<IOutliningRegionTag>
    
  4. Přidejte některá pole ke sledování textové vyrovnávací paměti a snímku a k nashromáždění sad řádků, které by se měly označit jako oblasti osnovy. Tento kód obsahuje seznam objektů oblastí (které se mají definovat později), které představují oblasti osnovy.

    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. Přidejte konstruktor taggeru, který inicializuje pole, analyzuje vyrovnávací paměť a přidá obslužnou rutinu události do Changed události.

    public OutliningTagger(ITextBuffer buffer)
    {
        this.buffer = buffer;
        this.snapshot = buffer.CurrentSnapshot;
        this.regions = new List<Region>();
        this.ReParse();
        this.buffer.Changed += BufferChanged;
    }
    
  6. Implementujte metodu GetTags , která vytvoří instanci značky. Tento příklad předpokládá, že rozsahy předávané NormalizedSpanCollection metodě jsou souvislé, i když to nemusí být vždy případ. Tato metoda vytvoří instanci nového rozsahu značek pro každou oblast osnovy.

    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. Deklarujte obslužnou rutinu TagsChanged události.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  8. Přidejte obslužnou rutinu BufferChanged události, která reaguje na Changed události parsováním textové vyrovnávací paměti.

    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. Přidejte metodu, která analyzuje vyrovnávací paměť. Příklad uvedený tady je jenom pro ilustraci. Synchronně analyzuje vyrovnávací paměť do vnořených oblastí osnovy.

    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. Následující pomocná metoda získá celé číslo, které představuje úroveň osnovy, tak, že 1 je levá závorka pár.

    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. Následující pomocná metoda přeloží oblast (definovanou dále v tomto článku) do 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. Následující kód je určen pouze pro ilustraci. Definuje třídu PartialRegion, která obsahuje číslo řádku a posun začátku osnovy oblasti a odkaz na nadřazenou oblast (pokud existuje). Tento kód umožňuje analyzátoru nastavit vnořené oblasti osnovy. Odvozená třída oblasti obsahuje odkaz na číslo řádku konce osnovy oblasti.

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

Implementace zprostředkovatele taggeru

Exportujte poskytovatele taggeru pro váš tagger. Zprostředkovatel taggeru OutliningTagger vytvoří vyrovnávací paměť typu obsahu "text", nebo vrátí OutliningTagger , pokud už vyrovnávací paměť obsahuje.

Implementace poskytovatele taggeru

  1. Vytvořte třídu s názvem OutliningTaggerProvider , která implementuje ITaggerProvidera exportuje s atributy ContentType a TagType.

    [Export(typeof(ITaggerProvider))]
    [TagType(typeof(IOutliningRegionTag))]
    [ContentType("text")]
    internal sealed class OutliningTaggerProvider : ITaggerProvider
    
  2. Implementujte metodu CreateTagger přidáním OutliningTagger vlastnosti vyrovnávací paměti.

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

Sestavení a otestování kódu

Tento kód otestujete tak, že sestavíte řešení OutlineRegionTest a spustíte ho v experimentální instanci.

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

  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. Zadejte text, který obsahuje levou závorku i pravou závorku.

    [
       Hello
    ]
    
  4. Měla by existovat osnova, která obsahuje obě hranaté závorky. Měli byste být schopni kliknout na znaménko minus vlevo od otevřené závorky a sbalit oblast osnovy. Když je oblast sbalená, měla by se nalevo od sbalené oblasti zobrazit symbol tří teček (...) a při přesunutí ukazatele myši na tři tečky by se měl zobrazit automaticky otevíraný text obsahující text .