逐步解說: 大綱
您可以實作語言為基礎的功能,例如大綱藉由定義您想要展開或摺疊的文字區域的類型。 您可以定義的區域語言服務層,您可以定義您自己的檔案名稱副檔名和內容類型,並將區域定義套用到該類型,或是您可以將區域定義套用到現有的內容類型 (例如,"text")。 這個逐步解說會示範如何定義和顯示大綱區域。
必要條件
若要完成這個逐步解說中,您必須安裝Visual Studio 2010 SDK。
![]() |
---|
如需有關 Visual Studio 的 SDK 的詳細資訊,請參閱擴充 Visual Studio 的概觀。若要了解如何下載 Visual Studio 的 SDK,請參閱Visual Studio 擴充性開發人員中心 MSDN 網站上。 |
建立受管理的擴充性架構 (MEF) 專案
若要建立 MEF 專案
建立編輯器類別器的專案。 為方案命名 OutlineRegionTest。
VSIX 資訊清單編輯器中開啟 source.extension.vsixmanifest 檔案。
請確定Content名包含 MEF 元件的內容類型,並Path設定為 OutlineRegionTest.dll。
儲存並關閉 source.extension.vsixmanifest。
刪除現有的類別檔案。
實作大綱的 Tagger
由一種標記所標記的大綱區域 (OutliningRegionTag)。 此標記提供標準的分層顯示行為。 可展開或摺疊的大綱的區域。 如果已摺疊的加號或減號標記外框的區域,如果已展開,並展開的區域以垂直線 demarcated。
下列步驟將示範如何定義建立大綱區域,以分隔的所有地區 tagger"["和"]"。
若要實作大綱的 tagger
將類別檔案,並命名為 OutliningTagger。
匯入下列命名空間。
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;
建立一個名為OutliningTagger,並讓該實作ITagger:
Friend NotInheritable Class OutliningTagger Implements ITagger(Of IOutliningRegionTag)
internal sealed class OutliningTagger : ITagger<IOutliningRegionTag>
新增一些欄位,追蹤文字緩衝區和快照集,並會累積一系列應該標記為大綱區域的線條。 這段程式碼會包含代表大綱區域的區域物件 (若要稍後定義) 的清單。
'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;
加入 tagger 建構函式初始化欄位,會剖析緩衝區,並加入事件處理常式來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; }
實作GetTags方法,這麼做會具現化該標記延伸。 本範例假設在 span NormalizedSpanCollection方法中傳入是連續的,雖然這不一定大小寫。 這個方法會產生新的標記範圍每個大綱區域。
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)); } } }
宣告TagsChanged事件處理常式。
Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) Implements ITagger(Of IOutliningRegionTag).TagsChanged
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
新增BufferChanged回應的事件處理常式Changed剖析文字緩衝區的事件。
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(); }
將會剖析緩衝區的方法。 此處提供的範例是僅供說明。 它會以同步方式會緩衝區剖析成巢狀的大綱區域。
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)))); } }
下列的 helper 方法會取得整數,表示的大綱層級 1 是最左邊的大括號組。
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; }
下列的 helper 方法會將一個 (稍後在本主題中所定義) 的區域轉譯為 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); }
下列程式碼是僅供說明。 它定義的 PartialRegion 類別所在的行號和大綱的地區,以及參考至父區域 (如果有的話) 的開頭的位移。 這可讓剖析器設定巢狀大綱區域。 在衍生的區域類別包含的大綱區域結尾的行號的參考。
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; } }
實作 Tagger 提供者
您必須匯出 tagger 提供者為您的 tagger。 Tagger 提供者會建立OutliningTagger的 「 文字 」 的內容類型或其他的傳回緩衝區的OutliningTagger如果緩衝區已有此項。
若要實作 tagger 提供者
建立一個名為OutliningTaggerProvider實作ITaggerProvider,並將其匯出不含 ContentType 和 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
實作CreateTagger``1方法,藉由新增OutliningTagger的緩衝區內容。
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); }
建置和測試程式碼
若要測試這段程式碼,建置 OutlineRegionTest 方案並執行它實驗性的執行個體中。
若要建置和測試 OutlineRegionTest 的解決方案
建置方案。
當您執行此專案在偵錯工具時,會執行個體化 Visual Studio 的第二個執行個體。
建立文字檔 輸入一些文字,其中包含左括號和右括號。
[ Hello ]
應該包含這兩個括號的大綱區域。 您應該能夠按一下減號左邊的大括號來摺疊大綱區域。 當區域摺疊,省略符號 (...) 摺疊的區域,以及包含文字的快顯畫面的左側應該會出現暫留文字應該會出現,當您將指標移入省略符號。