Поделиться через


Пошаговое руководство: Структурирование

Можно реализовать язык-основанные функции, такие как структурирование путем указания типов областей текста необходимо развернуть или свернуть. Можно определить области в контексте языковой службы или можно определить собственные расширения имени файла и тип содержимого и определение области применить только к этому типу, либо можно применить определения области в существующий тип содержимого (например "текст"). В этом пошаговом руководстве показано, как определить и отображения областей структуры.

Обязательные компоненты

Чтобы выполнить это пошаговое руководство, необходимо устанавливать SDK для Visual Studio 2010.

Примечание

Дополнительные сведения о пакете SDK для Visual Studio см. в разделе интеграция SDK Visual Studio.Чтобы узнать, как загрузить пакет SDK для Visual Studio см. в разделе Центр разработчиков расширяемости Visual Studio на веб-сайте MSDN.

Создание управляемой расширяемости проекта .NET Framework (платформа MEF)

Создание проекта MEF

  1. Создайте проект классификатора редактора. Назовите решение OutlineRegionTest.

  2. Откройте файл source.extension.vsixmanifest в редакторе манифеста VSIX.

  3. Убедитесь, что Content заголовок содержит тип содержимого и компонент MEF Path имеет значение OutlineRegionTest.dll.

  4. Сохранить и закрыть source.extension.vsixmanifest.

  5. Удалите существующие файлы классов.

Структурирование средство создания тегов для реализации

Структура области пометьте типом тега (OutliningRegionTag). Этот тег содержит стандартную структурирование расширений функциональности. Конспектированная область можно развернуть или свернуть. Конспектированная область помечена ЗНАК ПЛЮС или минус, если она свернута, если она развернут и разрезанная область разграничена вертикальной линии.

Следующие шаги показывают, как определить, создает средство создания тегов для областей структуры для всех регионов, в качестве разделителя "[" и "]".

Структурирование средство создания тегов для реализации

  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. Добавьте конструктор, который инициализирует средство создания тегов для поля, анализирует буфер и добавляет обработчик событий 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 метод, который создает диапазонов тегов. В этом примере предполагается, что в диапазоны 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. Declare a TagsChanged обработчик событий.

    Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) Implements ITagger(Of IOutliningRegionTag).TagsChanged
    
    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  8. Add a 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. Следующий вспомогательный метод возвращает целое число, представляющее уровень структурирования, расположенное в диапазоне 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. Следующий вспомогательный метод переводит область (указанную ниже) в 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; }
    } 
    

Средство создания тегов для реализации поставщика

Необходимо экспортировать для средство создания тегов для средство создания тегов для поставщика. Создает средство создания тегов для поставщика OutliningTagger для буфера типом содержимого "text" или возвращает OutliningTagger если буфер уже имеется.

Средство создания тегов для реализации поставщика

  1. Создайте класс OutliningTaggerProvider, реализующий ITaggerProviderи экспортировать его с атрибутами и TagType ContentType.

    <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. Должно быть структурирование область, содержащая обе фигурные скобки. Необходимо иметь возможность щелкните ЗНАК " минус " свернуть область структурирования слева от открытой фигурные скобки. Когда область свернута, символ с многоточием (...) должен стоять слева от свернутой области и контекстного меню, содержащее текст Текст наведения появиться при перемещении указателя над многоточием.

См. также

Задачи

Пошаговое руководство: Связывание тип контента в расширение имени файла