Skip to content

Commit

Permalink
Merge pull request #1942 from gentoo90/ko
Browse files Browse the repository at this point in the history
HTML: Add <!-- ko --> virtual element outlining
  • Loading branch information
madskristensen committed Aug 15, 2015
2 parents 47bee41 + 5c758e4 commit 5a6bf33
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 0 deletions.
230 changes: 230 additions & 0 deletions EditorExtensions/HTML/Outlining/KoTagger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Threading;
using MadsKristensen.EditorExtensions.JavaScript;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;

namespace MadsKristensen.EditorExtensions.HTML.Outlining
{
[Export(typeof(ITaggerProvider))]
[TagType(typeof(IOutliningRegionTag))]
[ContentType("htmlx")]
internal sealed class KoTaggerProvider : ITaggerProvider
{
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
{
return buffer.Properties.GetOrCreateSingletonProperty(() => new KoTagger(buffer)) as ITagger<T>;
}
}

internal sealed class KoTagger : ITagger<IOutliningRegionTag>
{
string startHide = "<!-- ko"; //the characters that start the outlining region
string endHide = "/ko -->"; //the characters that end the outlining region
ITextBuffer buffer;
ITextSnapshot snapshot;
List<Region> regions;
private static Regex regex = new Regex(@"ko (.*?)-->", RegexOptions.Compiled);

public KoTagger(ITextBuffer buffer)
{
this.buffer = buffer;
this.snapshot = buffer.CurrentSnapshot;
this.regions = new List<Region>();
this.buffer.Changed += BufferChanged;

Dispatcher.CurrentDispatcher.BeginInvoke(
new Action(() => ReParse()), DispatcherPriority.ApplicationIdle, null);

this.buffer.Changed += BufferChanged;
}

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);

var snapshot = new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End);
Match match = regex.Match(snapshot.GetText());

string text = string.IsNullOrWhiteSpace(match.Groups[1].Value) ? "" : match.Groups[1].Value.Trim();
string hoverText = snapshot.GetText();

//the region starts at the beginning of the "<!-- ko ", and goes until the *end* of the line that contains the "/ko -->".
yield return new TagSpan<IOutliningRegionTag>(
snapshot,
new OutliningRegionTag(false, true, "<!-- ko -->...<!-- /ko -->", hoverText));
}
}
}

public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
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;

Dispatcher.CurrentDispatcher.BeginInvoke(
new Action(() => ReParse()), DispatcherPriority.ApplicationIdle, null);
}

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 || (regionStart = text.IndexOf(startHide.Replace(" ", string.Empty), 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 || (regionStart = text.IndexOf(endHide.Replace(" ", string.Empty), 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)
{
if (this.TagsChanged != null)
this.TagsChanged(this, new SnapshotSpanEventArgs(
new SnapshotSpan(this.snapshot, Span.FromBounds(changeStart, changeEnd))));
}
}

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;
}

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);
}
}
}
1 change: 1 addition & 0 deletions EditorExtensions/WebEssentials2013.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@
<Compile Include="HTML\Completion\TwitterCardCompletion.cs" />
<Compile Include="HTML\Completion\ViewportCompletion.cs" />
<Compile Include="HTML\Helpers\HtmlHelpers.cs" />
<Compile Include="HTML\Outlining\KoTagger.cs" />
<Compile Include="HTML\Outlining\HtmlRegionTagger.cs" />
<Compile Include="HTML\Peek\IDs\IdPeekDefinitionItem.cs" />
<Compile Include="HTML\Peek\IDs\IdPeekItemProvider.cs" />
Expand Down

0 comments on commit 5a6bf33

Please sign in to comment.