From 0b8d32fb619f2dafc4172940a01f558c1a334bd3 Mon Sep 17 00:00:00 2001
From: Daniel Grunwald <daniel@danielgrunwald.de>
Date: Fri, 26 Mar 2010 20:44:12 +0000
Subject: [PATCH] Allow SharpDevelop AddIn to filter the logger output. Removed
 mutable 'CurrentErrorOrWarning' in BuildEngine and use logger filters
 instead.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5643 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
---
 AddIns/ICSharpCode.SharpDevelop.addin         |   1 +
 clean.bat                                     |   4 +-
 .../Project/Src/VbcEncodingFixingLogger.cs    |  70 ++------
 .../VBNetBinding/Project/VBNetBinding.addin   |   4 +-
 .../Misc/CodeAnalysis/CodeAnalysis.addin      |   4 +-
 .../Misc/CodeAnalysis/Src/FxCopLogger.cs      | 106 +++++-------
 .../Project/ICSharpCode.SharpDevelop.csproj   |   3 +-
 .../Base/Project/Src/Project/BuildEngine.cs   |   4 +-
 .../Base/Project/Src/Project/BuildError.cs    |  12 ++
 .../MSBuildEngine/BuildWorkerManager.cs       |  32 ++--
 .../MSBuildAdditionalLogger.cs                |   0
 .../Project/MSBuildEngine/MSBuildEngine.cs    | 153 +++++++++---------
 .../MSBuildEngine/MSBuildLoggerFilter.cs      | 136 ++++++++++++++++
 .../NumericUpDown.cs                          |   2 +-
 14 files changed, 301 insertions(+), 230 deletions(-)
 rename src/Main/Base/Project/Src/Project/{ => MSBuildEngine}/MSBuildAdditionalLogger.cs (100%)
 create mode 100644 src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildLoggerFilter.cs

diff --git a/AddIns/ICSharpCode.SharpDevelop.addin b/AddIns/ICSharpCode.SharpDevelop.addin
index 7d23279705..e6cc4e923b 100644
--- a/AddIns/ICSharpCode.SharpDevelop.addin
+++ b/AddIns/ICSharpCode.SharpDevelop.addin
@@ -46,6 +46,7 @@
 			<Doozer name="Debugger" class="ICSharpCode.SharpDevelop.Debugging.DebuggerDoozer"/>
 			<Doozer name="Directory" class="ICSharpCode.SharpDevelop.DirectoryDoozer"/>
 			<Doozer name="TaskBoundAdditionalLogger" class="ICSharpCode.SharpDevelop.Project.TaskBoundAdditionalLoggerDoozer"/>
+			<Doozer name="TaskBoundLoggerFilter" class="ICSharpCode.SharpDevelop.Project.TaskBoundLoggerFilterDoozer"/>
 		</Import>
 	</Runtime>
 	
diff --git a/clean.bat b/clean.bat
index 898504355c..a40479eaf5 100755
--- a/clean.bat
+++ b/clean.bat
@@ -1,2 +1,4 @@
-%windir%\microsoft.net\framework\v4.0.30128\msbuild /m SharpDevelop.sln /t:clean "/p:Platform=Any CPU"
+%windir%\microsoft.net\framework\v4.0.30128\msbuild /m SharpDevelop.sln /t:clean "/p:Platform=Any CPU" /p:Configuration=Debug
+@IF %ERRORLEVEL% NEQ 0 PAUSE
+%windir%\microsoft.net\framework\v4.0.30128\msbuild /m SharpDevelop.sln /t:clean "/p:Platform=Any CPU" /p:Configuration=Release
 @IF %ERRORLEVEL% NEQ 0 PAUSE
\ No newline at end of file
diff --git a/src/AddIns/BackendBindings/VBNetBinding/Project/Src/VbcEncodingFixingLogger.cs b/src/AddIns/BackendBindings/VBNetBinding/Project/Src/VbcEncodingFixingLogger.cs
index fe1e503e80..76a549c721 100644
--- a/src/AddIns/BackendBindings/VBNetBinding/Project/Src/VbcEncodingFixingLogger.cs
+++ b/src/AddIns/BackendBindings/VBNetBinding/Project/Src/VbcEncodingFixingLogger.cs
@@ -16,77 +16,39 @@ namespace VBNetBinding
 	/// <summary>
 	/// Fixes SD2-995 : Special characters not correctly encoded for languages others than English
 	/// </summary>
