diff --git a/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs b/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs
index 89e145afe8..185bb18c56 100644
--- a/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs
+++ b/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs
@@ -215,7 +215,7 @@ namespace ICSharpCode.UnitTesting
TaskService.ClearExceptCommentTasks();
TaskService.InUpdate = false;
- TestRunnerCategory.ClearText();
+ TestRunnerCategory.ClearText();
ShowUnitTestsPad();
ShowOutputPad();
@@ -225,7 +225,7 @@ namespace ICSharpCode.UnitTesting
OnBeforeRunTests();
}
-
+
///
/// Brings output pad to the front.
///
@@ -252,7 +252,7 @@ namespace ICSharpCode.UnitTesting
}
if (lineRef != null) {
return new Task(Path.GetFullPath(lineRef.FileName),
- message, lineRef.Column, lineRef.Line, taskType);
+ message, lineRef.Column, lineRef.Line, taskType);
}
return new Task(String.Empty, message, 0, 0, taskType);
}
@@ -297,7 +297,7 @@ namespace ICSharpCode.UnitTesting
///
void OnBuildComplete(BuildResults results, IProject project, string namespaceFilter, IClass fixture, IMember test)
{
- if (results.ErrorCount == 0 && IsRunningTest) {
+ if (results.ErrorCount == 0 && IsRunningTest) {
UnitTestApplicationStartHelper helper = new UnitTestApplicationStartHelper();
UnitTestingOptions options = new UnitTestingOptions();
@@ -422,7 +422,7 @@ namespace ICSharpCode.UnitTesting
ProjectService.RaiseEventEndBuild(new BuildEventArgs(LastBuildResults));
}
}
-
+
public class RunTestInPadCommand : AbstractRunTestCommand
{
ProcessRunner runner;
@@ -430,12 +430,13 @@ namespace ICSharpCode.UnitTesting
public RunTestInPadCommand()
{
runner = new ProcessRunner();
+ runner.LogStandardOutputAndError = false;
runner.OutputLineReceived += OutputLineReceived;
runner.ProcessExited += ProcessExited;
}
-
+
protected override void RunTests(UnitTestApplicationStartHelper helper)
- {
+ {
TestRunnerCategory.AppendLine(helper.GetCommandLine());
runner.Start(helper.UnitTestApplication, helper.GetArguments());
}
@@ -456,7 +457,7 @@ namespace ICSharpCode.UnitTesting
}
void ProcessExited(object source, EventArgs e)
- {
+ {
WorkbenchSingleton.SafeThreadAsyncCall(TestsFinished);
}
@@ -472,7 +473,7 @@ namespace ICSharpCode.UnitTesting
{
if (DebuggerService.IsDebuggerLoaded && DebuggerService.CurrentDebugger.IsDebugging) {
if (MessageService.AskQuestion("${res:XML.MainMenu.RunMenu.Compile.StopDebuggingQuestion}",
- "${res:XML.MainMenu.RunMenu.Compile.StopDebuggingTitle}"))
+ "${res:XML.MainMenu.RunMenu.Compile.StopDebuggingTitle}"))
{
DebuggerService.CurrentDebugger.Stop();
base.Run();
@@ -481,7 +482,7 @@ namespace ICSharpCode.UnitTesting
base.Run();
}
}
-
+
protected override void RunTests(UnitTestApplicationStartHelper helper)
{
bool running = false;
diff --git a/src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs b/src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs
index cd10c57e33..67fe1f3343 100644
--- a/src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs
+++ b/src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs
@@ -256,6 +256,7 @@ namespace ICSharpCode.SharpDevelop.Commands
try {
if (tool.UseOutputPad) {
ProcessRunner processRunner = new ProcessRunner();
+ processRunner.LogStandardOutputAndError = false;
processRunner.ProcessExited += ProcessExitEvent;
processRunner.OutputLineReceived += process_OutputLineReceived;
processRunner.ErrorLineReceived += process_OutputLineReceived;
@@ -289,7 +290,7 @@ namespace ICSharpCode.SharpDevelop.Commands
WorkbenchSingleton.SafeThreadAsyncCall(
delegate {
ProcessRunner p = (ProcessRunner)sender;
- TaskService.BuildMessageViewCategory.AppendLine("${res:XML.MainMenu.ToolMenu.ExternalTools.ExitedWithCode} " + p.ExitCode);
+ TaskService.BuildMessageViewCategory.AppendLine(StringParser.Parse("${res:XML.MainMenu.ToolMenu.ExternalTools.ExitedWithCode} " + p.ExitCode));
p.Dispose();
});
}
diff --git a/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs b/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs
index 6482048b76..f3ef079c00 100644
--- a/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs
+++ b/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs
@@ -7,7 +7,9 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Drawing;
+using System.Text;
using System.Threading;
using System.Windows.Forms;
@@ -56,15 +58,29 @@ namespace ICSharpCode.SharpDevelop.Gui
return selectedCategory;
}
set {
+ WorkbenchSingleton.AssertMainThread();
if (selectedCategory != value) {
selectedCategory = value;
- textEditorControl.Text = (value < 0) ? "" : StringParser.Parse(messageCategories[value].Text);
- //textEditorControl.Refresh();
+ DisplayActiveCategory();
OnSelectedCategoryIndexChanged(EventArgs.Empty);
}
}
}
+ void DisplayActiveCategory()
+ {
+ WorkbenchSingleton.DebugAssertMainThread();
+ if (selectedCategory < 0) {
+ textEditorControl.Text = "";
+ } else {
+ lock (messageCategories[selectedCategory].SyncRoot) {
+ // accessing a categories' text takes its lock - but we have to take locks in the same
+ // order as in the Append calls to prevent a deadlock
+ EnqueueAppend(new AppendCall(messageCategories[selectedCategory], messageCategories[selectedCategory].Text, true));
+ }
+ }
+ }
+
public bool WordWrap {
get {
return properties.Get("WordWrap", true);
@@ -136,19 +152,17 @@ namespace ICSharpCode.SharpDevelop.Gui
SetWordWrap();
myPanel.ResumeLayout(false);
- SetText(messageCategories[selectedCategory], messageCategories[selectedCategory].Text);
+ DisplayActiveCategory();
ProjectService.SolutionLoaded += SolutionLoaded;
}
void SolutionLoaded(object sender, SolutionEventArgs e)
{
- foreach (MessageViewCategory category in messageCategories)
- {
- ClearText(category);
+ foreach (MessageViewCategory category in messageCategories) {
+ category.ClearText();
}
}
-
void SetWordWrap()
{
bool wordWrap = this.WordWrap;
@@ -179,105 +193,111 @@ namespace ICSharpCode.SharpDevelop.Gui
return;
}
messageCategories.Add(category);
- category.Cleared += new EventHandler(CategoryTextCleared);
category.TextSet += new TextEventHandler(CategoryTextSet);
category.TextAppended += new TextEventHandler(CategoryTextAppended);
OnMessageCategoryAdded(EventArgs.Empty);
}
- void CategoryTextCleared(object sender, EventArgs e)
- {
- WorkbenchSingleton.SafeThreadAsyncCall(new Action(ClearText),
- (MessageViewCategory)sender);
- }
- void ClearText(MessageViewCategory category)
+ void CategoryTextSet(object sender, TextEventArgs e)
{
- if (messageCategories[SelectedCategoryIndex] == category) {
- textEditorControl.Text = String.Empty;
- //textEditorControl.Refresh();
- }
+ EnqueueAppend(new AppendCall((MessageViewCategory)sender, e.Text, true));
}
- void CategoryTextSet(object sender, TextEventArgs e)
+ struct AppendCall
{
- WorkbenchSingleton.SafeThreadAsyncCall(new Action(SetText),
- (MessageViewCategory)sender, e.Text);
+ internal readonly MessageViewCategory Category;
+ internal readonly string Text;
+ internal readonly bool ClearCategory;
+
+ public AppendCall(MessageViewCategory category, string text, bool clearCategory)
+ {
+ this.Category = category;
+ this.Text = text;
+ this.ClearCategory = clearCategory;
+ }
}
- object appendCallLock = new object();
- volatile int pendingAppendCalls = 0;
+ readonly object appendLock = new object();
+ List appendCalls = new List();
void CategoryTextAppended(object sender, TextEventArgs e)
{
- lock (appendCallLock) {
- pendingAppendCalls += 1;
- MessageViewCategory cat = (MessageViewCategory)sender;
- if (pendingAppendCalls < 5) {
- WorkbenchSingleton.SafeThreadAsyncCall(new Action(AppendText),
- cat, cat.Text, e.Text);
- } else if (pendingAppendCalls == 5) {
- WorkbenchSingleton.SafeThreadAsyncCall(new Action(AppendTextCombined),
- cat);
- }
- }
+ EnqueueAppend(new AppendCall((MessageViewCategory)sender, e.Text, false));
}
- void AppendTextCombined(MessageViewCategory category)
+ void EnqueueAppend(AppendCall appendCall)
{
- Application.DoEvents();
- Thread.Sleep(50);
- Application.DoEvents();
- lock (appendCallLock) {
- NativeMethods.SetWindowRedraw(textEditorControl.Handle, false);
- SetText(category, category.Text);
- NativeMethods.SetWindowRedraw(textEditorControl.Handle, true);
- textEditorControl.SelectionStart = textEditorControl.TextLength;
- if (LoggingService.IsDebugEnabled) {
- LoggingService.Debug("Replaced " + pendingAppendCalls + " appends with one set call");
- }
- pendingAppendCalls = 0;
- }
- textEditorControl.Refresh();
- }
-
- void AppendText(MessageViewCategory category, string fullText, string text)
+ bool waitForMainThread;
+ lock (appendLock) {
+ appendCalls.Add(appendCall);
+ if (appendCalls.Count == 1)
+ WorkbenchSingleton.SafeThreadAsyncCall(ProcessAppendText);
+ waitForMainThread = appendCalls.Count > 2000;
+ }
+ if (waitForMainThread && WorkbenchSingleton.InvokeRequired) {
+ int sleepLength = 20;
+ do {
+ Thread.Sleep(sleepLength);
+ sleepLength += 20;
+ lock (appendLock)
+ waitForMainThread = appendCalls.Count > 2000;
+ //if (waitForMainThread) LoggingService.Debug("Extending sleep (" + sleepLength + ")");
+ } while (waitForMainThread);
+ }
+ }
+
+ void ProcessAppendText()
{
- lock (appendCallLock) {
- if (pendingAppendCalls >= 5) {
- return;
- }
- pendingAppendCalls -= 1;
+ List appendCalls;
+ lock (appendLock) {
+ appendCalls = this.appendCalls;
+ this.appendCalls = new List();
}
- if (messageCategories[SelectedCategoryIndex] != category) {
- SelectCategory(category.Category, fullText);
+ Debug.Assert(appendCalls.Count > 0);
+ if (appendCalls.Count == 0)
return;
- }
- if (text != null) {
- text = StringParser.Parse(text);
- textEditorControl.AppendText(text);
- textEditorControl.SelectionStart = textEditorControl.TextLength;
- /*textEditorControl.Document.ReadOnly = false;
- textEditorControl.Document.Insert(textEditorControl.Document.TextLength, text);
- textEditorControl.Document.ReadOnly = true;
- textEditorControl.ActiveTextAreaControl.Caret.Position = new Point(0, textEditorControl.Document.TotalNumberOfLines);
- textEditorControl.ActiveTextAreaControl.ScrollTo(textEditorControl.Document.TotalNumberOfLines);*/
- }
- }
-
- void SetText(MessageViewCategory category, string text)
- {
- if (messageCategories[SelectedCategoryIndex] != category) {
- SelectCategory(category.Category);
+
+ MessageViewCategory newCategory = appendCalls[appendCalls.Count - 1].Category;
+ if (messageCategories[SelectedCategoryIndex] != newCategory) {
+ SelectCategory(newCategory.Category);
return;
}
- if (text == null) {
- text = String.Empty;
+
+ bool clear;
+ string text;
+ if (appendCalls.Count == 1) {
+ //LoggingService.Debug("CompilerMessageView: Single append.");
+ clear = appendCalls[0].ClearCategory;
+ text = appendCalls[0].Text;
+ } else {
+ if (LoggingService.IsDebugEnabled) {
+ LoggingService.Debug("CompilerMessageView: Combined " + appendCalls.Count + " appends.");
+ }
+
+ clear = false;
+ StringBuilder b = new StringBuilder();
+ foreach (AppendCall append in appendCalls) {
+ if (append.Category == newCategory) {
+ if (append.ClearCategory) {
+ b.Length = 0;
+ clear = true;
+ }
+ b.Append(append.Text);
+ }
+ }
+ text = b.ToString();
+ }
+
+ //NativeMethods.SetWindowRedraw(textEditorControl.Handle, false);
+ if (clear) {
+ textEditorControl.Text = text;
} else {
- text = StringParser.Parse(text);
+ textEditorControl.SelectionStart = textEditorControl.TextLength;
+ textEditorControl.SelectedText = text;
}
- textEditorControl.Text = text;
- //textEditorControl.Refresh();
+ //NativeMethods.SetWindowRedraw(textEditorControl.Handle, true);
+ textEditorControl.SelectionStart = textEditorControl.TextLength;
}
public void SelectCategory(string categoryName)
diff --git a/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs b/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs
index 01d7743e14..3412e822c9 100644
--- a/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs
+++ b/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs
@@ -16,9 +16,46 @@ namespace ICSharpCode.SharpDevelop.Gui
///
public class MessageViewCategory
{
+ #region Static methods to create MessageViewCategories
+ ///
+ /// Creates a new MessageViewCategory with the specified category
+ /// and adds it to the CompilerMessageView pad.
+ /// This method is thread-safe and works correctly even if called multiple times for the same
+ /// thread; only one messageViewCategory will be created.
+ ///
+ public static void Create(ref MessageViewCategory messageViewCategory, string category)
+ {
+ Create(ref messageViewCategory, category, category);
+ }
+
+ ///
+ /// Creates a new MessageViewCategory with the specified category
+ /// and adds it to the CompilerMessageView pad.
+ /// This method is thread-safe and works correctly even if called concurrently for the same
+ /// category; only one messageViewCategory will be created.
+ ///
+ public static void Create(ref MessageViewCategory messageViewCategory, string category, string displayCategory)
+ {
+ MessageViewCategory newMessageViewCategory = new MessageViewCategory(category, displayCategory);
+ if (System.Threading.Interlocked.CompareExchange(ref messageViewCategory, newMessageViewCategory, null) == null) {
+ // this thread was successful creating the category, so add it
+ CompilerMessageView.Instance.AddCategory(newMessageViewCategory);
+ }
+ }
+ #endregion
+
string category;
string displayCategory;
- StringBuilder textBuilder = new StringBuilder();
+ readonly StringBuilder textBuilder = new StringBuilder();
+
+ ///
+ /// Gets the object on which the MessageViewCategory locks.
+ ///
+ public object SyncRoot {
+ get {
+ return textBuilder;
+ }
+ }
public string Category {
get {
@@ -50,32 +87,6 @@ namespace ICSharpCode.SharpDevelop.Gui
this.displayCategory = displayCategory;
}
- ///
- /// Creates a new MessageViewCategory with the specified category
- /// and adds it to the CompilerMessageView pad.
- /// This method is thread-safe and works correctly even if called multiple times for the same
- /// thread; only one messageViewCategory will be created.
- ///
- public static void Create(ref MessageViewCategory messageViewCategory, string category)
- {
- Create(ref messageViewCategory, category, category);
- }
-
- ///
- /// Creates a new MessageViewCategory with the specified category
- /// and adds it to the CompilerMessageView pad.
- /// This method is thread-safe and works correctly even if called concurrently for the same
- /// category; only one messageViewCategory will be created.
- ///
- public static void Create(ref MessageViewCategory messageViewCategory, string category, string displayCategory)
- {
- MessageViewCategory newMessageViewCategory = new MessageViewCategory(category, displayCategory);
- if (System.Threading.Interlocked.CompareExchange(ref messageViewCategory, newMessageViewCategory, null) == null) {
- // this thread was successful creating the category, so add it
- CompilerMessageView.Instance.AddCategory(newMessageViewCategory);
- }
- }
-
public void AppendLine(string text)
{
AppendText(text + Environment.NewLine);
@@ -85,25 +96,25 @@ namespace ICSharpCode.SharpDevelop.Gui
{
lock (textBuilder) {
textBuilder.Append(text);
+ OnTextAppended(new TextEventArgs(text));
}
- OnTextAppended(new TextEventArgs(text));
}
public void SetText(string text)
{
lock (textBuilder) {
+ // clear text:
textBuilder.Length = 0;
+ // reset capacity: we must shrink the textBuilder at some point to reclaim memory
+ textBuilder.Capacity = text.Length + 16;
textBuilder.Append(text);
+ OnTextSet(new TextEventArgs(text));
}
- OnTextSet(new TextEventArgs(text));
}
public void ClearText()
{
- lock (textBuilder) {
- textBuilder.Length = 0;
- }
- OnCleared(EventArgs.Empty);
+ SetText(string.Empty);
}
protected virtual void OnTextAppended(TextEventArgs e)
@@ -113,14 +124,6 @@ namespace ICSharpCode.SharpDevelop.Gui
}
}
-
- protected virtual void OnCleared(EventArgs e)
- {
- if (Cleared != null) {
- Cleared(this, e);
- }
- }
-
protected virtual void OnTextSet(TextEventArgs e)
{
if (TextSet != null) {
@@ -128,8 +131,21 @@ namespace ICSharpCode.SharpDevelop.Gui
}
}
+ ///
+ /// Is raised when text is appended to the MessageViewCategory.
+ /// Warning: This event is raised inside a lock held by the MessageViewCategory. This is necessary
+ /// to ensure TextAppended event handlers are called in the same order as text is appended to the category
+ /// when there are multiple threads writing to the same category.
+ ///
public event TextEventHandler TextAppended;
+
+ ///
+ /// Is raised when text is appended to the MessageViewCategory.
+ /// Warning: This event is raised inside a lock held by the MessageViewCategory. This is necessary
+ /// to ensure TextAppended and TextSet event handlers are called in the same order as
+ /// text is appended or set
+ /// when there are multiple threads writing to the same category.
+ ///
public event TextEventHandler TextSet;
- public event EventHandler Cleared;
}
}
diff --git a/src/Main/Base/Project/Src/Project/BuildEngine.cs b/src/Main/Base/Project/Src/Project/BuildEngine.cs
index 1300390cd3..c9e5359bb6 100644
--- a/src/Main/Base/Project/Src/Project/BuildEngine.cs
+++ b/src/Main/Base/Project/Src/Project/BuildEngine.cs
@@ -72,7 +72,7 @@ namespace ICSharpCode.SharpDevelop.Project
public void ReportMessage(string message)
{
- messageView.AppendLine(message);
+ messageView.AppendLine(ICSharpCode.Core.StringParser.Parse(message));
}
public void Done(bool success)
diff --git a/src/Main/Base/Project/Src/Util/ProcessRunner.cs b/src/Main/Base/Project/Src/Util/ProcessRunner.cs
index 7401e1717b..8e7447e818 100644
--- a/src/Main/Base/Project/Src/Util/ProcessRunner.cs
+++ b/src/Main/Base/Project/Src/Util/ProcessRunner.cs
@@ -7,11 +7,10 @@
using System;
using System.Diagnostics;
-using System.Runtime.InteropServices;
using System.Text;
+using System.Threading;
using ICSharpCode.Core;
-using System.Threading;
namespace ICSharpCode.SharpDevelop.Util
{
@@ -47,6 +46,7 @@ namespace ICSharpCode.SharpDevelop.Util
///
public ProcessRunner()
{
+ this.LogStandardOutputAndError = true;
}
///
@@ -54,6 +54,14 @@ namespace ICSharpCode.SharpDevelop.Util
///
public string WorkingDirectory { get; set; }
+ ///
+ /// Gets or sets whether standard output is logged to the "StandardOutput" and "StandardError"
+ /// properties. When this property is false, output is still redirected to the
+ /// OutputLineReceived and ErrorLineReceived events, but the ProcessRunner uses less memory.
+ /// The default value is true.
+ ///
+ public bool LogStandardOutputAndError { get; set; }
+
///
/// Gets the standard output returned from the process.
///
@@ -161,19 +169,19 @@ namespace ICSharpCode.SharpDevelop.Util
process.Exited += OnProcessExited;
}
- bool started = false;
- try {
- process.Start();
- started = true;
- } finally {
- if (!started) {
- process.Exited -= OnProcessExited;
- process = null;
- }
+ bool started = false;
+ try {
+ process.Start();
+ started = true;
+ } finally {
+ if (!started) {
+ process.Exited -= OnProcessExited;
+ process = null;
}
-
- process.BeginOutputReadLine();
- process.BeginErrorReadLine();
+ }
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
}
///
@@ -229,8 +237,10 @@ namespace ICSharpCode.SharpDevelop.Util
endOfOutput.Set();
return;
}
- lock (standardOutput) {
- standardOutput.AppendLine(e.Data);
+ if (LogStandardOutputAndError) {
+ lock (standardOutput) {
+ standardOutput.AppendLine(e.Data);
+ }
}
if (OutputLineReceived != null) {
OutputLineReceived(this, new LineReceivedEventArgs(e.Data));
@@ -249,8 +259,10 @@ namespace ICSharpCode.SharpDevelop.Util
endOfOutput.Set();
return;
}
- lock (standardError) {
- standardError.AppendLine(e.Data);
+ if (LogStandardOutputAndError) {
+ lock (standardError) {
+ standardError.AppendLine(e.Data);
+ }
}
if (ErrorLineReceived != null) {
ErrorLineReceived(this, new LineReceivedEventArgs(e.Data));