Compartilhar via


Explicação passo a passo: estrutura de tópicos

Você pode implementar os recursos baseados em linguagem como, por exemplo, a estrutura de tópicos, definindo os tipos de regiões de texto que você deseja expandir ou recolher. Você pode definir regiões no contexto de um serviço de idioma, você pode definir seu próprio tipo de conteúdo e a extensão de nome do arquivo e aplicar a definição de região somente àquele tipo ou você pode aplicar as definições de região a um tipo de conteúdo existente (como "texto"). Esta explicação passo a passo mostra como definir e exibir regiões de estrutura de tópicos.

Pré-requisitos

Para concluir este passo a passo, você deve instalar o SDL do Visual Studio 2010.

Dica

Para obter mais informações sobre o SDK de Visual Studio, consulte Ampliando a visão geral de Visual Studio.Para descobrir como fazer o download do SDK do Visual Studio, consulte Visual Studio extensibilidade Developer Center no site do MSDN.

Criando um projeto do Framework (MEF) de extensibilidade gerenciada

Para criar um projeto MEF

  1. Crie um projeto do classificador de Editor. Nomeie a solução OutlineRegionTest.

  2. Abra o arquivo de source.extension.vsixmanifest no Editor de VSIX de manifesto.

  3. Certifique-se de que o Content título contém um tipo de conteúdo do componente MEF e que o Path for definido como OutlineRegionTest.dll.

  4. Salve e feche o source.extension.vsixmanifest.

  5. Exclua os arquivos de classe existentes.

Implementando uma estrutura de tópicos Tagger

Regiões de estrutura de tópicos são marcados por um tipo de marca (OutliningRegionTag). Esta marca fornece o padrão de comportamento de estrutura de tópicos. A região de estrutura de tópicos pode ser expandida ou recolhida. A região de contornada é marcada por um sinal de adição, se estiver recolhida ou um sinal de subtração se ele é expandido e a região expandida é delimitada por uma linha vertical.

As etapas a seguir mostram como definir um tagger que cria regiões de estrutura de tópicos para todas as regiões que são delimitadas por "[" e "]".

Para implementar uma estrutura de tópicos tagger

  1. Adicione um arquivo de classe e denomine- OutliningTagger.

  2. Importe os namespaces a seguir.

    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. Criar uma classe chamada OutliningTagger, e tê-lo a implementar ITagger:

    Friend NotInheritable Class OutliningTagger
        Implements ITagger(Of IOutliningRegionTag)
    
    internal sealed class OutliningTagger : ITagger<IOutliningRegionTag>
    
  4. Adicione alguns campos para controlar o buffer de texto e o instantâneo e acumular os conjuntos de linhas que devem ser etiquetados como regiões de estrutura de tópicos. Esse código inclui uma lista de objetos de região (a ser definido posteriormente) que representam as regiões de estrutura de tópicos.

    '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. Adicionar um construtor de tagger que inicializa os campos, analisa o buffer e adiciona um manipulador de eventos para o Changed evento.

    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. Implementar a GetTags se estende por método, que instancia a marca. Este exemplo assume que as extensões na NormalizedSpanCollection passado para o método são contíguos, embora isso nem sempre pode ser o caso. Esse método percorre uma nova extensão de marca para cada uma das regiões de estrutura de tópicos.

    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. Declarar um TagsChanged manipulador de eventos.

    Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) Implements ITagger(Of IOutliningRegionTag).TagsChanged
    
    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  8. Adicionar um BufferChanged o manipulador de eventos que responde a Changed eventos ao analisar o buffer de texto.

    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. Adicione um método que analisa o buffer. O exemplo fornecido aqui é apenas ilustrativo. Sincronicamente, ele analisa o buffer em regiões de estrutura de tópicos aninhadas.

    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. O seguinte método auxiliar obtém um número inteiro que representa o nível de estrutura de tópicos, de modo que 1 é o par de chaves mais à esquerda.

    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. O seguinte método auxiliar converte uma região (definida mais adiante neste tópico) em um 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. O código a seguir é apenas ilustrativo. Ele define uma classe de PartialRegion que contém o número da linha e o deslocamento do início de uma região de estrutura de tópicos e também uma referência para a região do pai (se houver). Isso permite que o analisador configurar aninhados regiões de estrutura de tópicos. Uma classe derivada de região contém uma referência ao número de linhas do final de uma região de estrutura de tópicos.

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

Implementando um provedor de Tagger

Você deve exportar um provedor de tagger para seu tagger. O provedor tagger cria um OutliningTagger um buffer do tipo de conteúdo "text" ou outra retorna um OutliningTagger se o buffer já tenha uma.

Para implementar um provedor de tagger

  1. Criar uma classe chamada OutliningTaggerProvider que implementa ITaggerProvidere exportá-los com os atributos ContentType e 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. Implementar a CreateTagger``1 método adicionando um OutliningTagger para as propriedades do buffer.

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

Criar e testar o código

Para testar esse código, crie a solução de OutlineRegionTest e execute-o na instância experimental.

Para criar e testar a solução de OutlineRegionTest

  1. Crie a solução.

  2. Ao executar este projeto no depurador, uma segunda instância do Visual Studio é instanciada.

  3. Criar um arquivo de texto. Digite algum texto que inclui a chave de abertura e a chave de fechamento.

    [
       Hello
    ]
    
  4. Deve haver uma região de estrutura de tópicos que inclui ambas as chaves. Você poderá clicar no sinal de subtração à esquerda da chave de abertura para recolher a região de estrutura de tópicos. Quando a região estiver recolhido, o símbolo de reticências (...) deve aparecer à esquerda da região recolhida e um pop-up que contém o texto texto em foco deve ser exibida quando você move o ponteiro sobre as reticências.

Consulte também

Tarefas

Passo a passo: Vinculação a um tipo de conteúdo a uma extensão de nome de arquivo