-	public class VbcEncodingFixingLogger : IMSBuildAdditionalLogger
+	public sealed class VbcEncodingFixingLogger : IMSBuildLoggerFilter
 	{
-		public ILogger CreateLogger(MSBuildEngine engineWorker)
+		public IMSBuildChainedLoggerFilter CreateFilter(MSBuildEngine engine, IMSBuildChainedLoggerFilter nextFilter)
 		{
-			return new VbcLoggerImpl(engineWorker);
+			return new VbcLoggerImpl(engine, nextFilter);
 		}
 		
-		private class VbcLoggerImpl : ILogger
+		sealed class VbcLoggerImpl : IMSBuildChainedLoggerFilter
 		{
-			MSBuildEngine engineWorker;
+			readonly MSBuildEngine engineWorker;
+			readonly IMSBuildChainedLoggerFilter nextFilter;
 			
-			public VbcLoggerImpl(MSBuildEngine engineWorker)
+			public VbcLoggerImpl(MSBuildEngine engineWorker, IMSBuildChainedLoggerFilter nextFilter)
 			{
 				this.engineWorker = engineWorker;
+				this.nextFilter = nextFilter;
 			}
 			
-			public LoggerVerbosity Verbosity {
-				get {
-					throw new NotImplementedException();
-				}
-				set {
-					throw new NotImplementedException();
-				}
-			}
-			
-			public string Parameters {
-				get {
-					throw new NotImplementedException();
-				}
-				set {
-					throw new NotImplementedException();
-				}
-			}
-			
-			IEventSource eventSource;
-			
-			public void Initialize(IEventSource eventSource)
-			{
-				this.eventSource = eventSource;
-				eventSource.ErrorRaised += OnError;
-				eventSource.WarningRaised += OnWarning;
-			}
-			
-			public void Shutdown()
-			{
-				if (eventSource != null) {
-					eventSource.ErrorRaised -= OnError;
-					eventSource.WarningRaised -= OnWarning;
-					eventSource = null;
-				}
-			}
-			
-			void OnError(object sender, BuildErrorEventArgs e)
-			{
-				FixMessage();
-			}
-			
-			void OnWarning(object sender, BuildWarningEventArgs e)
+			static string FixEncoding(string text)
 			{
-				FixMessage();
+				return Encoding.Default.GetString(ICSharpCode.SharpDevelop.Util.ProcessRunner.OemEncoding.GetBytes(text));
 			}
 			
-			void FixMessage()
+			public void HandleError(BuildError error)
 			{
-				engineWorker.CurrentErrorOrWarning.ErrorText = FixEncoding(engineWorker.CurrentErrorOrWarning.ErrorText);
-				engineWorker.CurrentErrorOrWarning.FileName = FixEncoding(engineWorker.CurrentErrorOrWarning.FileName);
+				error.ErrorText = FixEncoding(error.ErrorText);
+				error.FileName = FixEncoding(error.FileName);
+				nextFilter.HandleError(error);
 			}
 			
-			static string FixEncoding(string text)
+			public void HandleBuildEvent(Microsoft.Build.Framework.BuildEventArgs e)
 			{
-				return Encoding.Default.GetString(ICSharpCode.SharpDevelop.Util.ProcessRunner.OemEncoding.GetBytes(text));
+				nextFilter.HandleBuildEvent(e);
 			}
 		}
 	}
diff --git a/src/AddIns/BackendBindings/VBNetBinding/Project/VBNetBinding.addin b/src/AddIns/BackendBindings/VBNetBinding/Project/VBNetBinding.addin
index 343ef77d0f..8f5701300e 100644
--- a/src/AddIns/BackendBindings/VBNetBinding/Project/VBNetBinding.addin
+++ b/src/AddIns/BackendBindings/VBNetBinding/Project/VBNetBinding.addin
@@ -39,8 +39,8 @@
 		<String id="vbc" text = "vbc"/>
 	</Path>
 	
-	<Path name = "/SharpDevelop/MSBuildEngine/AdditionalLoggers">
-		<TaskBoundAdditionalLogger
+	<Path name = "/SharpDevelop/MSBuildEngine/LoggerFilters">
+		<TaskBoundLoggerFilter
 			id = "VbcEncodingFixingLogger"
 			taskname = "vbc"
 			class = "VBNetBinding.VbcEncodingFixingLogger"/>
diff --git a/src/AddIns/Misc/CodeAnalysis/CodeAnalysis.addin b/src/AddIns/Misc/CodeAnalysis/CodeAnalysis.addin
index 633ddcd62e..043ef5de48 100644
--- a/src/AddIns/Misc/CodeAnalysis/CodeAnalysis.addin
+++ b/src/AddIns/Misc/CodeAnalysis/CodeAnalysis.addin
@@ -42,8 +42,8 @@
 		</ComplexCondition>
 	</Path>
 	
-	<Path name = "/SharpDevelop/MSBuildEngine/AdditionalLoggers">
-		<TaskBoundAdditionalLogger
+	<Path name = "/SharpDevelop/MSBuildEngine/LoggerFilters">
+		<TaskBoundLoggerFilter
 			id = "FxCopLogger"
 			taskname = "FxCop"
 			class = "ICSharpCode.CodeAnalysis.FxCopLogger"/>
diff --git a/src/AddIns/Misc/CodeAnalysis/Src/FxCopLogger.cs b/src/AddIns/Misc/CodeAnalysis/Src/FxCopLogger.cs
index 9c5a960e6a..f9d49ce4f6 100644
--- a/src/AddIns/Misc/CodeAnalysis/Src/FxCopLogger.cs
+++ b/src/AddIns/Misc/CodeAnalysis/Src/FxCopLogger.cs
@@ -20,114 +20,80 @@ namespace ICSharpCode.CodeAnalysis
 	/// so this logger fixes the position.
 	/// Additionally, it registers the context menu containing the 'suppress message' command.
 	/// </summary>
