Siapkan fitur berbasis bahasa seperti menguraikan dengan menentukan jenis wilayah teks yang ingin Anda perluas atau ciutkan. Anda dapat menentukan wilayah dalam konteks layanan bahasa, atau menentukan ekstensi nama file dan jenis konten Anda sendiri dan menerapkan definisi wilayah hanya ke jenis tersebut, atau menerapkan definisi wilayah ke jenis konten yang ada (seperti "teks"). Panduan ini menunjukkan cara menentukan dan menampilkan wilayah kerangka.
Membuat proyek Managed Extensibility Framework (MEF)
Untuk membuat proyek MEF
Buat proyek VSIX. Beri nama solusi OutlineRegionTest.
Tambahkan templat item Pengklasifikasi Editor ke proyek. Untuk informasi selengkapnya, lihat Membuat ekstensi dengan templat item editor.
Hapus file kelas yang ada.
Menerapkan tagger kerangka
Wilayah kerangka ditandai oleh semacam tag (OutliningRegionTag). Tag ini menyediakan perilaku kerangka standar. Wilayah yang diuraikan dapat diperluas atau diciutkan. Wilayah yang diuraikan ditandai dengan tanda Plus (+) jika diciutkan atau tanda Minus (-) jika diperluas, dan wilayah yang diperluas didemarasi oleh garis vertikal.
Langkah-langkah berikut menunjukkan cara menentukan tagger yang membuat wilayah kerangka untuk semua wilayah yang dibatasi oleh tanda kurung siku ([,]).
Untuk mengimplementasikan tagger kerangka
Tambahkan file kelas dan beri nama 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)
Tambahkan beberapa bidang untuk melacak buffer teks dan rekam jepret dan untuk mengakumulasi kumpulan baris yang harus ditandai sebagai wilayah kerangka. Kode ini mencakup daftar objek Wilayah (akan ditentukan nanti) yang mewakili wilayah kerangka.
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)
Tambahkan konstruktor tagger yang menginisialisasi bidang, mengurai buffer, dan menambahkan penanganan aktivitas ke Changed peristiwa.
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
Terapkan GetTags metode , yang membuat instans rentang tag. Contoh ini mengasumsikan bahwa rentang dalam yang diteruskan NormalizedSpanCollection ke metode berdekatan, meskipun mungkin tidak selalu terjadi. Metode ini membuat instans rentang tag baru untuk setiap wilayah kerangka.
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
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
Tambahkan metode yang mengurai buffer. Contoh yang diberikan di sini hanya untuk ilustrasi. Ini secara sinkron menguraikan buffer ke wilayah kerangka berlapis.
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
Metode pembantu berikut mendapatkan bilangan bulat yang mewakili tingkat kerangka, sehingga 1 adalah pasangan kurung kurawal paling kiri.
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
Metode pembantu berikut menerjemahkan Wilayah (didefinisikan nanti dalam artikel ini) ke dalam 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
Kode berikut hanya untuk ilustrasi. Ini mendefinisikan kelas PartialRegion yang berisi nomor baris dan offset awal wilayah kerangka, dan referensi ke wilayah induk (jika ada). Kode ini memungkinkan pengurai untuk menyiapkan wilayah kerangka berlapis. Kelas Wilayah turunan berisi referensi ke nomor baris akhir wilayah kerangka.
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
Menerapkan penyedia tagger
Ekspor penyedia tagger untuk tagger Anda. Penyedia tagger membuat OutliningTagger untuk buffer tipe konten "teks", atau mengembalikan OutliningTagger jika buffer sudah memilikinya.
Untuk mengimplementasikan penyedia tagger
Buat kelas bernama OutliningTaggerProvider yang mengimplementasikan ITaggerProvider, dan ekspor dengan atribut ContentType dan 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
Membangun dan menguji kode
Untuk menguji kode ini, buat solusi OutlineRegionTest dan jalankan dalam instans eksperimental.
Untuk membangun dan menguji solusi OutlineRegionTest
Bangun solusinya.
Saat Anda menjalankan proyek ini di debugger, instans kedua Visual Studio dimulai.
Buat file teks. Ketik beberapa teks yang menyertakan tanda kurung siku pembuka dan tanda kurung siku penutup.
[
Hello
]
Harus ada wilayah kerangka yang mencakup kedua tanda kurung siku. Anda harus dapat mengklik Tanda Minus di sebelah kiri tanda kurung buka untuk menciutkan wilayah kerangka. Ketika wilayah diciutkan, simbol elipsis (...) akan muncul di sebelah kiri wilayah yang diciutkan, dan popup yang berisi teks hover teks akan muncul saat Anda memindahkan penunjuk ke elipsis.