Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions VSColorOutput.Tests/OutputClassifierProviderFilteringTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
using Moq;
using System;
using System.Reflection;
using VSColorOutput.Output.ColorClassifier;

namespace Tests
{
[TestClass]
public class OutputClassifierProviderFilteringTests
{
private const string BufferFilterStateKey = "VSColorOutput.Output.BufferLineFilterState";

[TestInitialize]
public void Setup()
{
var field = typeof(OutputClassifierProvider).GetField("_outputClassifier", BindingFlags.Static | BindingFlags.NonPublic);
if (field != null)
{
field.SetValue(null, null);
}
}

[TestMethod]
public void GetClassifier_AttachesHiddenLineFilterStateToOutputBuffer()
{
var provider = CreateProvider();

var properties = new PropertyCollection();
var contentType = new Mock<IContentType>();
contentType.Setup(c => c.IsOfType("Output")).Returns(true);

var buffer = new Mock<ITextBuffer>();
buffer.SetupGet(b => b.Properties).Returns(properties);
buffer.SetupGet(b => b.ContentType).Returns(contentType.Object);

var classifier = provider.GetClassifier(buffer.Object);

classifier.Should().NotBeNull();
properties.ContainsProperty(BufferFilterStateKey).Should().BeTrue();
}

[TestMethod]
public void GetClassifier_AttachesHiddenLineFilterOnlyOncePerBuffer()
{
var provider = CreateProvider();

var properties = new PropertyCollection();
var contentType = new Mock<IContentType>();
contentType.Setup(c => c.IsOfType("Output")).Returns(true);

var changedSubscriptionCount = 0;
var buffer = new Mock<ITextBuffer>();
buffer.SetupGet(b => b.Properties).Returns(properties);
buffer.SetupGet(b => b.ContentType).Returns(contentType.Object);
buffer.SetupAdd(b => b.Changed += It.IsAny<EventHandler<TextContentChangedEventArgs>>())
.Callback(() => changedSubscriptionCount++);

provider.GetClassifier(buffer.Object);
provider.GetClassifier(buffer.Object);

changedSubscriptionCount.Should().Be(1);
}

private static OutputClassifierProvider CreateProvider()
{
var provider = new OutputClassifierProvider();

var registryField = typeof(OutputClassifierProvider).GetField("ClassificationRegistry", BindingFlags.Instance | BindingFlags.NonPublic);
registryField.SetValue(provider, new Mock<IClassificationTypeRegistryService>().Object);

var formatMapField = typeof(OutputClassifierProvider).GetField("ClassificationFormatMapService", BindingFlags.Instance | BindingFlags.NonPublic);
formatMapField.SetValue(provider, new Mock<IClassificationFormatMapService>().Object);

return provider;
}
}
}
1 change: 1 addition & 0 deletions VSColorOutput.Tests/VSColorOutput.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
<Compile Include="FakeTextSnapshotLine.cs" />
<Compile Include="FindResultsClassifierProviderTests.cs" />
<Compile Include="FindResultsClassifierTests.cs" />
<Compile Include="OutputClassifierProviderFilteringTests.cs" />
<Compile Include="OutputClassifierProviderTests.cs" />
<Compile Include="OutputClassifierTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand Down
96 changes: 94 additions & 2 deletions VSColorOutput/Output/ColorClassifier/OutputClassifierProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using VSColorOutput.State;

Expand All @@ -15,14 +18,17 @@ namespace VSColorOutput.Output.ColorClassifier
[Export(typeof(IClassifierProvider))]
public class OutputClassifierProvider : IClassifierProvider
{
private const string SuppliedClassifierForThisBufferKey = "VSColorOutput.Output.SuppliedClassifierForThisTextBufferKey";
private const string BufferFilterStateKey = "VSColorOutput.Output.BufferLineFilterState";
private static readonly object BufferFilterStateLock = new object();

[Import]
internal IClassificationTypeRegistryService ClassificationRegistry;

[Import]
internal IClassificationFormatMapService ClassificationFormatMapService;

private static OutputClassifier _outputClassifier;
private const string SuppliedClassifierForThisBufferKey = "VSColorOutput.Output.SuppliedClassifierForThisTextBufferKey";

public IClassifier GetClassifier(ITextBuffer buffer)
{
Expand All @@ -40,6 +46,7 @@ public IClassifier GetClassifier(ITextBuffer buffer)
_outputClassifier.Initialize(ClassificationRegistry, ClassificationFormatMapService);
}

AttachHiddenLineFilter(buffer);
return _outputClassifier;
}

Expand Down Expand Up @@ -74,5 +81,90 @@ private static bool CanSupplyClassifier(ITextBuffer buffer)

return false;
}

