共用方式為


逐步解說: 大綱

您可以實作語言為基礎的功能,例如大綱藉由定義您想要展開或摺疊的文字區域的類型。 您可以定義的區域語言服務層,您可以定義您自己的檔案名稱副檔名和內容類型,並將區域定義套用到該類型,或是您可以將區域定義套用到現有的內容類型 (例如,"text")。 這個逐步解說會示範如何定義和顯示大綱區域。

必要條件

若要完成這個逐步解說中,您必須安裝Visual Studio 2010 SDK。

注意事項注意事項

如需有關 Visual Studio 的 SDK 的詳細資訊,請參閱擴充 Visual Studio 的概觀。若要了解如何下載 Visual Studio 的 SDK,請參閱Visual Studio 擴充性開發人員中心 MSDN 網站上。

建立受管理的擴充性架構 (MEF) 專案

若要建立 MEF 專案

  1. 建立編輯器類別器的專案。 為方案命名 OutlineRegionTest。

  2. VSIX 資訊清單編輯器中開啟 source.extension.vsixmanifest 檔案。

  3. 請確定Content名包含 MEF 元件的內容類型,並Path設定為 OutlineRegionTest.dll。

  4. 儲存並關閉 source.extension.vsixmanifest。

  5. 刪除現有的類別檔案。

實作大綱的 Tagger

由一種標記所標記的大綱區域 (OutliningRegionTag)。 此標記提供標準的分層顯示行為。 可展開或摺疊的大綱的區域。 如果已摺疊的加號或減號標記外框的區域,如果已展開,並展開的區域以垂直線 demarcated。

下列步驟將示範如何定義建立大綱區域,以分隔的所有地區 tagger"["和"]"。

若要實作大綱的 tagger

  1. 將類別檔案,並命名為 OutliningTagger。

  2. 匯入下列命名空間。

    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. 建立一個名為OutliningTagger,並讓該實作ITagger

    Friend NotInheritable Class OutliningTagger
        Implements ITagger(Of IOutliningRegionTag)
    
    internal sealed class OutliningTagger : ITagger<IOutliningRegionTag>
    
  4. 新增一些欄位,追蹤文字緩衝區和快照集,並會累積一系列應該標記為大綱區域的線條。 這段程式碼會包含代表大綱區域的區域物件 (若要稍後定義) 的清單。

    '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. 加入 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;
    }
    
  6. 實作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));
            }
        }
    }
    
  7. 宣告TagsChanged事件處理常式。

    Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) Implements ITagger(Of IOutliningRegionTag).TagsChanged
    
    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  8. 新增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();
    }
    
  9. 將會剖析緩衝區的方法。 此處提供的範例是僅供說明。 它會以同步方式會緩衝區剖析成巢狀的大綱區域。

    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. 下列的 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;
    }
    
  11. 下列的 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);
    }
    
  12. 下列程式碼是僅供說明。 它定義的 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 提供者

  1. 建立一個名為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
    
  2. 實作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 的解決方案

  1. 建置方案。

  2. 當您執行此專案在偵錯工具時,會執行個體化 Visual Studio 的第二個執行個體。

  3. 建立文字檔 輸入一些文字,其中包含左括號和右括號。

    [
       Hello
    ]
    
  4. 應該包含這兩個括號的大綱區域。 您應該能夠按一下減號左邊的大括號來摺疊大綱區域。 當區域摺疊,省略符號 (...) 摺疊的區域,以及包含文字的快顯畫面的左側應該會出現暫留文字應該會出現,當您將指標移入省略符號。

請參閱

工作

逐步解說: 連結至檔案的副檔名的內容類型