diff --git a/Tests/IntegrationTests/Test.cs b/Tests/IntegrationTests/Test.cs index 34aecbc..2bcdf13 100644 --- a/Tests/IntegrationTests/Test.cs +++ b/Tests/IntegrationTests/Test.cs @@ -27,17 +27,19 @@ public void ClassificationReturnsExpectedResult(Type testCaseType) var classifier = classifierProvider.GetClassifier(new TextBufferStub(testCase.ContentType)); var actualResult = new List(); + + var spansToReclassify = new List(); + classifier.ClassificationChanged += (sender, args) => spansToReclassify.Add(args.ChangeSpan); + foreach (var line in testCase.SourceText) { - var snapshot = new TextSnapshotStub(line); - var span = new SnapshotSpan(snapshot, new Span(0, snapshot.Length)); - var classificationSpans = classifier.GetClassificationSpans(span); - foreach (var classificationSpan in classificationSpans) + var span = Utils.CreateSpan(line); + DoClassification(classifier, span, actualResult); + foreach (var spanToReclassify in spansToReclassify) { - var classifiedText = classificationSpan.Span.GetText(); - var classificationType = classificationSpan.ClassificationType.Classification; - actualResult.Add(new ClassifiedText(classificationType, classifiedText)); + DoClassification(classifier, spanToReclassify, actualResult); } + spansToReclassify.Clear(); } actualResult.Should().BeEquivalentTo(testCase.ExpectedResult); @@ -62,5 +64,16 @@ private IClassifierProvider CreateClassifierProvider() var classifierProvider = container.GetExport(); return classifierProvider.Value; } + + private void DoClassification(IClassifier classifier, SnapshotSpan span, IList result) + { + var classificationSpans = classifier.GetClassificationSpans(span); + foreach (var classificationSpan in classificationSpans) + { + var classifiedText = classificationSpan.Span.GetText(); + var classificationType = classificationSpan.ClassificationType.Classification; + result.Add(new ClassifiedText(classificationType, classifiedText)); + } + } } } \ No newline at end of file diff --git a/Tests/IntegrationTests/TestCases/BuildOrderOutput.cs b/Tests/IntegrationTests/TestCases/BuildOrderOutput.cs index 6a8d281..a562688 100644 --- a/Tests/IntegrationTests/TestCases/BuildOrderOutput.cs +++ b/Tests/IntegrationTests/TestCases/BuildOrderOutput.cs @@ -16,15 +16,18 @@ public class BuildOrderOutput : ITestCase "2>------ Build started: Project: WebDemo, Configuration: Debug Any CPU ------\r\n", "1>C:\\test\\ConsoleDemo\\Program.cs: warning CS0168: The variable 'ex' is declared but never used\r\n", "1>C:\\test\\ConsoleDemo\\Program.cs(1,14,1,15): error CS1022: Type or namespace definition, or end-of-file expected\r\n", - "2> WebDemo -> C:\\test\\WebDemo\bin\\WebDemo.dll\r\n", + "2> WebDemo -> C:\\test\\WebDemo\\bin\\WebDemo.dll\r\n", "========== Build: 1 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========\r\n" }; public IReadOnlyList ExpectedResult { get; } = new[] { new ClassifiedText(ClassificationType.BuildMessageWarning, "warning CS0168: The variable 'ex' is declared but never used"), + new ClassifiedText(ClassificationType.BuildActionStartedWarning,"------ Build started: Project: ConsoleDemo, Configuration: Debug Any CPU ------"), new ClassifiedText(ClassificationType.BuildMessageError, "error CS1022: Type or namespace definition, or end-of-file expected"), - new ClassifiedText(ClassificationType.BuildResultFailed, "========== Build: 1 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========\r\n") + new ClassifiedText(ClassificationType.BuildActionStartedError,"------ Build started: Project: ConsoleDemo, Configuration: Debug Any CPU ------"), + new ClassifiedText(ClassificationType.BuildResultFailed, "========== Build: 1 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========\r\n"), + new ClassifiedText(ClassificationType.BuildActionStartedSuccess,"------ Build started: Project: WebDemo, Configuration: Debug Any CPU ------") }; } } \ No newline at end of file diff --git a/Tests/IntegrationTests/TestCases/BuildOutput.cs b/Tests/IntegrationTests/TestCases/BuildOutput.cs index 5816215..92f8121 100644 --- a/Tests/IntegrationTests/TestCases/BuildOutput.cs +++ b/Tests/IntegrationTests/TestCases/BuildOutput.cs @@ -23,8 +23,11 @@ public class BuildOutput : ITestCase public IReadOnlyList ExpectedResult { get; } = new[] { new ClassifiedText(ClassificationType.BuildMessageWarning, "warning CS0168: The variable 'ex' is declared but never used"), + new ClassifiedText(ClassificationType.BuildActionStartedWarning, "------ Build started: Project: ConsoleDemo, Configuration: Debug Any CPU ------"), new ClassifiedText(ClassificationType.BuildMessageError, "error CS1022: Type or namespace definition, or end-of-file expected"), - new ClassifiedText(ClassificationType.BuildResultFailed, "========== Build: 1 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========\r\n") + new ClassifiedText(ClassificationType.BuildActionStartedError, "------ Build started: Project: ConsoleDemo, Configuration: Debug Any CPU ------"), + new ClassifiedText(ClassificationType.BuildResultFailed, "========== Build: 1 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========\r\n"), + new ClassifiedText(ClassificationType.BuildActionStartedSuccess, "------ Build started: Project: WebDemo, Configuration: Debug Any CPU ------") }; } } \ No newline at end of file diff --git a/Tests/IntegrationTests/TestCases/PublishResult.cs b/Tests/IntegrationTests/TestCases/PublishResult.cs index 86b69cb..9c3451d 100644 --- a/Tests/IntegrationTests/TestCases/PublishResult.cs +++ b/Tests/IntegrationTests/TestCases/PublishResult.cs @@ -31,7 +31,9 @@ public class PublishResult : ITestCase public IReadOnlyList ExpectedResult { get; } = new[] { new ClassifiedText(ClassificationType.BuildResultSucceeded, "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========\r\n"), + new ClassifiedText(ClassificationType.BuildActionStartedSuccess,"------ Build started: Project: WebDemo, Configuration: Release Any CPU ------"), new ClassifiedText(ClassificationType.PublishResultSucceeded, "========== Publish: 1 succeeded, 0 failed, 0 skipped ==========\r\n"), + new ClassifiedText(ClassificationType.BuildActionStartedSuccess,"------ Publish started: Project: WebDemo, Configuration: Release Any CPU ------") }; } } \ No newline at end of file diff --git a/Tests/UnitTests/ClassificationFormatTests.cs b/Tests/UnitTests/ClassificationFormatTests.cs index af40e41..3ef2def 100644 --- a/Tests/UnitTests/ClassificationFormatTests.cs +++ b/Tests/UnitTests/ClassificationFormatTests.cs @@ -21,6 +21,9 @@ public class ClassificationFormatTests { typeof(BuildMessageWarningFormatDefinition), "Output enhancer: Build warning message" }, { typeof(BuildResultFailedFormatDefinition), "Output enhancer: Build failed" }, { typeof(BuildResultSucceededFormatDefinition), "Output enhancer: Build succeeded" }, + { typeof(BuildActionStartedSuccessFormatDefinition), "Output enhancer: Build action started and succeeded" }, + { typeof(BuildActionStartedWarningFormatDefinition), "Output enhancer: Build action started and finished with warnings" }, + { typeof(BuildActionStartedErrorFormatDefinition), "Output enhancer: Build action started and failed" }, { typeof(PublishResultSucceededFormatDefinition), "Output enhancer: Publish succeeded" }, { typeof(PublishResultFailedFormatDefinition), "Output enhancer: Publish failed" }, { typeof(DebugExceptionFormatDefinition), "Output enhancer: Debug exception message" }, diff --git a/Tests/UnitTests/DispatcherTests.cs b/Tests/UnitTests/DispatcherTests.cs new file mode 100644 index 0000000..90e0a90 --- /dev/null +++ b/Tests/UnitTests/DispatcherTests.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using FluentAssertions; +using Xunit; + +namespace Balakin.VSOutputEnhancer.Tests.UnitTests +{ + [ExcludeFromCodeCoverage] + public class DispatcherTests + { + private class TestEvent : IEvent + { + } + + private class AnotherTestEvent : IEvent + { + } + + private class TestEventHandler : IEventHandler where TEvent : IEvent + { + public IEnumerable ContentTypes { get; } + public Int32 InvocationCount { get; private set; } + + public void Handle(IDispatcher dispatcher, DataContainer data, TEvent @event) + { + InvocationCount++; + } + } + + [Fact] + public void AddHandlerDoesNotThrowException() + { + var dispatcher = new Dispatcher(); + var handler = new TestEventHandler(); + dispatcher.AddHandler(handler); + } + + [Fact] + public void DispatchInvokesHandler() + { + var dispatcher = new Dispatcher(); + var handler = new TestEventHandler(); + dispatcher.AddHandler(handler); + + var @event = new TestEvent(); + var data = new DataContainer(); + dispatcher.Dispatch(@event, data); + + handler.InvocationCount.Should().Be(1); + } + + [Fact] + public void DispatchInvokesOnlyAppropriateHandler() + { + var dispatcher = new Dispatcher(); + var handler = new TestEventHandler(); + dispatcher.AddHandler(handler); + + var anotherHandler = new TestEventHandler(); + dispatcher.AddHandler(anotherHandler); + + var @event = new TestEvent(); + var data = new DataContainer(); + dispatcher.Dispatch(@event, data); + + handler.InvocationCount.Should().Be(1); + anotherHandler.InvocationCount.Should().Be(0); + } + } +} \ No newline at end of file diff --git a/Tests/UnitTests/StyleManagerTests.cs b/Tests/UnitTests/StyleManagerTests.cs index fa905ab..bddc6ab 100644 --- a/Tests/UnitTests/StyleManagerTests.cs +++ b/Tests/UnitTests/StyleManagerTests.cs @@ -35,6 +35,7 @@ public void SimilarClassificationTypesHaveSimilarColors(Theme theme) { ClassificationType.BuildMessageError, ClassificationType.BuildResultFailed, + ClassificationType.BuildActionStartedError, ClassificationType.PublishResultFailed, ClassificationType.DebugTraceError, ClassificationType.DebugException, @@ -44,6 +45,7 @@ public void SimilarClassificationTypesHaveSimilarColors(Theme theme) }; var warning = new[] { + ClassificationType.BuildActionStartedWarning, ClassificationType.BuildMessageWarning, ClassificationType.DebugTraceWarning, ClassificationType.NpmMessageWarning @@ -51,6 +53,7 @@ public void SimilarClassificationTypesHaveSimilarColors(Theme theme) var success = new[] { ClassificationType.BuildResultSucceeded, + ClassificationType.BuildActionStartedSuccess, ClassificationType.PublishResultSucceeded, ClassificationType.NpmResultSucceeded }; diff --git a/Tests/VSOutputEnhancer.Logic.Tests/ClassificationTypeExportsTests.cs b/Tests/VSOutputEnhancer.Logic.Tests/ClassificationTypeExportsTests.cs index ebafcc3..39ecf2f 100644 --- a/Tests/VSOutputEnhancer.Logic.Tests/ClassificationTypeExportsTests.cs +++ b/Tests/VSOutputEnhancer.Logic.Tests/ClassificationTypeExportsTests.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.Composition; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -14,20 +16,51 @@ public class ClassificationTypeExportsTests { [Fact] public void AllClassificationTypeDefinitionsAreExported() + { + var exportedDefinitions = EnumerateClassificationTypes(); + + var allDefinitions = ClassificationType.All; + var notExportedDefinitions = allDefinitions.Except(exportedDefinitions); + notExportedDefinitions.Should().BeEmpty("All classification types should be exported"); + } + + [Theory] + [MemberData(nameof(ClassificationTypesTheoryData))] + public void AllClassificationTypesHaveFormatDefinitionExported(String classificationType) + { + var formatDefinitionType = typeof(ClassificationFormatDefinition); + var assembly = typeof(ClassificationType).Assembly; + var allFormatDefinitions = assembly.GetTypes().Where(formatDefinitionType.IsAssignableFrom); + + allFormatDefinitions.Should().ContainSingle(t => t.GetCustomAttributes().Any(a => a.ClassificationTypeNames.Contains(classificationType))); + } + + public static IEnumerable ClassificationTypesTheoryData() + { + var classificationTypesWithoutFormat = new[] { ClassificationType.DebugTraceInformation }; + + return EnumerateClassificationTypes() + .Where(t => !classificationTypesWithoutFormat.Contains(t)) + .Select(t => new Object[] { t }); + } + + private static IEnumerable EnumerateClassificationTypes() { var exportAttribute = typeof(ExportAttribute); var classificationTypeDefinition = typeof(ClassificationTypeDefinition); var allFields = typeof(ClassificationTypeExports).GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); var exportedDefinitions = allFields + .SelectMany(t => t.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) .Where(f => f.CustomAttributes.Any(a => a.AttributeType == exportAttribute)) .Where(f => f.GetCustomAttributes().Any(a => a.ContractType == classificationTypeDefinition)) .SelectMany(f => f.GetCustomAttributes()) .Select(a => a.Name) - .Distinct(); + .Distinct() + .ToList(); var allDefinitions = ClassificationType.All; - var notExportedDefinitions = allDefinitions.Except(exportedDefinitions); + var notExportedDefinitions = allDefinitions.Except(exportedDefinitions).ToList(); notExportedDefinitions.Should().BeEmpty("All classification types should be exported"); } } diff --git a/VSOutputEnhancer.Logic/ClassificationType.cs b/VSOutputEnhancer.Logic/ClassificationType.cs index 0f7e54e..5036205 100644 --- a/VSOutputEnhancer.Logic/ClassificationType.cs +++ b/VSOutputEnhancer.Logic/ClassificationType.cs @@ -10,6 +10,9 @@ public static class ClassificationType BuildMessageWarning, BuildResultFailed, BuildResultSucceeded, + BuildActionStartedSuccess, + BuildActionStartedWarning, + BuildActionStartedError, PublishResultFailed, PublishResultSucceeded, DebugTraceError, @@ -29,6 +32,10 @@ public static class ClassificationType public const String BuildResultFailed = "BuildResultFailed"; public const String BuildResultSucceeded = "BuildResultSucceeded"; + public const String BuildActionStartedSuccess = "BuildActionStartedSuccess"; + public const String BuildActionStartedWarning = "BuildActionStartedWarning"; + public const String BuildActionStartedError = "BuildActionStartedError"; + public const String PublishResultFailed = "PublishResultFailed"; public const String PublishResultSucceeded = "PublishResultSucceeded"; diff --git a/VSOutputEnhancer.Logic/ClassificationTypeExports.cs b/VSOutputEnhancer.Logic/ClassificationTypeExports.cs index c10e6a4..e70ddbe 100644 --- a/VSOutputEnhancer.Logic/ClassificationTypeExports.cs +++ b/VSOutputEnhancer.Logic/ClassificationTypeExports.cs @@ -14,6 +14,18 @@ public static class ClassificationTypeExports [Name(ClassificationType.BuildResultFailed)] public static ClassificationTypeDefinition BuildResultFailed; + [Export(typeof(ClassificationTypeDefinition))] + [Name(ClassificationType.BuildActionStartedSuccess)] + public static ClassificationTypeDefinition BuildActionStartedSuccess; + + [Export(typeof(ClassificationTypeDefinition))] + [Name(ClassificationType.BuildActionStartedWarning)] + public static ClassificationTypeDefinition BuildActionStartedWarning; + + [Export(typeof(ClassificationTypeDefinition))] + [Name(ClassificationType.BuildActionStartedError)] + public static ClassificationTypeDefinition BuildActionStartedError; + [Export(typeof(ClassificationTypeDefinition))] [Name(ClassificationType.BuildMessageError)] public static ClassificationTypeDefinition BuildMessageError; diff --git a/VSOutputEnhancer.Logic/Classifier.cs b/VSOutputEnhancer.Logic/Classifier.cs index 9600cad..f472abe 100644 --- a/VSOutputEnhancer.Logic/Classifier.cs +++ b/VSOutputEnhancer.Logic/Classifier.cs @@ -1,14 +1,17 @@ using System; using System.Collections.Generic; using System.Linq; +using Balakin.VSOutputEnhancer.Parsers; +using Balakin.VSOutputEnhancer.Events; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; namespace Balakin.VSOutputEnhancer.Logic { - public class Classifier : IClassifier + public class Classifier : IClassifier, IEventHandler { private readonly IDispatcher dispatcher; + private readonly DataContainer dataContainer; private readonly IReadOnlyCollection spanClassifiers; private readonly IClassificationTypeService classificationTypeService; @@ -20,14 +23,18 @@ public Classifier( this.dispatcher = dispatcher; this.spanClassifiers = spanClassifiers; this.classificationTypeService = classificationTypeService; + + dataContainer = new DataContainer(); } + public IEnumerable ContentTypes => throw new NotSupportedException(); + public IList GetClassificationSpans(SnapshotSpan span) { var result = new List(); foreach (var classifier in spanClassifiers) { - var classifierResult = classifier.Classify(span, dispatcher); + var classifierResult = classifier.Classify(span, dispatcher, dataContainer); var classificationSpans = classifierResult.Select(r => CreateClassificationSpan(span, r)); result.AddRange(classificationSpans); } @@ -35,11 +42,7 @@ public IList GetClassificationSpans(SnapshotSpan span) return result; } - public event EventHandler ClassificationChanged - { - add { } - remove { } - } + public event EventHandler ClassificationChanged; private ClassificationSpan CreateClassificationSpan(SnapshotSpan originalSpan, ProcessedParsedData data) { @@ -47,5 +50,11 @@ private ClassificationSpan CreateClassificationSpan(SnapshotSpan originalSpan, P var span = new SnapshotSpan(originalSpan.Snapshot, data.Span); return new ClassificationSpan(span, classificationType); } + + public void Handle(IDispatcher dispatcher, DataContainer data, ClassificationChangedEvent @event) + { + var eventArgs = new ClassificationChangedEventArgs(@event.Span); + ClassificationChanged?.Invoke(this, eventArgs); + } } } \ No newline at end of file diff --git a/VSOutputEnhancer.Logic/ClassifierProvider.cs b/VSOutputEnhancer.Logic/ClassifierProvider.cs index c34aff8..8a696d8 100644 --- a/VSOutputEnhancer.Logic/ClassifierProvider.cs +++ b/VSOutputEnhancer.Logic/ClassifierProvider.cs @@ -46,6 +46,7 @@ public IClassifier GetClassifier(ITextBuffer textBuffer) var dispatcher = CreateDispatcher(contentType); var classifier = new Classifier(dispatcher, classifiers, classificationTypeService); + dispatcher.AddHandler(classifier); return classifier; } diff --git a/VSOutputEnhancer.Logic/Classifiers/BowerMessage/BowerMessageClassifier.cs b/VSOutputEnhancer.Logic/Classifiers/BowerMessage/BowerMessageClassifier.cs index dac1f13..34fd240 100644 --- a/VSOutputEnhancer.Logic/Classifiers/BowerMessage/BowerMessageClassifier.cs +++ b/VSOutputEnhancer.Logic/Classifiers/BowerMessage/BowerMessageClassifier.cs @@ -18,7 +18,7 @@ public BowerMessageClassifier(IParser parser) : base(parser) ContentType.Output }; - protected override IEnumerable Classify(SnapshotSpan span, BowerMessageData parsedData) + protected override IEnumerable Classify(SnapshotSpan span, BowerMessageData parsedData, DataContainer data) { var classificationType = GetClassificationType(parsedData.Type); if (String.IsNullOrEmpty(classificationType)) diff --git a/VSOutputEnhancer.Logic/Classifiers/BuildFileRelatedMessage/BuildFileRelatedMessageClassifier.cs b/VSOutputEnhancer.Logic/Classifiers/BuildFileRelatedMessage/BuildFileRelatedMessageClassifier.cs index 5d9c4bd..12dc3e5 100644 --- a/VSOutputEnhancer.Logic/Classifiers/BuildFileRelatedMessage/BuildFileRelatedMessageClassifier.cs +++ b/VSOutputEnhancer.Logic/Classifiers/BuildFileRelatedMessage/BuildFileRelatedMessageClassifier.cs @@ -19,7 +19,7 @@ public BuildFileRelatedMessageClassifier(IParser pa ContentType.BuildOrderOutput }; - protected override IEnumerable Classify(SnapshotSpan span, BuildFileRelatedMessageData parsedData) + protected override IEnumerable Classify(SnapshotSpan span, BuildFileRelatedMessageData parsedData, DataContainer data) { var messageSpan = parsedData.FullMessage.Span; if (parsedData.Type == MessageType.Warning) diff --git a/VSOutputEnhancer.Logic/Classifiers/BuildResult/BuildResultClassifier.cs b/VSOutputEnhancer.Logic/Classifiers/BuildResult/BuildResultClassifier.cs index f7fd0e9..ca9d6b9 100644 --- a/VSOutputEnhancer.Logic/Classifiers/BuildResult/BuildResultClassifier.cs +++ b/VSOutputEnhancer.Logic/Classifiers/BuildResult/BuildResultClassifier.cs @@ -19,7 +19,7 @@ public BuildResultClassifier(IParser parser) : base(parser) ContentType.BuildOrderOutput }; - protected override IEnumerable Classify(SnapshotSpan span, BuildResultData parsedData) + protected override IEnumerable Classify(SnapshotSpan span, BuildResultData parsedData, DataContainer data) { if (parsedData.Failed == 0) { diff --git a/VSOutputEnhancer.Logic/Classifiers/DebugException/DebugExceptionClassifier.cs b/VSOutputEnhancer.Logic/Classifiers/DebugException/DebugExceptionClassifier.cs index e75ff38..fcf52f8 100644 --- a/VSOutputEnhancer.Logic/Classifiers/DebugException/DebugExceptionClassifier.cs +++ b/VSOutputEnhancer.Logic/Classifiers/DebugException/DebugExceptionClassifier.cs @@ -18,7 +18,7 @@ public DebugExceptionClassifier(IParser parser) : base(parse ContentType.DebugOutput, }; - protected override IEnumerable Classify(SnapshotSpan span, DebugExceptionData parsedData) + protected override IEnumerable Classify(SnapshotSpan span, DebugExceptionData parsedData, DataContainer data) { yield return new ProcessedParsedData(span, ClassificationType.DebugException); } diff --git a/VSOutputEnhancer.Logic/Classifiers/DebugTraceMessage/DebugTraceMessageClassifier.cs b/VSOutputEnhancer.Logic/Classifiers/DebugTraceMessage/DebugTraceMessageClassifier.cs index f8f74bb..df5e4d9 100644 --- a/VSOutputEnhancer.Logic/Classifiers/DebugTraceMessage/DebugTraceMessageClassifier.cs +++ b/VSOutputEnhancer.Logic/Classifiers/DebugTraceMessage/DebugTraceMessageClassifier.cs @@ -19,7 +19,7 @@ public DebugTraceMessageClassifier(IParser parser) : base ContentType.DebugOutput, }; - protected override IEnumerable Classify(SnapshotSpan span, DebugTraceMessageData parsedData) + protected override IEnumerable Classify(SnapshotSpan span, DebugTraceMessageData parsedData, DataContainer data) { var classificationType = GetClassificationType(parsedData.Type); if (String.IsNullOrEmpty(classificationType)) diff --git a/VSOutputEnhancer.Logic/Classifiers/NpmMessage/NpmMessageClassifier.cs b/VSOutputEnhancer.Logic/Classifiers/NpmMessage/NpmMessageClassifier.cs index 2440d4b..8017b5e 100644 --- a/VSOutputEnhancer.Logic/Classifiers/NpmMessage/NpmMessageClassifier.cs +++ b/VSOutputEnhancer.Logic/Classifiers/NpmMessage/NpmMessageClassifier.cs @@ -20,7 +20,7 @@ public NpmMessageClassifier(IParser parser) : base(parser) ContentType.BuildOrderOutput }; - protected override IEnumerable Classify(SnapshotSpan span, NpmMessageData parsedData) + protected override IEnumerable Classify(SnapshotSpan span, NpmMessageData parsedData, DataContainer data) { var classificationType = GetClassificationType(parsedData.Type); if (String.IsNullOrEmpty(classificationType)) diff --git a/VSOutputEnhancer.Logic/Classifiers/NpmResult/NpmResultClassifier.cs b/VSOutputEnhancer.Logic/Classifiers/NpmResult/NpmResultClassifier.cs index d992dde..ed79a5d 100644 --- a/VSOutputEnhancer.Logic/Classifiers/NpmResult/NpmResultClassifier.cs +++ b/VSOutputEnhancer.Logic/Classifiers/NpmResult/NpmResultClassifier.cs @@ -20,7 +20,7 @@ public NpmResultClassifier(IParser parser) : base(parser) ContentType.BuildOrderOutput }; - protected override IEnumerable Classify(SnapshotSpan span, NpmResultData parsedData) + protected override IEnumerable Classify(SnapshotSpan span, NpmResultData parsedData, DataContainer data) { if (parsedData.ExitCode == 0) { diff --git a/VSOutputEnhancer.Logic/Classifiers/ParserBasedSpanClassifier.cs b/VSOutputEnhancer.Logic/Classifiers/ParserBasedSpanClassifier.cs index 7694779..fc5709f 100644 --- a/VSOutputEnhancer.Logic/Classifiers/ParserBasedSpanClassifier.cs +++ b/VSOutputEnhancer.Logic/Classifiers/ParserBasedSpanClassifier.cs @@ -7,7 +7,7 @@ namespace Balakin.VSOutputEnhancer.Logic.Classifiers public abstract class ParserBasedSpanClassifier : ISpanClassifier where TParsedData : ParsedData { - private static readonly IEnumerable Empty = new ProcessedParsedData[0]; + protected static readonly IEnumerable EmptyClassification = new ProcessedParsedData[0]; private readonly IParser _parser; @@ -18,18 +18,18 @@ public ParserBasedSpanClassifier(IParser parser) public abstract IEnumerable ContentTypes { get; } - protected abstract IEnumerable Classify(SnapshotSpan span, TParsedData parsedData); + protected abstract IEnumerable Classify(SnapshotSpan span, TParsedData parsedData, DataContainer data); - public IEnumerable Classify(SnapshotSpan span, IDispatcher dispatcher) + public IEnumerable Classify(SnapshotSpan span, IDispatcher dispatcher, DataContainer data) { if (!_parser.TryParse(span, out var parsedData)) { - return Empty; + return EmptyClassification; } - dispatcher.Dispatch(new SpanParsedEvent(parsedData)); + dispatcher.Dispatch(new SpanParsedEvent(span, parsedData), data); - return Classify(span, parsedData); + return Classify(span, parsedData, data); } } } \ No newline at end of file diff --git a/VSOutputEnhancer.Logic/Classifiers/PublishResult/PublishResultClassifier.cs b/VSOutputEnhancer.Logic/Classifiers/PublishResult/PublishResultClassifier.cs index 1a51891..12ffce4 100644 --- a/VSOutputEnhancer.Logic/Classifiers/PublishResult/PublishResultClassifier.cs +++ b/VSOutputEnhancer.Logic/Classifiers/PublishResult/PublishResultClassifier.cs @@ -19,7 +19,7 @@ public PublishResultClassifier(IParser parser) : base(parser) ContentType.BuildOrderOutput }; - protected override IEnumerable Classify(SnapshotSpan span, PublishResultData parsedData) + protected override IEnumerable Classify(SnapshotSpan span, PublishResultData parsedData, DataContainer data) { if (parsedData.Failed == 0) { diff --git a/VSOutputEnhancer.Logic/Dispatcher.cs b/VSOutputEnhancer.Logic/Dispatcher.cs index 6906e5f..d1a2ad7 100644 --- a/VSOutputEnhancer.Logic/Dispatcher.cs +++ b/VSOutputEnhancer.Logic/Dispatcher.cs @@ -7,7 +7,7 @@ namespace Balakin.VSOutputEnhancer.Logic { public class Dispatcher : IDispatcher { - private readonly IDictionary> eventHandlers = new Dictionary>(); + private readonly IDictionary> eventHandlers = new Dictionary>(); public void AddHandler(IEventHandler handler) { @@ -17,12 +17,12 @@ public void AddHandler(IEventHandler handler) } } - public void Dispatch(IEvent @event) + public void Dispatch(IEvent @event, DataContainer data) { var type = @event.GetType(); while (type != null) { - InvokeHandlers(@event, type); + InvokeHandlers(@event, data, type); type = type.BaseType; } } @@ -50,7 +50,7 @@ private void AddEventHandler(IEventHandler handler, Type eventType) private void AddEventHandler(IEventHandler handler) where TEvent : IEvent { - Action handlerDelegateToAdd = (@event) => handler.Handle((TEvent)@event); + Action handlerDelegateToAdd = (@event, data) => handler.Handle(this, data, (TEvent)@event); if (eventHandlers.TryGetValue(typeof(TEvent), out var handlerDelegate)) { handlerDelegateToAdd = handlerDelegate + handlerDelegateToAdd; @@ -59,13 +59,11 @@ private void AddEventHandler(IEventHandler handler) where TEvent eventHandlers[typeof(TEvent)] = handlerDelegateToAdd; } - private void InvokeHandlers(Object @event, Type eventType) + private void InvokeHandlers(Object @event, DataContainer data, Type eventType) { + if (eventHandlers.TryGetValue(eventType, out var handler)) { - if (eventHandlers.TryGetValue(eventType, out var handler)) - { - handler(@event); - } + handler(@event, data); } } } diff --git a/VSOutputEnhancer.Logic/Events/SpanParsedEvent.cs b/VSOutputEnhancer.Logic/Events/SpanParsedEvent.cs index 75169a1..f86b29f 100644 --- a/VSOutputEnhancer.Logic/Events/SpanParsedEvent.cs +++ b/VSOutputEnhancer.Logic/Events/SpanParsedEvent.cs @@ -1,11 +1,15 @@ -namespace Balakin.VSOutputEnhancer.Logic.Events +using Microsoft.VisualStudio.Text; + +namespace Balakin.VSOutputEnhancer.Events { public class SpanParsedEvent : IEvent { public TParsedData ParsedData { get; } + public SnapshotSpan Span { get; } - public SpanParsedEvent(TParsedData parsedData) + public SpanParsedEvent(SnapshotSpan span, TParsedData parsedData) { + Span = span; ParsedData = parsedData; } } diff --git a/VSOutputEnhancer.Logic/IDispatcher.cs b/VSOutputEnhancer.Logic/IDispatcher.cs index 5cbfeaf..4febf87 100644 --- a/VSOutputEnhancer.Logic/IDispatcher.cs +++ b/VSOutputEnhancer.Logic/IDispatcher.cs @@ -2,6 +2,6 @@ namespace Balakin.VSOutputEnhancer.Logic { public interface IDispatcher { - void Dispatch(IEvent @event); + void Dispatch(IEvent @event, DataContainer data); } } \ No newline at end of file diff --git a/VSOutputEnhancer.Logic/IEventHandler.cs b/VSOutputEnhancer.Logic/IEventHandler.cs index 41c52c4..173e236 100644 --- a/VSOutputEnhancer.Logic/IEventHandler.cs +++ b/VSOutputEnhancer.Logic/IEventHandler.cs @@ -11,6 +11,6 @@ public interface IEventHandler public interface IEventHandler : IEventHandler where TEvent : IEvent { - void Handle(TEvent @event); + void Handle(IDispatcher dispatcher, DataContainer data, TEvent @event); } } \ No newline at end of file diff --git a/VSOutputEnhancer.Logic/ISpanClassifier.cs b/VSOutputEnhancer.Logic/ISpanClassifier.cs index 84ddcec..133ad13 100644 --- a/VSOutputEnhancer.Logic/ISpanClassifier.cs +++ b/VSOutputEnhancer.Logic/ISpanClassifier.cs @@ -7,6 +7,6 @@ namespace Balakin.VSOutputEnhancer.Logic public interface ISpanClassifier { IEnumerable ContentTypes { get; } - IEnumerable Classify(SnapshotSpan span, IDispatcher dispatcher); + IEnumerable Classify(SnapshotSpan span, IDispatcher dispatcher, DataContainer data); } } \ No newline at end of file diff --git a/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionCollection.cs b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionCollection.cs new file mode 100644 index 0000000..2b6b168 --- /dev/null +++ b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionCollection.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Text; + +namespace Balakin.VSOutputEnhancer.Classifiers.BuildActionStart +{ + public class BuildActionCollection + { + private class BuildActionValue + { + public BuildActionState? State { get; set; } + public SnapshotSpan? Span { get; set; } + } + + private IEnumerable EmptyStringArray = new String[0]; + + private readonly IDictionary> data = new Dictionary>(); + private readonly IDictionary latestProjects = new Dictionary(); + private readonly IDictionary> latestParallelProjects = new Dictionary>(); + + public IEnumerable EnumerateProjects(String action) + { + return data.ContainsKey(action) ? data[action].Keys : EmptyStringArray; + } + + public void HandleActionStart(String action, String projectName, Int32? buildTaskId, SnapshotSpan span) + { + var value = GetOrAddValue(action, projectName); + value.Span = span; + if (value.Span == null) + { + value.State = null; + } + + SetLatestProject(action, projectName, buildTaskId); + } + + public Boolean HandleStateChange(String action, String projectName, BuildActionState state) + { + var value = GetOrAddValue(action, projectName); + if (value.State >= state) + { + return false; + } + + value.State = state; + return true; + } + + public BuildActionState GetState(String action, String projectName) + { + var value = GetOrAddValue(action, projectName); + return value.State ?? BuildActionState.Unknown; + } + + public SnapshotSpan GetSpan(String action, String projectName) + { + var value = GetOrAddValue(action, projectName); + return value.Span.Value; + } + + public String GetLatestProject(String action, Int32? buildTaskId) + { + String projectName; + + if (buildTaskId == null) + { + if (!latestProjects.TryGetValue(action, out projectName)) + { + return null; + } + + return projectName; + } + + if (!latestParallelProjects.TryGetValue(action, out var projects)) + { + return null; + } + + if (!projects.TryGetValue(buildTaskId.Value, out projectName)) + { + return null; + } + + return projectName; + } + + public void DeleteAll(String action) + { + latestProjects.Remove(action); + latestParallelProjects.Remove(action); + } + + private BuildActionValue GetOrAddValue(String action, String projectName) + { + if (!data.TryGetValue(action, out var projects)) + { + projects = new Dictionary(); + data.Add(action, projects); + } + + if (!projects.TryGetValue(projectName, out var result)) + { + result = new BuildActionValue(); + projects.Add(projectName, result); + } + + return result; + } + + private void SetLatestProject(String action, String projectName, Int32? buildTaskId) + { + if (buildTaskId == null) + { + latestProjects[action] = projectName; + } + else + { + if (!latestParallelProjects.TryGetValue(action, out var projects)) + { + projects = new Dictionary(); + latestParallelProjects.Add(action, projects); + } + + projects[buildTaskId.Value] = projectName; + } + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionStartClassifier.cs b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionStartClassifier.cs new file mode 100644 index 0000000..6cf109d --- /dev/null +++ b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionStartClassifier.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Balakin.VSOutputEnhancer.Parsers; +using Microsoft.VisualStudio.Text; + +namespace Balakin.VSOutputEnhancer.Classifiers.BuildActionStart +{ + [Export(typeof(ISpanClassifier))] + public class BuildActionStartClassifier : ParserBasedSpanClassifier + { + [ImportingConstructor] + public BuildActionStartClassifier(IParser parser) : base(parser) + { + } + + public override IEnumerable ContentTypes { get; } = new[] + { + ContentType.BuildOutput, + ContentType.BuildOrderOutput + }; + + protected override IEnumerable Classify(SnapshotSpan span, BuildActionStartData parsedData, DataContainer data) + { + var action = parsedData.Action.Value; + var projectName = parsedData.ProjectName.Value; + var buildTaskId = parsedData.BuildTaskId.ToNullable(); + var actionCollection = data.Get(); + + actionCollection.HandleActionStart(action, projectName, buildTaskId, span); + var state = actionCollection.GetState(action, projectName); + + switch (state) + { + case BuildActionState.Success: + return new[] { new ProcessedParsedData(parsedData.FullMessage.Span, ClassificationType.BuildActionStartedSuccess) }; + case BuildActionState.Warning: + return new[] { new ProcessedParsedData(parsedData.FullMessage.Span, ClassificationType.BuildActionStartedWarning) }; + case BuildActionState.Error: + return new[] { new ProcessedParsedData(parsedData.FullMessage.Span, ClassificationType.BuildActionStartedError) }; + default: + return EmptyClassification; + } + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionStartData.cs b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionStartData.cs new file mode 100644 index 0000000..1e4fd73 --- /dev/null +++ b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionStartData.cs @@ -0,0 +1,30 @@ +using System; +using Balakin.VSOutputEnhancer.Parsers; + +namespace Balakin.VSOutputEnhancer.Classifiers.BuildActionStart +{ + public class BuildActionStartData : ParsedData + { + // TODO: Refactor ParsedData builder to get rid of this constructor + public BuildActionStartData() + { + } + + public BuildActionStartData( + ParsedValue buildTaskId, + ParsedValue projectName, + ParsedValue action, + ParsedValue fullMessage) + { + BuildTaskId = buildTaskId; + ProjectName = projectName; + Action = action; + FullMessage = fullMessage; + } + + public ParsedValue BuildTaskId { get; private set; } + public ParsedValue ProjectName { get; private set; } + public ParsedValue Action { get; private set; } + public ParsedValue FullMessage { get; private set; } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionStartParser.cs b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionStartParser.cs new file mode 100644 index 0000000..0176b61 --- /dev/null +++ b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionStartParser.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel.Composition; +using System.Text.RegularExpressions; +using Balakin.VSOutputEnhancer.Parsers; +using Microsoft.VisualStudio.Text; + +namespace Balakin.VSOutputEnhancer.Classifiers.BuildActionStart +{ + [Export(typeof(IParser))] + internal class BuildActionStartParser : IParser + { + public Boolean TryParse(SnapshotSpan span, out BuildActionStartData result) + { + var text = span.GetText(); + + result = null; + if (!text.EndsWith(" ------\r\n", StringComparison.Ordinal)) + { + return false; + } + + var regex = "^(?:(?\\d+)>)?(?------ (?.+) started: Project: (?.*), Configuration: (?.*) ------)\r\n$"; + var match = Regex.Match(text, regex, RegexOptions.Compiled); + if (!match.Success) + { + return false; + } + + result = ParsedData.Create(match, span.Span); + return true; + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionState.cs b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionState.cs new file mode 100644 index 0000000..238dd3a --- /dev/null +++ b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildActionState.cs @@ -0,0 +1,10 @@ +namespace Balakin.VSOutputEnhancer.Classifiers.BuildActionStart +{ + public enum BuildActionState + { + Unknown, + Success, + Warning, + Error + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Classifiers/BuildActionStart/BuildFinishedEventHandler.cs b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildFinishedEventHandler.cs new file mode 100644 index 0000000..14ab5eb --- /dev/null +++ b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildFinishedEventHandler.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Balakin.VSOutputEnhancer.Events; +using Balakin.VSOutputEnhancer.Parsers.BuildResult; +using Balakin.VSOutputEnhancer.Parsers.PublishResult; + +namespace Balakin.VSOutputEnhancer.Classifiers.BuildActionStart +{ + [Export(typeof(IEventHandler))] + public class BuildActionFinishedEventHandler : IEventHandler>, IEventHandler> + { + private const String BuildAction = "Build"; + private const String PublishAction = "Publish"; + + public IEnumerable ContentTypes { get; } = new[] + { + ContentType.BuildOutput, + ContentType.BuildOrderOutput + }; + + public void Handle(IDispatcher dispatcher, DataContainer data, SpanParsedEvent @event) + { + HandleActionFinished(BuildAction, dispatcher, data); + } + + public void Handle(IDispatcher dispatcher, DataContainer data, SpanParsedEvent @event) + { + HandleActionFinished(PublishAction, dispatcher, data); + } + + private void HandleActionFinished(String action, IDispatcher dispatcher, DataContainer data) + { + var actionCollection = data.Get(); + + var projects = actionCollection.EnumerateProjects(action); + foreach (var project in projects) + { + var changed = actionCollection.HandleStateChange(action, project, BuildActionState.Success); + if (changed) + { + var span = actionCollection.GetSpan(action, project); + var classificationChanged = new ClassificationChangedEvent(span); + dispatcher.Dispatch(classificationChanged, data); + } + } + + actionCollection.DeleteAll(action); + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Classifiers/BuildActionStart/BuildMessageEventHandler.cs b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildMessageEventHandler.cs new file mode 100644 index 0000000..154a91f --- /dev/null +++ b/VSOutputEnhancer/Classifiers/BuildActionStart/BuildMessageEventHandler.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Balakin.VSOutputEnhancer.Events; +using Balakin.VSOutputEnhancer.Parsers; +using Balakin.VSOutputEnhancer.Parsers.BuildFileRelatedMessage; + +namespace Balakin.VSOutputEnhancer.Classifiers.BuildActionStart +{ + [Export(typeof(IEventHandler))] + public class BuildMessageEventHandler : IEventHandler> + { + private const String BuildAction = "Build"; + + public IEnumerable ContentTypes { get; } = new[] + { + ContentType.BuildOutput, + ContentType.BuildOrderOutput + }; + + public void Handle(IDispatcher dispatcher, DataContainer data, SpanParsedEvent @event) + { + var actionCollection = data.Get(); + + var newState = Convert(@event.ParsedData.Type.Value); + var buildTaskId = @event.ParsedData.BuildTaskId.ToNullable(); + + var projectName = actionCollection.GetLatestProject(BuildAction, buildTaskId); + if (string.IsNullOrEmpty(projectName)) + { + return; + } + + var changed = actionCollection.HandleStateChange(BuildAction, projectName, newState); + if (changed) + { + var span = actionCollection.GetSpan(BuildAction, projectName); + var classificationChanged = new ClassificationChangedEvent(span); + dispatcher.Dispatch(classificationChanged, data); + } + } + + private BuildActionState Convert(MessageType messageType) + { + switch (messageType) + { + case MessageType.Warning: + return BuildActionState.Warning; + case MessageType.Error: + return BuildActionState.Error; + default: + return BuildActionState.Unknown; + } + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/DataContainer.cs b/VSOutputEnhancer/DataContainer.cs new file mode 100644 index 0000000..5b883bd --- /dev/null +++ b/VSOutputEnhancer/DataContainer.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace Balakin.VSOutputEnhancer +{ + public class DataContainer + { + private readonly IDictionary data = new Dictionary(); + + public TData Get() + where TData : new() + { + var type = typeof(TData); + if (data.TryGetValue(type, out var existingItem)) + { + return (TData)existingItem; + } + + var newItem = new TData(); + Set(newItem); + return newItem; + } + + public void Set(TData item) + { + var type = typeof(TData); + data.Add(type, item); + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Events/ClassificationChangedEvent.cs b/VSOutputEnhancer/Events/ClassificationChangedEvent.cs new file mode 100644 index 0000000..f823cb3 --- /dev/null +++ b/VSOutputEnhancer/Events/ClassificationChangedEvent.cs @@ -0,0 +1,14 @@ +using Microsoft.VisualStudio.Text; + +namespace Balakin.VSOutputEnhancer.Events +{ + public class ClassificationChangedEvent : IEvent + { + public SnapshotSpan Span { get; } + + public ClassificationChangedEvent(SnapshotSpan span) + { + Span = span; + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Exports/Formats/BuildActionStartedErrorFormatDefinition.cs b/VSOutputEnhancer/Exports/Formats/BuildActionStartedErrorFormatDefinition.cs new file mode 100644 index 0000000..be695af --- /dev/null +++ b/VSOutputEnhancer/Exports/Formats/BuildActionStartedErrorFormatDefinition.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace Balakin.VSOutputEnhancer.Exports.Formats +{ + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = ClassificationType.BuildActionStartedError)] + [Name(ClassificationType.BuildActionStartedError)] + [UserVisible(false)] + [Order(Before = Priority.Default)] + internal sealed class BuildActionStartedErrorFormatDefinition : StyledClassificationFormatDefinition + { + [ImportingConstructor] + public BuildActionStartedErrorFormatDefinition(IStyleManager styleManager) : base(styleManager) + { + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Exports/Formats/BuildActionStartedSuccessFormatDefinition.cs b/VSOutputEnhancer/Exports/Formats/BuildActionStartedSuccessFormatDefinition.cs new file mode 100644 index 0000000..84a6d02 --- /dev/null +++ b/VSOutputEnhancer/Exports/Formats/BuildActionStartedSuccessFormatDefinition.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace Balakin.VSOutputEnhancer.Exports.Formats +{ + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = ClassificationType.BuildActionStartedSuccess)] + [Name(ClassificationType.BuildActionStartedSuccess)] + [UserVisible(false)] + [Order(Before = Priority.Default)] + internal sealed class BuildActionStartedSuccessFormatDefinition : StyledClassificationFormatDefinition + { + [ImportingConstructor] + public BuildActionStartedSuccessFormatDefinition(IStyleManager styleManager) : base(styleManager) + { + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Exports/Formats/BuildActionStartedWarningFormatDefinition.cs b/VSOutputEnhancer/Exports/Formats/BuildActionStartedWarningFormatDefinition.cs new file mode 100644 index 0000000..b527e45 --- /dev/null +++ b/VSOutputEnhancer/Exports/Formats/BuildActionStartedWarningFormatDefinition.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace Balakin.VSOutputEnhancer.Exports.Formats +{ + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = ClassificationType.BuildActionStartedWarning)] + [Name(ClassificationType.BuildActionStartedWarning)] + [UserVisible(false)] + [Order(Before = Priority.Default)] + internal sealed class BuildActionStartedWarningFormatDefinition : StyledClassificationFormatDefinition + { + [ImportingConstructor] + public BuildActionStartedWarningFormatDefinition(IStyleManager styleManager) : base(styleManager) + { + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Parsers/ParsedValueExtensions.cs b/VSOutputEnhancer/Parsers/ParsedValueExtensions.cs new file mode 100644 index 0000000..65a68ab --- /dev/null +++ b/VSOutputEnhancer/Parsers/ParsedValueExtensions.cs @@ -0,0 +1,16 @@ +namespace Balakin.VSOutputEnhancer.Parsers +{ + public static class ParsedValueExtensions + { + public static T? ToNullable(this ParsedValue parsedValue) + where T : struct + { + if (parsedValue.HasValue) + { + return parsedValue.Value; + } + + return null; + } + } +} \ No newline at end of file diff --git a/VSOutputEnhancer/Resources.Designer.cs b/VSOutputEnhancer/Resources.Designer.cs index fb2622e..149156e 100644 --- a/VSOutputEnhancer/Resources.Designer.cs +++ b/VSOutputEnhancer/Resources.Designer.cs @@ -76,6 +76,24 @@ public static string FormatDisplayName_BuildResultSucceeded { } } + public static string FormatDisplayName_BuildActionStartedSuccess { + get { + return ResourceManager.GetString("FormatDisplayName_BuildActionStartedSuccess", resourceCulture); + } + } + + public static string FormatDisplayName_BuildActionStartedWarning { + get { + return ResourceManager.GetString("FormatDisplayName_BuildActionStartedWarning", resourceCulture); + } + } + + public static string FormatDisplayName_BuildActionStartedError { + get { + return ResourceManager.GetString("FormatDisplayName_BuildActionStartedError", resourceCulture); + } + } + public static string FormatDisplayName_DebugException { get { return ResourceManager.GetString("FormatDisplayName_DebugException", resourceCulture); diff --git a/VSOutputEnhancer/Resources.resx b/VSOutputEnhancer/Resources.resx index afcbbde..71244cd 100644 --- a/VSOutputEnhancer/Resources.resx +++ b/VSOutputEnhancer/Resources.resx @@ -132,6 +132,15 @@ Output enhancer: Build succeeded + + Output enhancer: Build action started and succeeded + + + Output enhancer: Build action started and finished with warnings + + + Output enhancer: Build action started and failed + Output enhancer: Debug exception message diff --git a/VSOutputEnhancer/Themes/DarkTheme.json b/VSOutputEnhancer/Themes/DarkTheme.json index b2f81fb..52f8a3d 100644 --- a/VSOutputEnhancer/Themes/DarkTheme.json +++ b/VSOutputEnhancer/Themes/DarkTheme.json @@ -15,6 +15,19 @@ "Bold": true }, + "BuildActionStartedSuccess": { + "ForegroundColor": "#57A64A", + "Bold": true + }, + "BuildActionStartedWarning": { + "ForegroundColor": "#E9D585", + "Bold": true + }, + "BuildActionStartedError": { + "ForegroundColor": "#D85050", + "Bold": true + }, + "PublishResultFailed": { "ForegroundColor": "#D85050", "Bold": true diff --git a/VSOutputEnhancer/Themes/LightTheme.json b/VSOutputEnhancer/Themes/LightTheme.json index 45cef9b..9956272 100644 --- a/VSOutputEnhancer/Themes/LightTheme.json +++ b/VSOutputEnhancer/Themes/LightTheme.json @@ -15,6 +15,19 @@ "Bold": true }, + "BuildActionStartedSuccess": { + "ForegroundColor": "#409A3A", + "Bold": true + }, + "BuildActionStartedWarning": { + "ForegroundColor": "#D0A825", + "Bold": true + }, + "BuildActionStartedError": { + "ForegroundColor": "#BF0606", + "Bold": true + }, + "PublishResultFailed": { "ForegroundColor": "#BF0606", "Bold": true