Partager via


Procédure pas - à - pas : Mode plan

Vous pouvez implémenter les fonctionnalités basées sur un langage telles que le mode Plan en définissant les types de zones de texte que vous voulez développer ou réduire. Vous pouvez définir des régions dans le contexte d'un service de langage, ou vous pouvez définir votre propre extension de nom de fichier et type de contenu et appliquer la définition de zone uniquement à ce type, ou vous pouvez appliquer les définitions de zones à un type de contenu existant (tel que « texte »). Cette procédure pas - à - pas montre comment définir et afficher les régions en mode Plan.

Composants requis

Pour exécuter cette procédure, vous devez installer Kit de développement logiciel Visual Studio 2010.

Notes

Pour plus d'informations sur le kit de développement Visual Studio, consultez Étendre la présentation de Visual Studio.Pour savoir comment télécharger le kit de développement Visual Studio, consultez Visual Studio Extensibility Developer Center sur le site Web MSDN.

Créer un projet managé (MEF) managed extensibility framework

Pour créer un projet MEF

  1. Créez un projet de classifieur d'éditeur. nommez la solution OutlineRegionTest.

  2. Ouvrez le fichier source.extension.vsixmanifest dans l'éditeur de manifeste VSIX.

  3. Assurez -vous que le titre d' Content contient un type de contenu composant MEF et qu' Path est défini à OutlineRegionTest.dll.

  4. Enregistrez et fermez le fichier source.extension.vsixmanifest.

  5. supprimez les fichiers de classe existants.

Implémenter un balises en mode Plan

Les régions en mode Plan est marquée par un type de balise (OutliningRegionTag). Cette balise fournit le comportement standard en mode Plan. Le mode plan d'une zone peut être développée ou réduite. la zone avec contour est marquée par un SIGNE PLUS si elle est réduite ou un SIGNE MOINS si elle est développée, et la zone développée est délimitée par une ligne verticale.

Les étapes suivantes indiquent comment définir un balises qui crée des régions en mode Plan pour toutes les régions par lesquelles sont délimités « [ » et « ] ».