private static void AttachHiddenLineFilter(ITextBuffer buffer)
{
if (buffer.Properties.ContainsProperty(BufferFilterStateKey)) return;

EventHandler<TextContentChangedEventArgs> handler = null;
handler = (sender, args) => HideMatchingLines(buffer, handler, args);

lock (BufferFilterStateLock)
{
if (buffer.Properties.ContainsProperty(BufferFilterStateKey)) return;
buffer.Properties.AddProperty(BufferFilterStateKey, handler);
buffer.Changed += handler;
}
}

private static void HideMatchingLines(ITextBuffer buffer, EventHandler<TextContentChangedEventArgs> handler, TextContentChangedEventArgs args)
{
if (args == null || args.After == null || args.EditTag == BufferFilterStateKey) return;

try
{
var settings = Settings.Load();
if (settings == null || string.IsNullOrWhiteSpace(settings.HiddenLinesRegExPattern)) return;

var options = settings.HiddenLinesRegExIgnoreCase
? RegexOptions.IgnoreCase
: RegexOptions.None;

var regex = new Regex(
settings.HiddenLinesRegExPattern,
options,
TimeSpan.FromMilliseconds(250));

var spansToDelete = new List<Span>();
foreach (var change in args.Changes)
{
var startLine = args.After.GetLineFromPosition(change.NewPosition).LineNumber;
var endPosition = change.NewEnd > change.NewPosition ? change.NewEnd - 1 : change.NewPosition;
var endLine = args.After.GetLineFromPosition(Math.Min(endPosition, Math.Max(args.After.Length - 1, 0))).LineNumber;

for (var i = startLine; i <= endLine; i++)
{
var line = args.After.GetLineFromLineNumber(i);
var text = line.GetText();
if (!regex.IsMatch(text)) continue;

var span = line.LineBreakLength > 0
? line.ExtentIncludingLineBreak.Span
: line.Extent.Span;

spansToDelete.Add(span);
}
}

if (spansToDelete.Count == 0) return;

using (var edit = buffer.CreateEdit(EditOptions.DefaultMinimalChange, null, BufferFilterStateKey))
{
foreach (var span in spansToDelete
.Distinct()
.OrderByDescending(s => s.Start))
{
edit.Delete(span);
}

if (edit.HasEffectiveChanges)
{
edit.Apply();
}
}
}
catch (RegexMatchTimeoutException)
{
// eat it.
}
catch (ArgumentException)
{
// eat it.
}
catch (Exception ex)
{
Log.LogError(ex.ToString());
}
}
}
}
}
6 changes: 6 additions & 0 deletions VSColorOutput/State/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ public class Settings
[DataMember(Order = 21)]
public string TimeStampDifference { get; set; } = DefaultTimeStampFormat;

[DataMember(Order = 23)]
public string HiddenLinesRegExPattern { get; set; } = string.Empty;

[DataMember(Order = 24)]
public bool HiddenLinesRegExIgnoreCase { get; set; } = true;

private static readonly string ProgramDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VSColorOutput64");

public static event EventHandler SettingsUpdated;
Expand Down
13 changes: 13 additions & 0 deletions VSColorOutput/State/VsColorOutputOptionsDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ public class VsColorOutputOptionsDialog : DialogPage
[Description("Formats elapsed build time, and elapsed and incremental times in debug output window according to system locale")]
public bool FormatTimeInSystemLocale { get; set; }

[Category(ActionSubCategory)]
[DisplayName("Hide Output Lines (RegEx)")]
[Description("Any output line matching this regular expression will be removed from the Output window.")]
public string HiddenLinesRegExPattern { get; set; }

[Category(ActionSubCategory)]
[DisplayName("Hide Output Lines Ignore Case")]
public bool HiddenLinesRegExIgnoreCase { get; set; }

[Category(ActionSubCategory)]
[DisplayName("Yes, I Donated!")]
[Description("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=REEP6X7DSPMZU")]
Expand Down Expand Up @@ -162,6 +171,8 @@ public override void LoadSettingsFromStorage()
ShowTimeStampOnEveryLine = settings.ShowTimeStampOnEveryLine;
ShowHoursInTimeStamps = settings.ShowHoursInTimeStamps;
FormatTimeInSystemLocale = settings.FormatTimeInSystemLocale;
HiddenLinesRegExPattern = settings.HiddenLinesRegExPattern;
HiddenLinesRegExIgnoreCase = settings.HiddenLinesRegExIgnoreCase;

RegExPatterns = settings.Patterns;

Expand Down Expand Up @@ -200,6 +211,8 @@ public override void SaveSettingsToStorage()
ShowTimeStampOnEveryLine = ShowTimeStampOnEveryLine,
ShowHoursInTimeStamps = ShowHoursInTimeStamps,
FormatTimeInSystemLocale = FormatTimeInSystemLocale,
HiddenLinesRegExPattern = HiddenLinesRegExPattern,
HiddenLinesRegExIgnoreCase = HiddenLinesRegExIgnoreCase,
// ---
Patterns = RegExPatterns,
// ---
Expand Down
4 changes: 3 additions & 1 deletion VSColorOutput/VSColorOutput.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Output\Filtering\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down