From f15a135bf9e9f7c0261d950e400d758bf129c8cb Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 30 Aug 2009 13:11:52 +0000 Subject: [PATCH] implemented ErrorDrawer for ITextEditor git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4828 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Src/AvalonEditViewContent.cs | 4 +- .../AvalonEdit.AddIn/Src/CodeEditor.cs | 38 +++-- .../AvalonEdit.AddIn/Src/TextMarkerService.cs | 66 ++++++++ .../Rendering/BackgroundGeometryBuilder.cs | 9 +- .../Rendering/LayerPosition.cs | 4 +- .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../Base/Project/Src/Editor/ITextMarker.cs | 27 ++++ .../Project/Src/Services/Tasks/ErrorDrawer.cs | 151 ++++++++++++++++++ 8 files changed, 285 insertions(+), 15 deletions(-) create mode 100644 src/Main/Base/Project/Src/Services/Tasks/ErrorDrawer.cs diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs index f9a64ac95c..fd9504c5d6 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs @@ -155,9 +155,7 @@ namespace ICSharpCode.AvalonEdit.AddIn { base.Dispose(); BookmarksDetach(); - codeEditor.DisposeLanguageBinding(); - // Unload document on dispose. - codeEditor.Document = null; + codeEditor.Dispose(); } public override string ToString() diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs index cea7f6734e..cc310f21e1 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs @@ -38,7 +38,7 @@ namespace ICSharpCode.AvalonEdit.AddIn /// Integrates AvalonEdit with SharpDevelop. /// Also provides support for Split-View (showing two AvalonEdit instances using the same TextDocument) /// - public class CodeEditor : Grid + public class CodeEditor : Grid, IDisposable { const string contextMenuPath = "/SharpDevelop/ViewContent/AvalonEdit/ContextMenu"; @@ -49,6 +49,7 @@ namespace ICSharpCode.AvalonEdit.AddIn CodeEditorAdapter secondaryTextEditorAdapter; readonly IconBarManager iconBarManager; readonly TextMarkerService textMarkerService; + readonly ErrorDrawer errorDrawer; public TextEditor PrimaryTextEditor { get { return primaryTextEditor; } @@ -64,7 +65,7 @@ namespace ICSharpCode.AvalonEdit.AddIn get { return document; } - set { + private set { if (document != value) { if (document != null) document.UpdateFinished -= DocumentUpdateFinished; @@ -124,13 +125,6 @@ namespace ICSharpCode.AvalonEdit.AddIn } } - internal void DisposeLanguageBinding() - { - primaryTextEditorAdapter.Language.Detach(); - if (secondaryTextEditorAdapter != null) - secondaryTextEditorAdapter.Language.Detach(); - } - public void Redraw(ISegment segment, DispatcherPriority priority) { primaryTextEditor.TextArea.TextView.Redraw(segment, priority); @@ -150,6 +144,8 @@ namespace ICSharpCode.AvalonEdit.AddIn primaryTextEditorAdapter = (CodeEditorAdapter)primaryTextEditor.TextArea.GetService(typeof(ITextEditor)); Debug.Assert(primaryTextEditorAdapter != null); + this.errorDrawer = new ErrorDrawer(primaryTextEditorAdapter); + this.Document = primaryTextEditor.Document; primaryTextEditor.SetBinding(TextEditor.DocumentProperty, new Binding("Document") { Source = this }); @@ -312,6 +308,20 @@ namespace ICSharpCode.AvalonEdit.AddIn args.LogicalPosition = AvalonEditDocumentAdapter.ToLocation(pos.Value); } + var markersAtOffset = textMarkerService.GetMarkersAtOffset(args.Editor.Document.PositionToOffset(args.LogicalPosition.Line, args.LogicalPosition.Column)); + + ITextMarker markerWithToolTip = markersAtOffset.FirstOrDefault(marker => marker.ToolTip != null); + + if (markerWithToolTip != null) { + if (toolTip == null) { + toolTip = new ToolTip(); + toolTip.Closed += toolTip_Closed; + } + toolTip.Content = markerWithToolTip.ToolTip; + toolTip.IsOpen = true; + e.Handled = true; + } + ToolTipRequestService.RequestToolTip(args); if (args.ContentToShow != null) { @@ -648,5 +658,15 @@ namespace ICSharpCode.AvalonEdit.AddIn } iconBarManager.UpdateClassMemberBookmarks(parseInfo); } + + public void Dispose() + { + primaryTextEditorAdapter.Language.Detach(); + if (secondaryTextEditorAdapter != null) + secondaryTextEditorAdapter.Language.Detach(); + + errorDrawer.Dispose(); + this.Document = null; + } } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs index 8cea26356b..55f0629aa3 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Windows; using System.Windows.Media; using System.Windows.Threading; @@ -59,6 +60,11 @@ namespace ICSharpCode.AvalonEdit.AddIn return m; } + public IEnumerable GetMarkersAtOffset(int offset) + { + return markers.FindSegmentsContaining(offset); + } + public IEnumerable TextMarkers { get { return markers; } } @@ -152,8 +158,41 @@ namespace ICSharpCode.AvalonEdit.AddIn drawingContext.DrawGeometry(brush, null, geometry); } } + if (marker.MarkerType != TextMarkerType.None) { + foreach (Rect r in BackgroundGeometryBuilder.GetRectsForSegment(textView, marker)) { + Point startPoint = r.BottomLeft; + Point endPoint = r.BottomRight; + + Pen usedPen = new Pen(new SolidColorBrush(marker.MarkerColor), 1); + usedPen.Freeze(); + switch (marker.MarkerType) { + case TextMarkerType.SquigglyUnderline: + double offset = 2.5; + + int count = (int)((endPoint.X - startPoint.X) / offset) + 1; + + StreamGeometry geometry = new StreamGeometry(); + + using (StreamGeometryContext ctx = geometry.Open()) { + ctx.BeginFigure(startPoint, false, false); + ctx.PolyLineTo(CreatePoints(startPoint, endPoint, offset, count).ToArray(), true, false); + } + + geometry.Freeze(); + + drawingContext.DrawGeometry(Brushes.Transparent, usedPen, geometry); + break; + } + } + } } } + + IEnumerable CreatePoints(Point start, Point end, double offset, int count) + { + for (int i = 0; i < count; i++) + yield return new Point(start.X + i * offset, start.Y - ((i + 1) % 2 == 0 ? offset : 0)); + } #endregion } @@ -168,6 +207,7 @@ namespace ICSharpCode.AvalonEdit.AddIn this.service = service; this.StartOffset = startOffset; this.Length = length; + this.markerType = TextMarkerType.None; } public event EventHandler Deleted; @@ -217,5 +257,31 @@ namespace ICSharpCode.AvalonEdit.AddIn } public object Tag { get; set; } + + TextMarkerType markerType; + + public TextMarkerType MarkerType { + get { return markerType; } + set { + if (markerType != value) { + markerType = value; + Redraw(); + } + } + } + + Color markerColor; + + public Color MarkerColor { + get { return markerColor; } + set { + if (markerColor != value) { + markerColor = value; + Redraw(); + } + } + } + + public object ToolTip { get; set; } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs index 5e1ff9d51f..94b3d689b4 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs @@ -6,6 +6,7 @@ // using System; +using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Media; @@ -42,6 +43,12 @@ namespace ICSharpCode.AvalonEdit.Rendering /// Adds the specified segment to the geometry. /// public void AddSegment(TextView textView, ISegment segment) + { + foreach (Rect r in GetRectsForSegment(textView, segment)) + AddRectangle(r.Left, r.Top, r.Right, r.Bottom); + } + + public static IEnumerable GetRectsForSegment(TextView textView, ISegment segment) { if (textView == null) throw new ArgumentNullException("textView"); @@ -89,7 +96,7 @@ namespace ICSharpCode.AvalonEdit.Rendering y -= scrollOffset.Y; left -= scrollOffset.X; right -= scrollOffset.X; - AddRectangle(left, y, right, y + line.Height); + yield return new Rect(left, y, right - left, line.Height); } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/LayerPosition.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/LayerPosition.cs index 553e7e0dea..2b1ef38e4c 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/LayerPosition.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/LayerPosition.cs @@ -35,7 +35,7 @@ namespace ICSharpCode.AvalonEdit.Rendering /// /// This layer contains the blinking caret. /// - /// This layer is above the Text layer. + /// This layer is above the Text layer. All items on this layer will blink with the same frequency as the caret. Caret } @@ -92,4 +92,4 @@ namespace ICSharpCode.AvalonEdit.Rendering return this.Position.CompareTo(other.Position); } } -} +} \ No newline at end of file diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 0391d46d6c..32e51b8837 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -259,6 +259,7 @@ + diff --git a/src/Main/Base/Project/Src/Editor/ITextMarker.cs b/src/Main/Base/Project/Src/Editor/ITextMarker.cs index 7671cc4b47..f7d08ac7f6 100644 --- a/src/Main/Base/Project/Src/Editor/ITextMarker.cs +++ b/src/Main/Base/Project/Src/Editor/ITextMarker.cs @@ -56,10 +56,37 @@ namespace ICSharpCode.SharpDevelop.Editor /// Color? ForegroundColor { get; set; } + /// + /// Gets/Sets the type of the marker. Use TextMarkerType.None for normal markers. + /// + TextMarkerType MarkerType { get; set; } + + /// + /// Gets/Sets the color of the marker. + /// + Color MarkerColor { get; set; } + /// /// Gets/Sets an object with additional data for this text marker. /// object Tag { get; set; } + + /// + /// Gets/Sets an object that will be displayed as tooltip in the text editor. + /// + object ToolTip { get; set; } + } + + public enum TextMarkerType + { + /// + /// Use no marker + /// + None, + /// + /// Use squiggly underline marker + /// + SquigglyUnderline } public interface ITextMarkerService diff --git a/src/Main/Base/Project/Src/Services/Tasks/ErrorDrawer.cs b/src/Main/Base/Project/Src/Services/Tasks/ErrorDrawer.cs new file mode 100644 index 0000000000..c1e6a805c7 --- /dev/null +++ b/src/Main/Base/Project/Src/Services/Tasks/ErrorDrawer.cs @@ -0,0 +1,151 @@ +// +// +// +// +// $Revision$ +// +using ICSharpCode.Core; +using System; +using System.Windows.Media; +using ICSharpCode.SharpDevelop.Debugging; +using ICSharpCode.SharpDevelop.Editor; + +namespace ICSharpCode.SharpDevelop +{ + /// + /// Synchronizes the ITextEditors with the TaskService. It adds and removes error markers. + /// + public class ErrorDrawer : IDisposable + { + ITextEditor textEditor; + ITextMarkerService markerService; + + public ErrorDrawer(ITextEditor textEditor) + { + this.textEditor = textEditor; + this.markerService = this.textEditor.GetService(typeof(ITextMarkerService)) as ITextMarkerService; + + if (this.markerService == null) + throw new InvalidOperationException("this ITextEditor has no text marker service!"); + + TaskService.Added += new TaskEventHandler(OnAdded); + TaskService.Removed += new TaskEventHandler(OnRemoved); + TaskService.Cleared += new EventHandler(OnCleared); + DebuggerService.DebugStarted += OnDebugStarted; + DebuggerService.DebugStopped += OnDebugStopped; + } + + bool isDisposed; + + /// + /// Deregisters the event handlers so the error drawer (and associated TextEditorControl) + /// can be garbage collected. + /// + public void Dispose() + { + if (isDisposed) + return; + isDisposed = true; + TaskService.Added -= new TaskEventHandler(OnAdded); + TaskService.Removed -= new TaskEventHandler(OnRemoved); + TaskService.Cleared -= new EventHandler(OnCleared); + DebuggerService.DebugStarted -= OnDebugStarted; + DebuggerService.DebugStopped -= OnDebugStopped; + ClearErrors(); + } + + void OnDebugStarted(object sender, EventArgs e) + { + ClearErrors(); + } + + void OnDebugStopped(object sender, EventArgs e) + { + foreach (Task task in TaskService.Tasks) { + AddTask(task); + } + } + + void OnAdded(object sender, TaskEventArgs e) + { + AddTask(e.Task); + } + + void OnRemoved(object sender, TaskEventArgs e) + { + markerService.RemoveAll(marker => marker.Tag == e.Task); + } + + void OnCleared(object sender, EventArgs e) + { + ClearErrors(); + } + + /// + /// Clears all TextMarkers representing errors. + /// + /// Returns true when there were markers deleted, false when there were no error markers. + void ClearErrors() + { + markerService.RemoveAll(marker => marker.Tag is Task); + } + + bool CheckTask(Task task) + { + if (textEditor.FileName == null) + return false; + if (task.FileName == null || task.FileName.Length == 0 || task.Column < 0) + return false; + if (task.TaskType != TaskType.Warning && task.TaskType != TaskType.Error) + return false; + return FileUtility.IsEqualFileName(task.FileName, textEditor.FileName); + } + + void AddTask(Task task) + { + if (!CheckTask(task)) return; + + if (task.Line >= 0 && task.Line < textEditor.Document.TotalNumberOfLines) { + int offset = textEditor.Document.PositionToOffset(task.Line, task.Column); + int length = textEditor.Document.GetWordAt(offset).Length; + + if (length < 2) + length = 2; + + ITextMarker marker = this.markerService.Create(offset, length); + + Color markerColor = Colors.Transparent; + + switch (task.TaskType) { + case TaskType.Error: + markerColor = Colors.Red; + break; + case TaskType.Message: + markerColor = Colors.Blue; + break; + case TaskType.Warning: + markerColor = Colors.Yellow; + break; + } + + marker.MarkerColor = markerColor; + marker.MarkerType = TextMarkerType.SquigglyUnderline; + + marker.ToolTip = task.Description; + + marker.Tag = task; + } + } + + /// + /// Clears all errors and adds them again. + /// + public void UpdateErrors() + { + ClearErrors(); + foreach (Task task in TaskService.Tasks) { + AddTask(task); + } + } + } +}