Pour implémenter un balises en mode Plan

  1. ajoutez un fichier de classe et nommez-le OutliningTagger.

  2. importez les espaces de noms suivants.

    Imports System
    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.Text
    Imports System.ComponentModel.Composition
    Imports Microsoft.VisualStudio.Text.Outlining
    Imports Microsoft.VisualStudio.Text.Tagging
    Imports Microsoft.VisualStudio.Utilities
    Imports Microsoft.VisualStudio.Text
    
    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. créez une classe nommée OutliningTagger, et faites-implémenter la ITagger:

    Friend NotInheritable Class OutliningTagger
        Implements ITagger(Of IOutliningRegionTag)
    
    internal sealed class OutliningTagger : ITagger<IOutliningRegionTag>
    
  4. Ajoutez des champs pour suivre la mémoire tampon de texte et l'instantané et pour accumuler les jeux de lignes qui doivent être référencées en tant que régions en mode Plan. Ce code inclut une liste d'objets de région (être défini ci-après) qui représentent les régions en mode Plan.

    'the characters that start the outlining region 
    Private startHide As String = "[" 
    'the characters that end the outlining region 
    Private endHide As String = "]" 
    'the characters that are displayed when the region is collapsed 
    Private ellipsis As String = "..." 
    'the contents of the tooltip for the collapsed span 
    Private hoverText As String = "hover text" 
    Private buffer As ITextBuffer
    Private snapshot As ITextSnapshot
    Private regions As List(Of Region)
    
    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. Ajoutez un constructeur de balises qui initialise les champs, analyse la mémoire tampon, puis ajoute un gestionnaire d'événements à l'événement d' Changed .

    Public Sub New(ByVal buffer As ITextBuffer)
        Me.buffer = buffer
        Me.snapshot = buffer.CurrentSnapshot
        Me.regions = New List(Of Region)()
        Me.ReParse()
        AddHandler Me.buffer.Changed, AddressOf BufferChanged
    End Sub
    
    public OutliningTagger(ITextBuffer buffer)
    {
        this.buffer = buffer;
        this.snapshot = buffer.CurrentSnapshot;
        this.regions = new List<Region>();
        this.ReParse();
        this.buffer.Changed += BufferChanged;
    }
    
  6. Implémentez la méthode d' GetTags , qui instancie les étendues de balises. Cet exemple suppose que les étendues dans NormalizedSpanCollection passé à la méthode sont adjacentes, bien que cela puisse toujours ne pas être le cas. Cette méthode instancie une nouvelle étendue de balise pour les régions en mode Plan.

    Public Function GetTags(ByVal spans As NormalizedSnapshotSpanCollection) As IEnumerable(Of ITagSpan(Of IOutliningRegionTag)) Implements ITagger(Of Microsoft.VisualStudio.Text.Tagging.IOutliningRegionTag).GetTags
        If spans.Count = 0 Then 
            Return Nothing 
            Exit Function 
        End If 
        Dim currentRegions As List(Of Region) = Me.regions
        Dim currentSnapshot As ITextSnapshot = Me.snapshot
        Dim entire As SnapshotSpan = New SnapshotSpan(spans(0).Start, spans(spans.Count - 1).[End]).TranslateTo(currentSnapshot, SpanTrackingMode.EdgeExclusive)
        Dim startLineNumber As Integer = entire.Start.GetContainingLine().LineNumber
        Dim endLineNumber As Integer = entire.[End].GetContainingLine().LineNumber
    
        Dim list As List(Of ITagSpan(Of IOutliningRegionTag))
        list = New List(Of ITagSpan(Of IOutliningRegionTag))()
    
        For Each region In currentRegions
            If region.StartLine <= endLineNumber AndAlso region.EndLine >= startLineNumber Then 
                Dim startLine = currentSnapshot.GetLineFromLineNumber(region.StartLine)
                Dim endLine = currentSnapshot.GetLineFromLineNumber(region.EndLine)
    
                'the region starts at the beginning of the "[", and goes until the *end* of the line that contains the "]".
                list.Add(New TagSpan(Of IOutliningRegionTag)(New SnapshotSpan(startLine.Start + region.StartOffset, endLine.End),
                New OutliningRegionTag(False, False, ellipsis, hoverText)))
            End If 
        Next 
        Return list
    End Function
    
    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. déclarez un gestionnaire d'événements d' TagsChanged .

    Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) Implements ITagger(Of IOutliningRegionTag).TagsChanged
    
    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  8. ajoutez un gestionnaire d'événements d' BufferChanged qui répond aux événements d' Changed en analysant la mémoire tampon de texte.

    Private Sub BufferChanged(ByVal sender As Object, ByVal e As TextContentChangedEventArgs)
        ' 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 IsNot buffer.CurrentSnapshot Then 
            Exit Sub 
        End If 
        Me.ReParse()
    End Sub
    
    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. ajoutez une méthode qui analyse la mémoire tampon. L'exemple indiqué ici est fourni à titre de illustration uniquement. Il analyse de façon synchrone la mémoire tampon dans les régions en mode Plan imbriquées.

    Private Sub ReParse()
        Dim newSnapshot As ITextSnapshot = buffer.CurrentSnapshot
        Dim newRegions As New List(Of Region)()
    
        'keep the current (deepest) partial region, which will have 
        ' references to any parent partial regions. 
        Dim currentRegion As PartialRegion = Nothing 
    
        For Each line In newSnapshot.Lines
            Dim regionStart As Integer = -1
            Dim text As String = line.GetText()
    
            'lines that contain a "[" denote the start of a new region. 
            If text.IndexOf(startHide, StringComparison.Ordinal) <> -1 Then
                regionStart = text.IndexOf(startHide, StringComparison.Ordinal)
                Dim currentLevel As Integer = If((currentRegion IsNot Nothing), currentRegion.Level, 1)
                Dim newLevel As Integer 
                If Not TryGetLevel(text, regionStart, newLevel) Then
                    newLevel = currentLevel + 1
                End If 
    
                'levels are the same and we have an existing region; 
                'end the current region and start the next 
                If currentLevel = newLevel AndAlso currentRegion IsNot Nothing Then 
                    Dim newRegion = New Region()
                    newRegion.Level = currentRegion.Level
                    newRegion.StartLine = currentRegion.StartLine
                    newRegion.StartOffset = currentRegion.StartOffset
                    newRegion.EndLine = line.LineNumber
                    newRegions.Add(newRegion)
    
                    currentRegion = New PartialRegion()
                    currentRegion.Level = newLevel
                    currentRegion.StartLine = line.LineNumber
                    currentRegion.StartOffset = regionStart
                    currentRegion.PartialParent = currentRegion.PartialParent
    
                Else 
                    'this is a new (sub)region
                    currentRegion = New PartialRegion()
                    currentRegion.Level = newLevel
                    currentRegion.StartLine = line.LineNumber
                    currentRegion.StartOffset = regionStart
                    currentRegion.PartialParent = currentRegion
                End If 
                'lines that contain "]" denote the end of a region 
            ElseIf (text.IndexOf(endHide, StringComparison.Ordinal)) <> -1 Then
                regionStart = text.IndexOf(endHide, StringComparison.Ordinal)
                Dim currentLevel As Integer = If((currentRegion IsNot Nothing), currentRegion.Level, 1)
                Dim closingLevel As Integer 
                If Not TryGetLevel(text, regionStart, closingLevel) Then
                    closingLevel = currentLevel
                End If 
    
                'the regions match 
                If currentRegion IsNot Nothing AndAlso currentLevel = closingLevel Then 
                    Dim newRegion As Region
                    newRegion = New Region()
                    newRegion.Level = currentLevel
                    newRegion.StartLine = currentRegion.StartLine
                    newRegion.StartOffset = currentRegion.StartOffset
                    newRegion.EndLine = line.LineNumber
                    newRegions.Add(newRegion)
    
                    currentRegion = currentRegion.PartialParent
                End If 
            End If 
        Next 
        'determine the changed span, and send a changed event with the new spans 
        Dim oldSpans As New List(Of Span)(Me.regions.[Select](Function(r) AsSnapshotSpan(r, Me.snapshot).TranslateTo(newSnapshot, SpanTrackingMode.EdgeExclusive).Span))
        Dim newSpans As New List(Of Span)(newRegions.[Select](Function(r) AsSnapshotSpan(r, newSnapshot).Span))
    
        Dim oldSpanCollection As New NormalizedSpanCollection(oldSpans)
        Dim newSpanCollection As New NormalizedSpanCollection(newSpans)
    
        'the changed regions are regions that appear in one set or the other, but not both. 
        Dim removed As NormalizedSpanCollection = NormalizedSpanCollection.Difference(oldSpanCollection, newSpanCollection)
    
        Dim changeStart As Integer = Integer.MaxValue
        Dim changeEnd As Integer = -1
    
        If removed.Count > 0 Then
            changeStart = removed(0).Start
            changeEnd = removed(removed.Count - 1).[End]
        End If 
    
        If newSpans.Count > 0 Then
            changeStart = Math.Min(changeStart, newSpans(0).Start)
            changeEnd = Math.Max(changeEnd, newSpans(newSpans.Count - 1).[End])
        End If 
    
        Me.snapshot = newSnapshot
        Me.regions = newRegions
    
        If changeStart <= changeEnd Then 
            Dim snap As ITextSnapshot = Me.snapshot
            RaiseEvent TagsChanged(Me, New SnapshotSpanEventArgs(New SnapshotSpan(Me.snapshot, Span.FromBounds(changeStart, changeEnd))))
        End If 
    End Sub
    
    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. La méthode d'assistance suivante récupère un entier qui représente le niveau du mode Plan, tel que 1 est la paire à l'extrême gauche d'accolades.

    Private Shared Function TryGetLevel(ByVal text As String, ByVal startIndex As Integer, ByRef level As Integer) As Boolean
        level = -1
        If text.Length > startIndex + 3 Then 
            If Integer.TryParse(text.Substring(startIndex + 1), level) Then 
                Return True 
            End If 
        End If 
    
        Return False 
    End Function
    
    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. La méthode d'assistance suivante convertit une région (définie ultérieurement dans cette rubrique) en SnapshotSpan.

    Private Shared Function AsSnapshotSpan(ByVal region As Region, ByVal snapshot As ITextSnapshot) As SnapshotSpan
        Dim startLine = snapshot.GetLineFromLineNumber(region.StartLine)
        Dim endLine = If((region.StartLine = region.EndLine), startLine, snapshot.GetLineFromLineNumber(region.EndLine))
        Return New SnapshotSpan(startLine.Start + region.StartOffset, endLine.[End])
    End Function
    
    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. Le code suivant est fourni à titre de illustration uniquement. Il définit une classe de PartialRegion qui contient le numéro de ligne et l'offset du début d'une région en mode Plan, et une référence vers la zone parent (le cas échéant). Cela permet à l'analyseur pour installer les régions en mode Plan imbriquées. Une classe dérivée de zone contient une référence au numéro de ligne de la fin d'une région en mode Plan.

    Private Class PartialRegion
        Private _StartLine As Integer 
        Public Property StartLine() As Integer 
            Get 
                Return _StartLine
            End Get 
            Set(ByVal value As Integer)
                _StartLine = value
            End Set 
        End Property 
        Private _StartOffset As Integer 
        Public Property StartOffset() As Integer 
            Get 
                Return _StartOffset
            End Get 
            Set(ByVal value As Integer)
                _StartOffset = value
            End Set 
        End Property 
        Private _Level As Integer 
        Public Property Level() As Integer 
            Get 
                Return _Level
            End Get 
            Set(ByVal value As Integer)
                _Level = value
            End Set 
        End Property 
        Private _PartialParent As PartialRegion
        Public Property PartialParent() As PartialRegion
            Get 
                Return _PartialParent
            End Get 
            Set(ByVal value As PartialRegion)
                _PartialParent = value
            End Set 
        End Property 
    End Class 
    
    Private Class Region
        Inherits PartialRegion
        Private _EndLine As Integer 
        Public Property EndLine() As Integer 
            Get 
                Return _EndLine
            End Get 
            Set(ByVal value As Integer)
                _EndLine = value
            End Set 
        End Property 
    End Class
    
    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; }
    } 
    

