Пошаговое руководство: Структурирование
Можно реализовать язык-основанные функции, такие как структурирование путем указания типов областей текста необходимо развернуть или свернуть. Можно определить области в контексте языковой службы или можно определить собственные расширения имени файла и тип содержимого и определение области применить только к этому типу, либо можно применить определения области в существующий тип содержимого (например "текст"). В этом пошаговом руководстве показано, как определить и отображения областей структуры.
Обязательные компоненты
Чтобы выполнить это пошаговое руководство, необходимо устанавливать SDK для Visual Studio 2010.
Примечание
Дополнительные сведения о пакете SDK для Visual Studio см. в разделе интеграция SDK Visual Studio.Чтобы узнать, как загрузить пакет SDK для Visual Studio см. в разделе Центр разработчиков расширяемости Visual Studio на веб-сайте MSDN.
Создание управляемой расширяемости проекта .NET Framework (платформа MEF)
Создание проекта MEF
Создайте проект классификатора редактора. Назовите решение OutlineRegionTest.
Откройте файл source.extension.vsixmanifest в редакторе манифеста VSIX.
Убедитесь, что Content заголовок содержит тип содержимого и компонент MEF Path имеет значение OutlineRegionTest.dll.
Сохранить и закрыть source.extension.vsixmanifest.
Удалите существующие файлы классов.
Структурирование средство создания тегов для реализации
Структура области пометьте типом тега (OutliningRegionTag). Этот тег содержит стандартную структурирование расширений функциональности. Конспектированная область можно развернуть или свернуть. Конспектированная область помечена ЗНАК ПЛЮС или минус, если она свернута, если она развернут и разрезанная область разграничена вертикальной линии.
Следующие шаги показывают, как определить, создает средство создания тегов для областей структуры для всех регионов, в качестве разделителя "[" и "]".
Структурирование средство создания тегов для реализации
Добавьте файл классов и назовите его 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;
Добавьте конструктор, который инициализирует средство создания тегов для поля, анализирует буфер и добавляет обработчик событий 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 метод, который создает диапазонов тегов. В этом примере предполагается, что в диапазоны 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)); } } }
Declare a TagsChanged обработчик событий.
Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) Implements ITagger(Of IOutliningRegionTag).TagsChanged
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
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(); }
Добавьте метод, который анализирует буфер. Пример заданный здесь только для иллюстрации. Он одновременно анализирует буфер во вложенной области структуры.
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)))); } }
Следующий вспомогательный метод возвращает целое число, представляющее уровень структурирования, расположенное в диапазоне 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; }
Следующий вспомогательный метод переводит область (указанную ниже) в 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; } }
Средство создания тегов для реализации поставщика
Необходимо экспортировать для средство создания тегов для средство создания тегов для поставщика. Создает средство создания тегов для поставщика OutliningTagger для буфера типом содержимого "text" или возвращает OutliningTagger если буфер уже имеется.
Средство создания тегов для реализации поставщика
Создайте класс 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
Реализуйте 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 ]
Должно быть структурирование область, содержащая обе фигурные скобки. Необходимо иметь возможность щелкните ЗНАК " минус " свернуть область структурирования слева от открытой фигурные скобки. Когда область свернута, символ с многоточием (...) должен стоять слева от свернутой области и контекстного меню, содержащее текст Текст наведения появиться при перемещении указателя над многоточием.
См. также
Задачи
Пошаговое руководство: Связывание тип контента в расширение имени файла