Przewodnik: tworzenie konspektu

Skonfiguruj funkcje oparte na języku, takie jak konspektowanie, definiując rodzaje regionów tekstu, które chcesz rozwinąć lub zwinąć. Regiony można zdefiniować w kontekście usługi językowej lub zdefiniować własne rozszerzenie nazwy pliku i typ zawartości oraz zastosować definicję regionu tylko do tego typu lub zastosować definicje regionów do istniejącego typu zawartości (na przykład "text"). W tym przewodniku pokazano, jak definiować i wyświetlać regiony konspektujące.

Tworzenie projektu zarządzanej struktury rozszerzalności (MEF)

Aby utworzyć projekt MEF

  1. Utwórz projekt VSIX. Nadaj rozwiązaniu OutlineRegionTestnazwę .

  2. Dodaj szablon elementu Klasyfikator edytora do projektu. Aby uzyskać więcej informacji, zobacz Tworzenie rozszerzenia za pomocą szablonu elementu edytora.

  3. Usuń istniejące pliki klas.

Implementowanie konspektowania sztyletu

Tworzenie konspektowania regionów jest oznaczone tagiem (OutliningRegionTag). Ten tag zapewnia standardowe zachowanie konspektujące. Region konspektu można rozwinąć lub zwinąć. Konturowany region jest oznaczony znakiem plus (+), jeśli jest zwinięty lub znak minus (-), jeśli jest rozwinięty, a rozwinięty region jest oddzielony przez linię pionową.

W poniższych krokach pokazano, jak zdefiniować sztylet, który tworzy podkreślenie regionów dla wszystkich regionów rozdzielonych nawiasami kwadratowymi ([,]).

Aby zaimplementować konspektowanie sztyletu

  1. Dodaj plik klasy i nadaj mu OutliningTaggernazwę .

  2. Zaimportuj następujące przestrzenie nazw.

    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. Utwórz klasę o nazwie OutliningTaggeri zaimplementuj ITagger<T>ją:

    internal sealed class OutliningTagger : ITagger<IOutliningRegionTag>
    
  4. Dodaj niektóre pola, aby śledzić bufor tekstu i migawkę oraz gromadzić zestawy wierszy, które powinny być oznaczone jako regiony konspektu. Ten kod zawiera listę obiektów regionów (do późniejszego zdefiniowania), które reprezentują regiony konspektujące.

    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. Dodaj konstruktor tagger, który inicjuje pola, analizuje bufor i dodaje procedurę obsługi zdarzeń do Changed zdarzenia.

    public OutliningTagger(ITextBuffer buffer)
    {
        this.buffer = buffer;
        this.snapshot = buffer.CurrentSnapshot;
        this.regions = new List<Region>();
        this.ReParse();
        this.buffer.Changed += BufferChanged;
    }
    
  6. Zaimplementuj metodę GetTags , która tworzy wystąpienie tagu. W tym przykładzie przyjęto założenie, że zakresy przekazywane NormalizedSpanCollection do metody są ciągłe, chociaż nie zawsze może być tak. Ta metoda tworzy wystąpienie nowego zakresu tagów dla każdego z regionów tworzenia konspektu.

    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. Zadeklaruj program obsługi zdarzeń TagsChanged .

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  8. Dodaj program obsługi zdarzeń BufferChanged , który reaguje na Changed zdarzenia, analizuje bufor tekstowy.

    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. Dodaj metodę, która analizuje bufor. Przykład podany tutaj dotyczy tylko ilustracji. Synchronicznie analizuje bufor w zagnieżdżonych regionach.

    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. Poniższa metoda pomocnika pobiera liczbę całkowitą reprezentującą poziom podkreślenia, tak aby 1 to najbardziej lewa para nawiasów klamrowych.

    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. Poniższa metoda pomocnika tłumaczy region (zdefiniowany w dalszej części tego artykułu) na element 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. Poniższy kod jest przeznaczony tylko dla ilustracji. Definiuje klasę PartialRegion zawierającą numer wiersza i przesunięcie początku regionu konspektowania oraz odwołanie do regionu nadrzędnego (jeśli istnieje). Ten kod umożliwia analizatorowi skonfigurowanie zagnieżdżonych regionów tworzenia konspektów. Klasa regionu pochodnego zawiera odwołanie do numeru wiersza końca regionu konspektowania.

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

Implementowanie dostawcy tagger

Wyeksportuj dostawcę tagger dla swojego sztyletu. Dostawca tagger tworzy OutliningTagger obiekt dla bufora typu zawartości "text" lub zwraca OutliningTagger wartość , jeśli bufor ma już jeden.

Aby zaimplementować dostawcę tagger

  1. Utwórz klasę o nazwie OutliningTaggerProvider , która implementuje ITaggerProviderelement , i wyeksportuj ją za pomocą atrybutów ContentType i TagType.

    [Export(typeof(ITaggerProvider))]
    [TagType(typeof(IOutliningRegionTag))]
    [ContentType("text")]
    internal sealed class OutliningTaggerProvider : ITaggerProvider
    
  2. Zaimplementuj metodę CreateTagger , dodając element OutliningTagger do właściwości buforu.

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

Kompilowanie i testowanie kodu

Aby przetestować ten kod, skompiluj rozwiązanie OutlineRegionTest i uruchom je w wystąpieniu eksperymentalnym.

Aby skompilować i przetestować rozwiązanie OutlineRegionTest

  1. Stwórz rozwiązanie.

  2. Po uruchomieniu tego projektu w debugerze zostanie uruchomione drugie wystąpienie programu Visual Studio.

  3. Utwórz plik tekstowy. Wpisz tekst zawierający zarówno nawiasy otwierające, jak i nawiasy zamykające.

    [
       Hello
    ]
    
  4. Powinien istnieć region konspektujący zawierający oba nawiasy. Powinno być możliwe kliknięcie znaku minus po lewej stronie otwartego nawiasu, aby zwinąć region konspektowania. Po zwinięciu regionu symbol wielokropka (...) powinien pojawić się po lewej stronie zwiniętego regionu, a po przeniesieniu wskaźnika na wielokropek powinien pojawić się wyskakujące okienko zawierające tekst aktywowania tekstu.