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
Crie um projeto do classificador de Editor. Nomeie a solução OutlineRegionTest.
Abra o arquivo de source.extension.vsixmanifest no Editor de VSIX de manifesto.
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.
Salve e feche o source.extension.vsixmanifest.
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
Adicione um arquivo de classe e denomine- OutliningTagger.
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;
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>
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;
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; }
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)); } } }
Declarar um TagsChanged manipulador de eventos.
Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) Implements ITagger(Of IOutliningRegionTag).TagsChanged
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
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(); }
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)))); } }
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; }
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); }
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
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
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
Crie a solução.
Ao executar este projeto no depurador, uma segunda instância do Visual Studio é instanciada.
Criar um arquivo de texto. Digite algum texto que inclui a chave de abertura e a chave de fechamento.
[ Hello ]
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