-	public class FxCopLogger : IMSBuildAdditionalLogger
+	public class FxCopLogger : IMSBuildLoggerFilter
 	{
-		public ILogger CreateLogger(MSBuildEngine engineWorker)
+		public IMSBuildChainedLoggerFilter CreateFilter(MSBuildEngine engine, IMSBuildChainedLoggerFilter nextFilter)
 		{
-			return new FxCopLoggerImpl(engineWorker);
+			engine.OutputTextLine(StringParser.Parse("${res:ICSharpCode.CodeAnalysis.RunningFxCopOn} " + Path.GetFileNameWithoutExtension(engine.ProjectFileName)));
+			return new FxCopLoggerImpl(engine, nextFilter);
 		}
 		
-		private class FxCopLoggerImpl : ILogger
+		sealed class FxCopLoggerImpl : IMSBuildChainedLoggerFilter
 		{
-			MSBuildEngine engineWorker;
+			readonly MSBuildEngine engineWorker;
+			readonly IMSBuildChainedLoggerFilter nextChainElement;
 			
-			public FxCopLoggerImpl(MSBuildEngine engineWorker)
+			public FxCopLoggerImpl(MSBuildEngine engineWorker, IMSBuildChainedLoggerFilter nextChainElement)
 			{
 				this.engineWorker = engineWorker;
+				this.nextChainElement = nextChainElement;
 			}
 			
-			public LoggerVerbosity Verbosity { get; set; }
-			public string Parameters { get; set; }
-			
-			IEventSource eventSource;
-			
-			public void Initialize(IEventSource eventSource)
-			{
-				this.eventSource = eventSource;
-				engineWorker.OutputText(StringParser.Parse("${res:ICSharpCode.CodeAnalysis.RunningFxCopOn} " + Path.GetFileNameWithoutExtension(engineWorker.CurrentProjectFile)));
-				eventSource.ErrorRaised += OnError;
-				eventSource.WarningRaised += OnWarning;
-			}
-			
-			public void Shutdown()
-			{
-				if (eventSource != null) {
-					eventSource.ErrorRaised -= OnError;
-					eventSource.WarningRaised -= OnWarning;
-					eventSource = null;
-				}
-			}
-			
-			void OnError(object sender, BuildErrorEventArgs e)
+			public void HandleError(BuildError error)
 			{
-				AppendError(e.File, e.LineNumber, e.ColumnNumber, e.Message, false,
-				            e.HelpKeyword, e.Code, e.Subcategory);
-			}
-			
-			void OnWarning(object sender, BuildWarningEventArgs e)
-			{
-				AppendError(e.File, e.LineNumber, e.ColumnNumber, e.Message, true,
-				            e.HelpKeyword, e.Code, e.Subcategory);
-			}
-			
-			void AppendError(string file, int lineNumber, int columnNumber,
-			                 string message, bool isWarning,
-			                 string category, string checkId, string subcategory)
-			{
-				LoggingService.Debug("Got " + (isWarning ? "warning" : "error") + ":\n"
-				                     + "  file: " + file + "\n"
-				                     + "  line: " + lineNumber + ", col: " + columnNumber + "\n"
-				                     + "  message: " + message + "\n"
-				                     + "  category: " + category + "\n"
-				                     + "  checkId: " + checkId + "\n"
-				                     + "  subcategory: " + subcategory);
+				LoggingService.Debug("FxCopLogger got " + error.ToString());
 				
-				string[] moreData = (subcategory ?? "").Split('|');
-				BuildError err = engineWorker.CurrentErrorOrWarning;
-				err.ErrorCode = (checkId != null) ? checkId.Split(':')[0] : null;
-				if (FileUtility.IsValidPath(file) &&
-				    Path.GetFileName(file) == "SharpDevelop.CodeAnalysis.targets")
+				string[] moreData = (error.Subcategory ?? "").Split('|');
+				string checkId = error.ErrorCode;
+				error.ErrorCode = (error.ErrorCode != null) ? error.ErrorCode.Split(':')[0] : null;
+				if (FileUtility.IsValidPath(error.FileName) &&
+				    Path.GetFileName(error.FileName) == "SharpDevelop.CodeAnalysis.targets")
 				{
-					err.FileName = null;
+					error.FileName = null;
 				}
-				IProject project = ProjectService.GetProject(engineWorker.CurrentProjectFile);
+				IProject project = ProjectService.GetProject(engineWorker.ProjectFileName);
 				if (project != null) {
 					IProjectContent pc = ParserService.GetProjectContent(project);
 					if (pc != null) {
-						if (file.StartsWith("positionof#")) {
-							string memberName = file.Substring(11);
-							file = "";
+						if (error.FileName != null && error.FileName.StartsWith("positionof#")) {
+							string memberName = error.FileName.Substring(11);
 							FilePosition pos = GetPosition(pc, memberName);
 							if (pos.IsEmpty == false && pos.CompilationUnit != null) {
-								err.FileName = pos.FileName ?? "";
-								err.Line = pos.Line;
-								err.Column = pos.Column;
+								error.FileName = pos.FileName ?? "";
+								error.Line = pos.Line;
+								error.Column = pos.Column;
 							} else {
-								err.FileName = null;
+								error.FileName = null;
 							}
 						}
 						
 						if (moreData.Length > 1 && !string.IsNullOrEmpty(moreData[0])) {
-							err.Tag = new FxCopTaskTag {
+							error.Tag = new FxCopTaskTag {
 								ProjectContent = pc,
 								TypeName = moreData[0],
 								MemberName = moreData[1],
-								Category = category,
+								Category = error.HelpKeyword,
 								CheckID = checkId
 							};
 						} else {
-							err.Tag = new FxCopTaskTag {
+							error.Tag = new FxCopTaskTag {
 								ProjectContent = pc,
-								Category = category,
+								Category = error.HelpKeyword,
 								CheckID = checkId
 							};
 						}
-						err.ContextMenuAddInTreeEntry = "/SharpDevelop/Pads/ErrorList/CodeAnalysisTaskContextMenu";
+						error.ContextMenuAddInTreeEntry = "/SharpDevelop/Pads/ErrorList/CodeAnalysisTaskContextMenu";
 						if (moreData.Length > 2) {
-							(err.Tag as FxCopTaskTag).MessageID = moreData[2];
+							(error.Tag as FxCopTaskTag).MessageID = moreData[2];
 						}
 					}
 				}
+				nextChainElement.HandleError(error);
+			}
+			
+			public void HandleBuildEvent(Microsoft.Build.Framework.BuildEventArgs e)
+			{
+				nextChainElement.HandleBuildEvent(e);
 			}
 			
 			static FilePosition GetPosition(IProjectContent pc, string memberName)
diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
index 7859a48b49..c97c7afcdc 100644
--- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
+++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
@@ -260,6 +260,8 @@
     <Compile Include="Src\Project\Converter\UpgradeViewContent.cs" />
     <Compile Include="Src\Project\IBuildFeedbackSink.cs" />
     <Compile Include="Src\Project\IProjectItemBackendStore.cs" />
+    <Compile Include="Src\Project\MSBuildEngine\MSBuildAdditionalLogger.cs" />
+    <Compile Include="Src\Project\MSBuildEngine\MSBuildLoggerFilter.cs" />
     <Compile Include="Src\Project\MSBuildEngine\SDConsoleLogger.cs" />
     <Compile Include="Src\Project\MSBuildEngine\BuildWorkerManager.cs" />
     <Compile Include="Src\Project\MSBuildEngine\MSBuildEngine.cs" />
@@ -711,7 +713,6 @@
     <Compile Include="Src\TextEditor\Gui\Editor\TextNavigationPoint.cs" />
     <Compile Include="Src\Project\BuildResults.cs" />
     <Compile Include="Src\Project\BuildError.cs" />
-    <Compile Include="Src\Project\MSBuildAdditionalLogger.cs" />
     <Compile Include="Src\Services\HelpProvider.cs" />
     <Compile Include="Src\Services\ParserService\CodeCompletionOptions.cs" />
     <Compile Include="Src\Services\RefactoringService\TextEditorDocument.cs" />
diff --git a/src/Main/Base/Project/Src/Project/BuildEngine.cs b/src/Main/Base/Project/Src/Project/BuildEngine.cs
index e356387759..9108095798 100644
--- a/src/Main/Base/Project/Src/Project/BuildEngine.cs
+++ b/src/Main/Base/Project/Src/Project/BuildEngine.cs
@@ -45,7 +45,9 @@ namespace ICSharpCode.SharpDevelop.Project
 			if (guiBuildCancellation != null) {
 				BuildResults results = new BuildResults();
 				StatusBarService.ShowErrorMessage(Core.ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning"));
-				results.Add(new BuildError(null, Core.ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning")));
+				BuildError error = new BuildError(null, Core.ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning"));
+				results.Add(error);
+				TaskService.Add(new Task(error));
 				results.Result = BuildResultCode.MSBuildAlreadyRunning;
 				if (options.Callback != null) {
 					options.Callback(results);
diff --git a/src/Main/Base/Project/Src/Project/BuildError.cs b/src/Main/Base/Project/Src/Project/BuildError.cs
index 27d231b666..a80e3b6fe8 100644
--- a/src/Main/Base/Project/Src/Project/BuildError.cs
+++ b/src/Main/Base/Project/Src/Project/BuildError.cs
@@ -50,6 +50,18 @@ namespace ICSharpCode.SharpDevelop.Project
 		[NonSerialized]
 		object tag;
 		string contextMenuAddInTreeEntry;
+		string subcategory;
+		string helpKeyword;
+		
+		public string HelpKeyword {
+			get { return helpKeyword; }
+			set { helpKeyword = value; }
+		}
+		
+		public string Subcategory {
+			get { return subcategory; }
+			set { subcategory = value; }
+		}
 		
 		public int Column {
 			get {
diff --git a/src/Main/Base/Project/Src/Project/MSBuildEngine/BuildWorkerManager.cs b/src/Main/Base/Project/Src/Project/MSBuildEngine/BuildWorkerManager.cs
index e309db5228..ab51025e8e 100644
--- a/src/Main/Base/Project/Src/Project/MSBuildEngine/BuildWorkerManager.cs
+++ b/src/Main/Base/Project/Src/Project/MSBuildEngine/BuildWorkerManager.cs
@@ -31,10 +31,10 @@ namespace ICSharpCode.SharpDevelop.Project
 			this.workerProcessName = workerProcessName;
 		}
 		
-		public void RunBuildJob(BuildJob job, IEnumerable<ILogger> loggers, IBuildFeedbackSink reportWhenDone)
+		public void RunBuildJob(BuildJob job, IMSBuildChainedLoggerFilter loggerChain, Action<bool> reportWhenDone, CancellationToken cancellationToken)
 		{
 			BuildWorker worker = GetFreeWorker();
-			worker.RunJob(job, loggers, reportWhenDone);
+			worker.RunJob(job, loggerChain, reportWhenDone, cancellationToken);
 		}
 		
 		BuildWorker GetFreeWorker()
@@ -70,26 +70,21 @@ namespace ICSharpCode.SharpDevelop.Project
 				process.Start(startInfo);
 			}
 
-			EventSource source;
-			IEnumerable<ILogger> loggers;
-			IBuildFeedbackSink reportWhenDone;
+			IMSBuildChainedLoggerFilter loggerChain;
+			Action<bool> reportWhenDone;
 			CancellationTokenRegistration cancellationRegistration;
 			
-			public void RunJob(BuildJob job, IEnumerable<ILogger> loggers, IBuildFeedbackSink reportWhenDone)
+			public void RunJob(BuildJob job, IMSBuildChainedLoggerFilter loggerChain, Action<bool> reportWhenDone, CancellationToken cancellationToken)
 			{
-				this.source = new EventSource();
-				this.loggers = loggers;
+				this.loggerChain = loggerChain;
 				this.reportWhenDone = reportWhenDone;
-				foreach (var logger in loggers) {
-					logger.Initialize(source);
-				}
 				try {
 					process.Writer.Write("StartBuild");
 					job.WriteTo(process.Writer);
-					this.cancellationRegistration = reportWhenDone.ProgressMonitor.CancellationToken.Register(OnCancel);
+					this.cancellationRegistration = cancellationToken.Register(OnCancel);
 				} catch (IOException ex) {
 					// "Pipe is broken"
-					source.ForwardEvent(new BuildErrorEventArgs(null, null, null, 0, 0, 0, 0, "Error talking to build worker: " + ex.Message, null, null));
+					loggerChain.HandleError(new BuildError(null, 0, 0, null, "Error talking to build worker: " + ex.Message));
 					BuildDone(false);
 				}
 			}
@@ -105,7 +100,7 @@ namespace ICSharpCode.SharpDevelop.Project
 				LoggingService.Debug("Received command " + command);
 				switch (command) {
 					case "ReportEvent":
-						source.ForwardEvent(EventSource.DecodeEvent(reader));
+						loggerChain.HandleBuildEvent(EventSource.DecodeEvent(reader));
 						break;
 					case "BuildDone":
 						bool success = reader.ReadBoolean();
@@ -127,10 +122,7 @@ namespace ICSharpCode.SharpDevelop.Project
 					if (reportWhenDone == null)
 						return;
 					cancellationRegistration.Dispose();
-					foreach (var logger in loggers) {
-						logger.Shutdown();
-					}
-					reportWhenDone.Done(success);
+					reportWhenDone(success);
 					reportWhenDone = null;
 				}
 			}
@@ -163,11 +155,11 @@ namespace ICSharpCode.SharpDevelop.Project
 						parentManager.freeWorkers.Remove(this);
 						MarkAsInUse();
 					} else {
-						reportBuildError = true;
+						reportBuildError = (reportWhenDone != null); // only if not done
 					}
 				}
 				if (reportBuildError) {
-					source.ForwardEvent(new BuildErrorEventArgs(null, null, null, 0, 0, 0, 0, "Build worker process exited unexpectedly.", null, null));
+					loggerChain.HandleError(new BuildError(null, 0, 0, null, "Build worker process exited unexpectedly."));
 					BuildDone(false);
 				}
 				process.Dispose();
diff --git a/src/Main/Base/Project/Src/Project/MSBuildAdditionalLogger.cs b/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildAdditionalLogger.cs
similarity index 100%
rename from src/Main/Base/Project/Src/Project/MSBuildAdditionalLogger.cs
rename to src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildAdditionalLogger.cs
diff --git a/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildEngine.cs b/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildEngine.cs
index 4b17ad49d1..41a052c885 100755
--- a/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildEngine.cs
+++ b/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildEngine.cs
@@ -34,6 +34,7 @@ namespace ICSharpCode.SharpDevelop.Project
 		const string CompileTaskNamesPath = "/SharpDevelop/MSBuildEngine/CompileTaskNames";
 		const string AdditionalTargetFilesPath = "/SharpDevelop/MSBuildEngine/AdditionalTargetFiles";
 		const string AdditionalLoggersPath = "/SharpDevelop/MSBuildEngine/AdditionalLoggers";
+		const string LoggerFiltersPath = "/SharpDevelop/MSBuildEngine/LoggerFilters";
 		internal const string AdditionalPropertiesPath = "/SharpDevelop/MSBuildEngine/AdditionalProperties";
 		
 		/// <summary>
@@ -75,6 +76,13 @@ namespace ICSharpCode.SharpDevelop.Project
 		/// </summary>
 		public static readonly IList<IMSBuildAdditionalLogger> AdditionalMSBuildLoggers;
 		
+		/// <summary>
+		/// Gets a list of MSBuild logger filter.
+		/// You can register your loggers by putting them into
+		/// "/SharpDevelop/MSBuildEngine/LoggerFilters"
+		/// </summary>
+		public static readonly IList<IMSBuildLoggerFilter> MSBuildLoggerFilters;
+		
 		public static string SharpDevelopBinPath {
 			get {
 				return Path.GetDirectoryName(typeof(MSBuildEngine).Assembly.Location);
@@ -89,6 +97,7 @@ namespace ICSharpCode.SharpDevelop.Project
 			);
 			AdditionalTargetFiles = AddInTree.BuildItems<string>(AdditionalTargetFilesPath, null, false);
 			AdditionalMSBuildLoggers = AddInTree.BuildItems<IMSBuildAdditionalLogger>(AdditionalLoggersPath, null, false);
+			MSBuildLoggerFilters = AddInTree.BuildItems<IMSBuildLoggerFilter>(LoggerFiltersPath, null, false);
 		}
 		
 		public static void StartBuild(IProject project, ThreadSafeServiceContainer serviceContainer, ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IEnumerable<string> additionalTargetFiles)
@@ -110,7 +119,8 @@ namespace ICSharpCode.SharpDevelop.Project
 			engine.StartBuild();
 		}
 		
-		IProject project;
+		readonly string projectFileName;
+		readonly int projectMinimumSolutionVersion;
 		ProjectBuildOptions options;
 		IBuildFeedbackSink feedbackSink;
 		IEnumerable<string> additionalTargetFiles;
@@ -118,7 +128,8 @@ namespace ICSharpCode.SharpDevelop.Project
 		
 		private MSBuildEngine(IProject project, ProjectBuildOptions options, IBuildFeedbackSink feedbackSink)
 		{
-			this.project = project;
+			this.projectFileName = project.FileName;
+			this.projectMinimumSolutionVersion = project.MinimumSolutionVersion;
 			this.options = options;
 			this.feedbackSink = feedbackSink;
 		}
@@ -163,6 +174,13 @@ namespace ICSharpCode.SharpDevelop.Project
 		/// </summary>
 		public bool ReportUnknownEvents { get; set; }
 		
+		/// <summary>
+		/// Gets the name of the project file being compiled by this engine.
+		/// </summary>
+		public string ProjectFileName {
+			get { return projectFileName; }
+		}
+		
 		List<string> interestingTasks = new List<string>();
 		string temporaryFileName;
 		
@@ -175,6 +193,10 @@ namespace ICSharpCode.SharpDevelop.Project
 			get { return interestingTasks; }
 		}
 		
+		readonly EventSource eventSource = new EventSource();
+		List<ILogger> loggers = new List<ILogger>();
+		IMSBuildChainedLoggerFilter loggerChain;
+		
 		void StartBuild()
 		{
 			Dictionary<string, string> globalProperties = new Dictionary<string, string>();
@@ -192,7 +214,6 @@ namespace ICSharpCode.SharpDevelop.Project
 			
 			InterestingTasks.AddRange(MSBuildEngine.CompileTaskNames);
 			
-			List<ILogger> loggers = new List<ILogger>();
 			loggers.Add(new SharpDevelopLogger(this));
 			if (options.BuildOutputVerbosity == BuildOutputVerbosity.Diagnostic) {
 				this.ReportMessageEvents = true;
@@ -207,10 +228,16 @@ namespace ICSharpCode.SharpDevelop.Project
 			foreach (IMSBuildAdditionalLogger loggerProvider in MSBuildEngine.AdditionalMSBuildLoggers) {
 				loggers.Add(loggerProvider.CreateLogger(this));
 			}
+			
+			loggerChain = new EndOfChain(this);
+			foreach (IMSBuildLoggerFilter loggerFilter in MSBuildEngine.MSBuildLoggerFilters) {
+				loggerChain = loggerFilter.CreateFilter(this, loggerChain) ?? loggerChain;
+			}
+			
 			WriteAdditionalTargetsToTempFile(globalProperties);
 			
 			BuildJob job = new BuildJob();
-			job.ProjectFileName = project.FileName;
+			job.ProjectFileName = projectFileName;
 			job.Target = options.Target.TargetName;
 			
 			// First remove the flags for the controllable events.
@@ -237,13 +264,25 @@ namespace ICSharpCode.SharpDevelop.Project
 				job.Properties.Add(pair.Key, pair.Value);
 			}
 			
-			if (project.MinimumSolutionVersion <= Solution.SolutionVersionVS2008) {
-				BuildWorkerManager.MSBuild35.RunBuildJob(job, loggers, feedbackSink);
+			foreach (ILogger logger in loggers) {
+				logger.Initialize(eventSource);
+			}
+			
+			if (projectMinimumSolutionVersion <= Solution.SolutionVersionVS2008) {
+				BuildWorkerManager.MSBuild35.RunBuildJob(job, loggerChain, OnDone, feedbackSink.ProgressMonitor.CancellationToken);
 			} else {
-				BuildWorkerManager.MSBuild40.RunBuildJob(job, loggers, feedbackSink);
+				BuildWorkerManager.MSBuild40.RunBuildJob(job, loggerChain, OnDone, feedbackSink.ProgressMonitor.CancellationToken);
 			}
 		}
 		
+		void OnDone(bool success)
+		{
+			foreach (ILogger logger in loggers) {
+				logger.Shutdown();
+			}
+			feedbackSink.Done(success);
+		}
+		
 		void WriteAdditionalTargetsToTempFile(Dictionary<string, string> globalProperties)
 		{
 			// Using projects with in-memory modifications doesn't work with parallel build.
@@ -273,7 +312,7 @@ namespace ICSharpCode.SharpDevelop.Project
 				// 'MsTestToolsTargets' is preferred because it's at the end of the MSBuild 3.5 and 4.0 target file,
 				// but on MSBuild 2.0 we need to fall back to 'CodeAnalysisTargets'.
 				string hijackedProperty = "MsTestToolsTargets";
-				if (project.MinimumSolutionVersion == Solution.SolutionVersionVS2005)
+				if (projectMinimumSolutionVersion == Solution.SolutionVersionVS2005)
 					hijackedProperty = "CodeAnalysisTargets";
 				
 				// because we'll replace the hijackedProperty, manually write the corresponding include
@@ -295,33 +334,7 @@ namespace ICSharpCode.SharpDevelop.Project
 			#endif
 		}
 		
-		BuildError currentErrorOrWarning;
-		
-		/// <summary>
-		/// Gets the last build error/warning created by the default
-		/// SharpDevelop logger.
-		/// </summary>
-		public BuildError CurrentErrorOrWarning {
-			get {
-				return currentErrorOrWarning;
-			}
-		}
-		
-		Stack<string> projectFiles = new Stack<string>();
-		
-		/// <summary>
-		/// Gets the name of the currently building project file.
-		/// </summary>
-		public string CurrentProjectFile {
-			get {
-				if (projectFiles.Count == 0)
-					return null;
-				else
-					return projectFiles.Peek();
-			}
-		}
-		
-		public void OutputText(string message)
+		public void OutputTextLine(string message)
 		{
 			feedbackSink.ReportMessage(message);
 		}
@@ -331,40 +344,33 @@ namespace ICSharpCode.SharpDevelop.Project
 			feedbackSink.ReportError(error);
 		}
 		
-		class SharpDevelopLogger : ILogger
+		sealed class EndOfChain : IMSBuildChainedLoggerFilter
 		{
-			MSBuildEngine worker;
-			
-			public SharpDevelopLogger(MSBuildEngine engine)
-			{
-				this.worker = engine;
-			}
+			readonly MSBuildEngine engine;
 			
-			void AppendLine(string text)
+			public EndOfChain(MSBuildEngine engine)
 			{
-				worker.OutputText(text);
+				this.engine = engine;
 			}
 			
-			internal void FlushCurrentError()
+			public void HandleError(BuildError error)
 			{
-				if (worker.currentErrorOrWarning != null) {
-					worker.ReportError(worker.currentErrorOrWarning);
-					worker.currentErrorOrWarning = null;
-				}
+				engine.ReportError(error);
 			}
 			
-			void OnProjectStarted(object sender, ProjectStartedEventArgs e)
+			public void HandleBuildEvent(Microsoft.Build.Framework.BuildEventArgs e)
 			{
-				worker.projectFiles.Push(e.ProjectFile);
+				engine.eventSource.ForwardEvent(e);
 			}
+		}
+		
+		sealed class SharpDevelopLogger : ILogger
+		{
+			MSBuildEngine engine;
 			
-			void OnProjectFinished(object sender, ProjectFinishedEventArgs e)
+			public SharpDevelopLogger(MSBuildEngine engine)
 			{
-				FlushCurrentError();
-				// it's possible that MSBuild raises ProjectFinished without a matching
-				// ProjectStarted - e.g. if an additional import is missing
-				if (worker.projectFiles.Count > 0)
-					worker.projectFiles.Pop();
+				this.engine = engine;
 			}
 			
 			string activeTaskName;
@@ -373,34 +379,28 @@ namespace ICSharpCode.SharpDevelop.Project
 			{
 				activeTaskName = e.TaskName;
 				if (MSBuildEngine.CompileTaskNames.Contains(e.TaskName.ToLowerInvariant())) {
-					AppendLine(StringParser.Parse("${res:MainWindow.CompilerMessages.CompileVerb} " + Path.GetFileNameWithoutExtension(e.ProjectFile)));
+					engine.OutputTextLine(StringParser.Parse("${res:MainWindow.CompilerMessages.CompileVerb} " + Path.GetFileNameWithoutExtension(e.ProjectFile)));
 				}
 			}
 			
-			void OnTaskFinished(object sender, TaskFinishedEventArgs e)
-			{
-				FlushCurrentError();
-			}
-			
 			void OnError(object sender, BuildErrorEventArgs e)
 			{
-				AppendError(e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, false);
+				AppendError(e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile, e.Subcategory, e.HelpKeyword, false);
 			}
 			
 			void OnWarning(object sender, BuildWarningEventArgs e)
 			{
-				AppendError(e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, true);
+				AppendError(e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile, e.Subcategory, e.HelpKeyword, true);
 			}
 			
-			// TODO: Add XmlDocBloc to MSBuildError.AppendError()
-			void AppendError(string file, int lineNumber, int columnNumber, string code, string message, bool isWarning)
+			void AppendError(string file, int lineNumber, int columnNumber, string code, string message, string projectFile, string subcategory, string helpKeyword, bool isWarning)
 			{
 				if (string.Equals(file, activeTaskName, StringComparison.OrdinalIgnoreCase)) {
 					file = "";
 				} else if (FileUtility.IsValidPath(file)) {
 					bool isShortFileName = file == Path.GetFileNameWithoutExtension(file);
-					if (worker.CurrentProjectFile != null) {
-						file = Path.Combine(Path.GetDirectoryName(worker.CurrentProjectFile), file);
+					if (projectFile != null) {
+						file = Path.Combine(Path.GetDirectoryName(projectFile), file);
 					}
 					if (isShortFileName && !File.Exists(file)) {
 						file = "";
@@ -413,10 +413,11 @@ namespace ICSharpCode.SharpDevelop.Project
 						file = "";
 					}
 				}
-				FlushCurrentError();
 				BuildError error = new BuildError(file, lineNumber, columnNumber, code, message);
 				error.IsWarning = isWarning;
-				worker.currentErrorOrWarning = error;
+				error.Subcategory = subcategory;
+				error.HelpKeyword = helpKeyword;
+				engine.loggerChain.HandleError(error);
 			}
 			
 			#region ILogger interface implementation
@@ -425,10 +426,7 @@ namespace ICSharpCode.SharpDevelop.Project
 			
 			public void Initialize(IEventSource eventSource)
 			{
-				eventSource.ProjectStarted  += OnProjectStarted;
-				eventSource.ProjectFinished += OnProjectFinished;
 				eventSource.TaskStarted     += OnTaskStarted;
-				eventSource.TaskFinished    += OnTaskFinished;
 				
 				eventSource.ErrorRaised     += OnError;
 				eventSource.WarningRaised   += OnWarning;
@@ -436,10 +434,9 @@ namespace ICSharpCode.SharpDevelop.Project
 			
 			public void Shutdown()
 			{
-				FlushCurrentError();
-				if (worker.temporaryFileName != null) {
-					File.Delete(worker.temporaryFileName);
-					worker.temporaryFileName = null;
+				if (engine.temporaryFileName != null) {
+					File.Delete(engine.temporaryFileName);
+					engine.temporaryFileName = null;
 				}
 			}
 			#endregion
diff --git a/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildLoggerFilter.cs b/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildLoggerFilter.cs
new file mode 100644
index 0000000000..63487e9701
--- /dev/null
+++ b/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildLoggerFilter.cs
@@ -0,0 +1,136 @@
+// <file>
+//     <copyright see="prj:///doc/copyright.txt"/>
+//     <license see="prj:///doc/license.txt"/>
+//     <author name="Daniel Grunwald"/>
+//     <version>$Revision$</version>
+// </file>
+
+using System;
+using ICSharpCode.Core;
+using Microsoft.Build.Framework;
+
+namespace ICSharpCode.SharpDevelop.Project
+{
+	/// <summary>
+	/// Interface for elements in /SharpDevelop/MSBuildEngine/LoggerFilters
+	/// </summary>
+	public interface IMSBuildLoggerFilter
+	{
+		IMSBuildChainedLoggerFilter CreateFilter(MSBuildEngine engine, IMSBuildChainedLoggerFilter nextFilter);
+	}
+	
+	/// <summary>
+	/// Element in the logger filter chain.
+	/// Receives build events and errors and forwards them to the next element in the chain (possibly after modifying the event).
+	/// </summary>
+	public interface IMSBuildChainedLoggerFilter
+	{
+		void HandleError(BuildError error);
+		void HandleBuildEvent(Microsoft.Build.Framework.BuildEventArgs e);
+	}
+	
+	/// <summary>
+	/// Creates <see cref="IMSBuildLoggerFilter"/> objects that are only
+	/// activated when a specific MSBuild task is running.
+	/// </summary>
+	/// <attribute name="class" use="required">
+	/// Name of the IMSBuildLoggerFilter class.
+	/// </attribute>
+	/// <attribute name="taskname" use="required">
+	/// Specifies the name of the MSBuild task that must be running for
+	/// this logger to be active.
+	/// </attribute>
+	/// <example>
+	/// &lt;TaskBoundLoggerFilter
+	/// 	id = "FxCopLogger"
+	/// 	taskname = "FxCop"
+	/// 	class = "ICSharpCode.CodeAnalysis.FxCopLoggerFilter"/&gt;
+	/// </example>
+	/// <usage>Only in /SharpDevelop/MSBuildEngine/LoggerFilters</usage>
+	/// <returns>
+	/// A IMSBuildLoggerFilter object that lazy-loads the specified
+	/// IMSBuildLoggerFilter when the specified task is running.
+	/// </returns>
+	public class TaskBoundLoggerFilterDoozer : IDoozer
+	{
+		public bool HandleConditions {
+			get {
+				return false;
+			}
+		}
+		
+		public object BuildItem(object caller, Codon codon, System.Collections.ArrayList subItems)
+		{
+			return new TaskBoundLoggerFilterDescriptor(codon);
+		}
+		
+		sealed class TaskBoundLoggerFilterDescriptor : IMSBuildLoggerFilter
+		{
+			internal string taskname;
+			internal string classname;
+			internal AddIn addIn;
+			
+			public TaskBoundLoggerFilterDescriptor(Codon codon)
+			{
+				classname = codon.Properties["class"];
+				taskname = codon.Properties["taskname"];
+				addIn = codon.AddIn;
+			}
+			
+			public IMSBuildChainedLoggerFilter CreateFilter(MSBuildEngine engine, IMSBuildChainedLoggerFilter nextFilter)
+			{
+				if (nextFilter == null)
+					throw new ArgumentNullException("nextFilter");
+				// Create a Filter that tracks whether the task is active.
+				// If active, forward to 'baseFilter', otherwise forward to 'nextFilter'.
+				return new TaskBoundLoggerFilter(this, engine, nextFilter);
+			}
+		}
+		
+		sealed class TaskBoundLoggerFilter : IMSBuildChainedLoggerFilter
+		{
+			readonly TaskBoundLoggerFilterDescriptor desc;
+			readonly MSBuildEngine engine;
+			readonly IMSBuildChainedLoggerFilter nextFilter;
+			IMSBuildChainedLoggerFilter baseFilter = null;
+			bool insideTask = false;
+			
+			public TaskBoundLoggerFilter(TaskBoundLoggerFilterDescriptor desc, MSBuildEngine engine, IMSBuildChainedLoggerFilter nextFilter)
+			{
+				this.desc = desc;
+				this.engine = engine;
+				this.nextFilter = nextFilter;
+			}
+			
+			public void HandleError(BuildError error)
+			{
+				if (insideTask)
+					baseFilter.HandleError(error);
+				else
+					nextFilter.HandleError(error);
+			}
+			
+			public void HandleBuildEvent(Microsoft.Build.Framework.BuildEventArgs e)
+			{
+				TaskStartedEventArgs start = e as TaskStartedEventArgs;
+				if (start != null && string.Equals(start.TaskName, desc.taskname, StringComparison.OrdinalIgnoreCase)) {
+					insideTask = true;
+					if (baseFilter == null) {
+						IMSBuildLoggerFilter baseLoggerFilter = (IMSBuildLoggerFilter)desc.addIn.CreateObject(desc.classname);
+						if (baseLoggerFilter != null)
+							baseFilter = baseLoggerFilter.CreateFilter(engine, nextFilter) ?? nextFilter;
+						else
+							baseFilter = nextFilter;
+					}
+				}
+				if (insideTask)
+					baseFilter.HandleBuildEvent(e);
+				else
+					nextFilter.HandleBuildEvent(e);
+				if (insideTask && e is TaskFinishedEventArgs) {
+					insideTask = false;
+				}
+			}
+		}
+	}
+}
diff --git a/src/Main/ICSharpCode.Core.Presentation/NumericUpDown.cs b/src/Main/ICSharpCode.Core.Presentation/NumericUpDown.cs
index bb230cd528..906fe46265 100644
--- a/src/Main/ICSharpCode.Core.Presentation/NumericUpDown.cs
+++ b/src/Main/ICSharpCode.Core.Presentation/NumericUpDown.cs
@@ -31,7 +31,7 @@ namespace ICSharpCode.Core.Presentation
 			DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
 				new FrameworkPropertyMetadata(typeof(NumericUpDown)));
 		}
-
+		
 		TextBox textBox;
 		DragRepeatButton upButton;
 		DragRepeatButton downButton;