Implémenter un fournisseur de balises

Vous devez exporter un fournisseur de balises pour votre balises. Le fournisseur de balises crée OutliningTagger pour une mémoire tampon du type de contenu de « texte », ou bien retourne OutliningTagger si la mémoire tampon a déjà un.

Pour implémenter un fournisseur de balises

  1. créez une classe nommée OutliningTaggerProvider qui implémente ITaggerProvider, et exportez-la avec les attributs de ContentType et de TagType.

    <Export(GetType(ITaggerProvider))> _
    <TagType(GetType(IOutliningRegionTag))> _
    <ContentType("text")> _
    Friend NotInheritable Class OutliningTaggerProvider
        Implements ITaggerProvider
    
    [Export(typeof(ITaggerProvider))]
    [TagType(typeof(IOutliningRegionTag))]
    [ContentType("text")]
    internal sealed class OutliningTaggerProvider : ITaggerProvider
    
  2. Implémentez la méthode d' CreateTagger``1 en ajoutant OutliningTagger aux propriétés de la mémoire tampon.

    Public Function CreateTagger(Of T As ITag)(ByVal buffer As ITextBuffer) As ITagger(Of T) Implements ITaggerProvider.CreateTagger
        'create a single tagger for each buffer. 
        Dim sc As Func(Of ITagger(Of T)) = Function() TryCast(New OutliningTagger(buffer), ITagger(Of T))
        Return buffer.Properties.GetOrCreateSingletonProperty(Of ITagger(Of T))(sc)
    End Function
    
    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);
    } 
    

Génération et test de code

Pour tester ce code, générez la solution d'OutlineRegionTest et exécutez -la dans l'instance expérimentale.

Pour générer et tester la solution d'OutlineRegionTest

  1. Générez la solution.

  2. Lorsque vous exécutez ce projet dans le débogueur, une deuxième instance de Visual Studio est instanciée.

  3. Créer un fichier texte. Tapez du texte qui inclut l'accolade ouvrante et l'accolade fermante.

    [
       Hello
    ]
    
  4. Il doit y a une région en mode Plan qui inclut les deux accolades. Vous devez être en mesure de cliquer sur le SIGNE MOINS (-) situé à gauche de l'accolade ouvrante de réduire la région en mode Plan. Lorsque la région est réduite, le symbole de sélection (...) doit apparaître à gauche de la zone réduite, et un message contenant le texte texte de pointage doit s'afficher lorsque vous déplacez le pointeur sur les points de suspension.

Voir aussi

Tâches

Procédure pas - à - pas : lier un type de contenu à une extension de nom de fichier