Configurez des fonctionnalités linguistiques telles que le plan en définissant les types de régions de texte que vous souhaitez développer ou réduire. Vous pouvez définir des régions dans le contexte d’un service de langage, ou définir votre propre extension de nom de fichier et type de contenu et appliquer la définition de région à ce type uniquement, ou appliquer les définitions de région à un type de contenu existant (par exemple, « text »). Cette procédure pas à pas montre comment définir et afficher des zones de plan.
Créer un projet MEF (Managed Extensibility Framework)
Pour créer un projet MEF
Créez un projet VSIX. Nommez la solution OutlineRegionTest.
Ajoutez un modèle d’élément Classifieur d’éditeur au projet. Pour plus d’informations, consultez Créer une extension avec un modèle d’élément d’éditeur.
Supprimez les fichiers de classe existants.
Implémenter un balisage de plan
Les zones de plan sont marquées par un type de balise (OutliningRegionTag). Cette balise fournit le comportement standard du plan. La région décrite peut être développée ou réduite. La région décrite est marquée par un signe Plus (+) s’il est réduit ou moins (-) s’il est développé et la région développée est délimitée par une ligne verticale.
Les étapes suivantes montrent comment définir un balisage qui crée des zones de plan pour toutes les régions délimitées par les crochets ([,]).
Pour implémenter un balisage de plan
Ajoutez un fichier de classe et nommez-le OutliningTagger.
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;
internal sealed class OutliningTagger : ITagger<IOutliningRegionTag>
Friend NotInheritable Class OutliningTagger
Implements ITagger(Of IOutliningRegionTag)
Ajoutez certains champs pour suivre la mémoire tampon de texte et instantané et accumuler les ensembles de lignes qui doivent être étiquetés comme des zones de plan. Ce code inclut une liste d’objets Region (à définir ultérieurement) qui représentent les régions de plan.
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;
'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)
Ajoutez un constructeur de balisage qui initialise les champs, analyse la mémoire tampon et ajoute un gestionnaire d’événements à l’événement Changed .
public OutliningTagger(ITextBuffer buffer)
{
this.buffer = buffer;
this.snapshot = buffer.CurrentSnapshot;
this.regions = new List<Region>();
this.ReParse();
this.buffer.Changed += BufferChanged;
}
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
Implémentez la GetTags méthode, qui instancie les étendues de balise. Cet exemple part du principe que les étendues de la NormalizedSpanCollection méthode passées sont contiguës, bien qu’il ne soit pas toujours le cas. Cette méthode instancie une nouvelle étendue de balise pour chacune des régions de plan.
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));
}
}
}
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
Déclarez un gestionnaire d’événements TagsChanged .
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();
}
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
Ajoutez une méthode qui analyse la mémoire tampon. L’exemple donné ici est destiné uniquement à l’illustration. Il analyse de manière synchrone la mémoire tampon dans des régions de plan imbriquées.
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))));
}
}
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
La méthode d’assistance suivante obtient un entier qui représente le niveau du plan, de sorte que 1 est la paire d’accolades la plus à gauche.
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;
}
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
La méthode d’assistance suivante traduit une région (définie plus loin dans cet article) en un composant 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);
}
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
Le code suivant est destiné uniquement à l’illustration. Il définit une classe PartialRegion qui contient le numéro de ligne et le décalage du début d’une région de plan et une référence à la région parente (le cas échéant). Ce code permet à l’analyseur de configurer des régions de plan imbriquées. Une classe Region dérivée contient une référence au numéro de ligne de la fin d’une région de plan.
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; }
}
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
Implémenter un fournisseur de balisage
Exportez un fournisseur de balisage pour votre taggeur. Le fournisseur de balisage crée une OutliningTagger mémoire tampon du type de contenu « text » ou retourne une OutliningTagger valeur si la mémoire tampon en a déjà une.
Pour implémenter un fournisseur de balisage
Créez une classe nommée OutliningTaggerProvider qui implémente ITaggerProvider, puis exportez-la avec les attributs ContentType et TagType.
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);
}
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
Générer et tester le code
Pour tester ce code, générez la solution OutlineRegionTest et exécutez-la dans l’instance expérimentale.
Pour générer et tester la solution OutlineRegionTest
Générez la solution.
Lorsque vous exécutez ce projet dans le débogueur, une deuxième instance de Visual Studio est démarrée.
Créer un fichier texte. Tapez du texte incluant les crochets ouvrants et les crochets fermants.
[
Hello
]
Il doit y avoir une zone de plan qui inclut les deux crochets. Vous devez être en mesure de cliquer sur le signe Moins à gauche du crochet ouvert pour réduire la zone de plan. Lorsque la région est réduite, le symbole de points de suspension (...) doit apparaître à gauche de la région réduite, et une fenêtre contextuelle contenant le texte de pointage du texte doit apparaître lorsque vous déplacez le pointeur sur les points de suspension.