// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.Search; using ICSharpCode.Core; using ICSharpCode.Core.Presentation; using ICSharpCode.SharpDevelop.Gui.OptionPanels; using ICSharpCode.SharpDevelop.Project; namespace ICSharpCode.SharpDevelop.Gui { /// /// This class displays the errors and warnings which the compiler outputs and /// allows the user to jump to the source of the warning / error /// public class CompilerMessageView : AbstractPadContent, IClipboardHandler { static CompilerMessageView instance; /// /// Gets the instance of the CompilerMessageView. This property is thread-safe, but /// most instance methods of the CompilerMessageView aren't. /// public static CompilerMessageView Instance { get { if (instance == null) WorkbenchSingleton.SafeThreadCall(InitializeInstance); return instance; } } static void InitializeInstance() { WorkbenchSingleton.Workbench.GetPad(typeof(CompilerMessageView)).CreatePad(); } #region MessageViewLinkElementGenerator enum LinkType { CSharp, NUnit, Cpp } class MessageViewLinkElementGenerator : LinkElementGenerator { LinkType type; public MessageViewLinkElementGenerator(LinkType type) : base(GetRegex(type)) { this.type = type; RequireControlModifierForClick = false; } protected override Uri GetUriFromMatch(Match match) { return new Uri(match.Groups[1].Value.Trim()); } public override VisualLineElement ConstructElement(int offset) { int matchOffset; Match m = GetMatch(offset, out matchOffset); if (m.Success && matchOffset == offset) { Uri uri = GetUriFromMatch(m); if (uri == null) return null; VisualLineMessageViewLinkText linkText = new VisualLineMessageViewLinkText(CurrentContext.VisualLine, m.Length); linkText.NavigateUri = uri; linkText.RequireControlModifierForClick = this.RequireControlModifierForClick; linkText.Line = int.Parse(m.Groups[2].Value); if (type == LinkType.CSharp) linkText.Column = int.Parse(m.Groups[3].Value); return linkText; } else { return null; } } static Regex GetRegex(LinkType type) { switch (type) { case CompilerMessageView.LinkType.CSharp: return new Regex(@"\b(\w:[/\\].*?)\((\d+),(\d+)\)"); case CompilerMessageView.LinkType.NUnit: return new Regex(@"\b(\w:[/\\].*?):line\s(\d+)?\r?$"); case CompilerMessageView.LinkType.Cpp: return new Regex(@"\b(\w:[/\\].*?)\((\d+)\)"); default: throw new Exception("Invalid value for LinkType"); } } public static void RegisterGenerators(TextView textView) { textView.ElementGenerators.Add(new MessageViewLinkElementGenerator(LinkType.CSharp)); textView.ElementGenerators.Add(new MessageViewLinkElementGenerator(LinkType.NUnit)); textView.ElementGenerators.Add(new MessageViewLinkElementGenerator(LinkType.Cpp)); } } class VisualLineMessageViewLinkText : VisualLineLinkText { /// /// Creates a visual line text element with the specified length. /// It uses the and its /// to find the actual text string. /// public VisualLineMessageViewLinkText(VisualLine parentVisualLine, int length) : base(parentVisualLine, length) { this.RequireControlModifierForClick = false; } public int Line { get; set; } public int Column { get; set; } protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Left && !e.Handled && LinkIsClickable() && NavigateUri.IsFile) { FileService.JumpToFilePosition(NavigateUri.LocalPath, Line, Column); e.Handled = true; } } protected override VisualLineText CreateInstance(int length) { return new VisualLineMessageViewLinkText(ParentVisualLine, length) { NavigateUri = this.NavigateUri, Line = this.Line, Column = this.Column, TargetName = this.TargetName, RequireControlModifierForClick = this.RequireControlModifierForClick }; } } #endregion TextEditor textEditor = new TextEditor(); DockPanel panel = new DockPanel(); ToolBar toolStrip; List messageCategories = new List(); int selectedCategory = 0; public int SelectedCategoryIndex { get { return selectedCategory; } set { WorkbenchSingleton.AssertMainThread(); if (selectedCategory != value) { selectedCategory = value; DisplayActiveCategory(); OnSelectedCategoryIndexChanged(EventArgs.Empty); } } } void DisplayActiveCategory() { WorkbenchSingleton.DebugAssertMainThread(); if (selectedCategory < 0) { textEditor.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); } set { properties.Set("WordWrap", value); } } public MessageViewCategory SelectedMessageViewCategory { get { if (selectedCategory >= 0) { return messageCategories[selectedCategory]; } return null; } } // The compiler message view properties. Properties properties = null; public List MessageCategories { get { return messageCategories; } } public override object Control { get { return panel; } } public CompilerMessageView() { instance = this; AddCategory(TaskService.BuildMessageViewCategory); textEditor.IsReadOnly = true; textEditor.ContextMenu = MenuService.CreateContextMenu(this, "/SharpDevelop/Pads/CompilerMessageView/ContextMenu"); properties = (Properties)PropertyService.Get(OutputWindowOptionsPanel.OutputWindowsProperty, new Properties()); var font = FontSelectionPanel.ParseFont(properties.Get("DefaultFont", Core.WinForms.WinFormsResourceService.DefaultMonospacedFont.ToString()).ToString()); textEditor.FontFamily = new FontFamily(font.FontFamily.Name); textEditor.FontSize = Math.Round(font.Size * 96.0 / 72.0); properties.PropertyChanged += new PropertyChangedEventHandler(PropertyChanged); MessageViewLinkElementGenerator.RegisterGenerators(textEditor.TextArea.TextView); textEditor.TextArea.TextView.ElementGenerators.OfType().ForEach(x => x.RequireControlModifierForClick = false); toolStrip = ToolBarService.CreateToolBar(panel, this, "/SharpDevelop/Pads/CompilerMessageView/Toolbar"); toolStrip.SetValue(DockPanel.DockProperty, Dock.Top); panel.Children.Add(toolStrip); panel.Children.Add(textEditor); SetWordWrap(); DisplayActiveCategory(); ProjectService.SolutionLoaded += SolutionLoaded; textEditor.TextArea.DefaultInputHandler.NestedInputHandlers.Add(new SearchInputHandler(textEditor.TextArea)); } void SolutionLoaded(object sender, SolutionEventArgs e) { foreach (MessageViewCategory category in messageCategories) { category.ClearText(); } } void SetWordWrap() { bool wordWrap = this.WordWrap; textEditor.WordWrap = wordWrap; } #region Category handling /// /// Adds a category to the compiler message view. This method is thread-safe. /// public void AddCategory(MessageViewCategory category) { if (WorkbenchSingleton.InvokeRequired) { WorkbenchSingleton.SafeThreadAsyncCall((Action)AddCategory, category); return; } messageCategories.Add(category); category.TextSet += new TextEventHandler(CategoryTextSet); category.TextAppended += new TextEventHandler(CategoryTextAppended); OnMessageCategoryAdded(EventArgs.Empty); } void CategoryTextSet(object sender, TextEventArgs e) { EnqueueAppend(new AppendCall((MessageViewCategory)sender, e.Text, true)); } struct AppendCall { 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; } } readonly object appendLock = new object(); List appendCalls = new List(); void CategoryTextAppended(object sender, TextEventArgs e) { EnqueueAppend(new AppendCall((MessageViewCategory)sender, e.Text, false)); } void EnqueueAppend(AppendCall appendCall) { 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() { List appendCalls; lock (appendLock) { appendCalls = this.appendCalls; this.appendCalls = new List(); } Debug.Assert(appendCalls.Count > 0); if (appendCalls.Count == 0) return; MessageViewCategory newCategory = appendCalls[appendCalls.Count - 1].Category; if (messageCategories[SelectedCategoryIndex] != newCategory) { SelectCategory(newCategory.Category); return; } 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(); } if (clear) textEditor.Text = text; else textEditor.AppendText(text); textEditor.ScrollToEnd(); } public void SelectCategory(string categoryName) { for (int i = 0; i < messageCategories.Count; ++i) { MessageViewCategory category = (MessageViewCategory)messageCategories[i]; if (category.Category == categoryName) { SelectedCategoryIndex = i; break; } } } void SelectCategory(string categoryName, string text) { for (int i = 0; i < messageCategories.Count; ++i) { MessageViewCategory category = (MessageViewCategory)messageCategories[i]; if (category.Category == categoryName) { selectedCategory = i; textEditor.Text = StringParser.Parse(text); OnSelectedCategoryIndexChanged(EventArgs.Empty); break; } } } public MessageViewCategory GetCategory(string categoryName) { foreach (MessageViewCategory category in messageCategories) { if (category.Category == categoryName) { return category; } } return null; } #endregion /// /// Changes wordwrap settings if that property has changed. /// void PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.Key == "WordWrap") { SetWordWrap(); } if (e.Key == "DefaultFont") { var font = FontSelectionPanel.ParseFont(properties.Get("DefaultFont", Core.WinForms.WinFormsResourceService.DefaultMonospacedFont.ToString()).ToString()); textEditor.FontFamily = new FontFamily(font.FontFamily.Name); textEditor.FontSize = Math.Round(font.Size * 96.0 / 72.0); } } protected virtual void OnMessageCategoryAdded(EventArgs e) { if (MessageCategoryAdded != null) { MessageCategoryAdded(this, e); } } protected virtual void OnSelectedCategoryIndexChanged(EventArgs e) { if (SelectedCategoryIndexChanged != null) { SelectedCategoryIndexChanged(this, e); } } public event EventHandler MessageCategoryAdded; public event EventHandler SelectedCategoryIndexChanged; #region ICSharpCode.SharpDevelop.Gui.IClipboardHandler interface implementation public bool EnableCut { get { return false; } } public bool EnableCopy { get { return textEditor.SelectionLength > 0; } } public bool EnablePaste { get { return false; } } public bool EnableDelete { get { return false; } } public bool EnableSelectAll { get { return textEditor.Document.TextLength > 0; } } public void Cut() { } public void Copy() { textEditor.Copy(); } public void Paste() { } public void Delete() { } public void SelectAll() { textEditor.SelectAll(); } #endregion } }