diff --git a/Debugger/ILSpy.Debugger/AvalonEdit/ITextMarker.cs b/Debugger/ILSpy.Debugger/AvalonEdit/ITextMarker.cs new file mode 100644 index 000000000..f0513f949 --- /dev/null +++ b/Debugger/ILSpy.Debugger/AvalonEdit/ITextMarker.cs @@ -0,0 +1,126 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Windows.Media; + +namespace ILSpy.Debugger.AvalonEdit +{ + /// + /// Represents a text marker. + /// + public interface ITextMarker + { + /// + /// Gets the start offset of the marked text region. + /// + int StartOffset { get; } + + /// + /// Gets the end offset of the marked text region. + /// + int EndOffset { get; } + + /// + /// Gets the length of the marked region. + /// + int Length { get; } + + /// + /// Deletes the text marker. + /// + void Delete(); + + /// + /// Gets whether the text marker was deleted. + /// + bool IsDeleted { get; } + + /// + /// Event that occurs when the text marker is deleted. + /// + event EventHandler Deleted; + + /// + /// Gets/Sets the background color. + /// + Color? BackgroundColor { get; set; } + + /// + /// Gets/Sets the foreground color. + /// + 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 + { + /// + /// Creates a new text marker. The text marker will be invisible at first, + /// you need to set one of the Color properties to make it visible. + /// + ITextMarker Create(int startOffset, int length); + + /// + /// Gets the list of text markers. + /// + IEnumerable TextMarkers { get; } + + /// + /// Removes the specified text marker. + /// + void Remove(ITextMarker marker); + + /// + /// Removes all text markers that match the condition. + /// + void RemoveAll(Predicate predicate); + } +} diff --git a/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs b/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs index 146148d4d..5bccc2e89 100644 --- a/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs +++ b/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs @@ -81,7 +81,7 @@ namespace ILSpy.Debugger.AvalonEdit foreach (var bm in BookmarkManager.Bookmarks) { if (CurrentType == null || bm.TypeName != CurrentType.FullName) continue; - if (bm is BreakpointBookmark && + if (bm is BreakpointBookmark && ((BreakpointBookmark)bm).Language != DebuggerService.CurrentDebugger.Language) continue; @@ -219,7 +219,7 @@ namespace ILSpy.Debugger.AvalonEdit b.LineNumber == GetLineFromMousePosition(e) && b is BreakpointBookmark) as BreakpointBookmark; - this.ToolTip = (bm != null) ? bm.Tooltip : null; + this.ToolTip = (bm != null) ? bm.Tooltip : null; } protected override void OnMouseUp(MouseButtonEventArgs e) @@ -263,7 +263,7 @@ namespace ILSpy.Debugger.AvalonEdit // no bookmark on the line: create a new breakpoint DebuggerService.ToggleBreakpointAt( - CurrentType.FullName, + CurrentType.FullName, line, DebuggerService.CurrentDebugger.Language); } diff --git a/Debugger/ILSpy.Debugger/AvalonEdit/TextMarkerService.cs b/Debugger/ILSpy.Debugger/AvalonEdit/TextMarkerService.cs new file mode 100644 index 000000000..7d956ab89 --- /dev/null +++ b/Debugger/ILSpy.Debugger/AvalonEdit/TextMarkerService.cs @@ -0,0 +1,309 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Threading; + +using ICSharpCode.AvalonEdit; +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Rendering; +using ILSpy.Debugger.Bookmarks; + +namespace ILSpy.Debugger.AvalonEdit +{ + /// + /// Handles the text markers for a code editor. + /// + public sealed class TextMarkerService : DocumentColorizingTransformer, IBackgroundRenderer, ITextMarkerService + { + readonly TextEditor codeEditor; + TextSegmentCollection markers = new TextSegmentCollection(); + + public TextMarkerService(TextEditor codeEditor) + { + if (codeEditor == null) + throw new ArgumentNullException("codeEditor"); + this.codeEditor = codeEditor; + + BookmarkManager.Added += new BookmarkEventHandler(BookmarkManager_Added); + BookmarkManager.Removed += new BookmarkEventHandler(BookmarkManager_Removed); + } + + void BookmarkManager_Removed(object sender, BookmarkEventArgs e) + { + if (e.Bookmark is MarkerBookmark) { + var bm = (MarkerBookmark)e.Bookmark; + Remove(bm.Marker); + } + } + + void BookmarkManager_Added(object sender, BookmarkEventArgs e) + { + if (e.Bookmark is MarkerBookmark) { + var bm = (MarkerBookmark)e.Bookmark; + DocumentLine line = codeEditor.Document.GetLineByNumber(bm.LineNumber); + bm.Marker = bm.CreateMarker(this, line.Offset, line.Length); + } + } + + #region ITextMarkerService + public ITextMarker Create(int startOffset, int length) + { + int textLength = codeEditor.TextArea.TextView.Document.TextLength; + if (startOffset < 0 || startOffset > textLength) + throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between 0 and " + textLength); + if (length < 0 || startOffset + length > textLength) + throw new ArgumentOutOfRangeException("length", length, "length must not be negative and startOffset+length must not be after the end of the document"); + + TextMarker m = new TextMarker(this, startOffset, length); + markers.Add(m); + // no need to mark segment for redraw: the text marker is invisible until a property is set + return m; + } + + public IEnumerable GetMarkersAtOffset(int offset) + { + return markers.FindSegmentsContaining(offset); + } + + public IEnumerable TextMarkers { + get { return markers; } + } + + public void RemoveAll(Predicate predicate) + { + if (predicate == null) + throw new ArgumentNullException("predicate"); + foreach (TextMarker m in markers.ToArray()) { + if (predicate(m)) + Remove(m); + } + } + + public void Remove(ITextMarker marker) + { + if (marker == null) + throw new ArgumentNullException("marker"); + TextMarker m = marker as TextMarker; + if (markers.Remove(m)) { + Redraw(m); + m.OnDeleted(); + } + } + + /// + /// Redraws the specified text segment. + /// + public void Redraw(ISegment segment) + { + codeEditor.TextArea.TextView.Redraw(segment, DispatcherPriority.Normal); + } + #endregion + + #region DocumentColorizingTransformer + protected override void ColorizeLine(DocumentLine line) + { + if (markers == null) + return; + int lineStart = line.Offset; + int lineEnd = lineStart + line.Length; + foreach (TextMarker marker in markers.FindOverlappingSegments(lineStart, line.Length)) { + Brush foregroundBrush = null; + if (marker.ForegroundColor != null) { + foregroundBrush = new SolidColorBrush(marker.ForegroundColor.Value); + foregroundBrush.Freeze(); + } + ChangeLinePart( + Math.Max(marker.StartOffset, lineStart), + Math.Min(marker.EndOffset, lineEnd), + element => { + if (foregroundBrush != null) { + element.TextRunProperties.SetForegroundBrush(foregroundBrush); + } + } + ); + } + } + #endregion + + #region IBackgroundRenderer + public KnownLayer Layer { + get { + // draw behind selection + return KnownLayer.Selection; + } + } + + public void Draw(TextView textView, DrawingContext drawingContext) + { + if (textView == null) + throw new ArgumentNullException("textView"); + if (drawingContext == null) + throw new ArgumentNullException("drawingContext"); + if (markers == null || !textView.VisualLinesValid) + return; + var visualLines = textView.VisualLines; + if (visualLines.Count == 0) + return; + int viewStart = visualLines.First().FirstDocumentLine.Offset; + int viewEnd = visualLines.Last().LastDocumentLine.Offset + visualLines.Last().LastDocumentLine.Length; + foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { + if (marker.BackgroundColor != null) { + BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToWholePixels = true; + geoBuilder.CornerRadius = 3; + geoBuilder.AddSegment(textView, marker); + Geometry geometry = geoBuilder.CreateGeometry(); + if (geometry != null) { + Color color = marker.BackgroundColor.Value; + SolidColorBrush brush = new SolidColorBrush(color); + brush.Freeze(); + 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 = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4); + + 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 + } + + sealed class TextMarker : TextSegment, ITextMarker + { + readonly TextMarkerService service; + + public TextMarker(TextMarkerService service, int startOffset, int length) + { + if (service == null) + throw new ArgumentNullException("service"); + this.service = service; + this.StartOffset = startOffset; + this.Length = length; + this.markerType = TextMarkerType.None; + } + + public event EventHandler Deleted; + + public bool IsDeleted { + get { return !this.IsConnectedToCollection; } + } + + public void Delete() + { + service.Remove(this); + } + + internal void OnDeleted() + { + if (Deleted != null) + Deleted(this, EventArgs.Empty); + } + + void Redraw() + { + service.Redraw(this); + } + + Color? backgroundColor; + + public Color? BackgroundColor { + get { return backgroundColor; } + set { + if (backgroundColor != value) { + backgroundColor = value; + Redraw(); + } + } + } + + Color? foregroundColor; + + public Color? ForegroundColor { + get { return foregroundColor; } + set { + if (foregroundColor != value) { + foregroundColor = value; + Redraw(); + } + } + } + + 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/Debugger/ILSpy.Debugger/Bookmarks/BreakpointBookmark.cs b/Debugger/ILSpy.Debugger/Bookmarks/BreakpointBookmark.cs index e0689e16b..94bcd6bef 100644 --- a/Debugger/ILSpy.Debugger/Bookmarks/BreakpointBookmark.cs +++ b/Debugger/ILSpy.Debugger/Bookmarks/BreakpointBookmark.cs @@ -18,8 +18,10 @@ using System; using System.Windows.Media; +using ICSharpCode.AvalonEdit.Document; using ICSharpCode.Decompiler; using ICSharpCode.NRefactory.CSharp; +using ILSpy.Debugger.AvalonEdit; using ILSpy.Debugger.Services; namespace ILSpy.Debugger.Bookmarks @@ -31,7 +33,7 @@ namespace ILSpy.Debugger.Bookmarks Condition } - public class BreakpointBookmark : BookmarkBase + public class BreakpointBookmark : MarkerBookmark { bool isHealthy = true; bool isEnabled = true; @@ -97,5 +99,13 @@ namespace ILSpy.Debugger.Bookmarks return ImageService.Breakpoint; } } + + public override ITextMarker CreateMarker(ITextMarkerService markerService, int offset, int length) + { + ITextMarker marker = markerService.Create(offset, length); + marker.BackgroundColor = Color.FromRgb(180, 38, 38); + marker.ForegroundColor = Colors.White; + return marker; + } } } diff --git a/Debugger/ILSpy.Debugger/Bookmarks/CurrentLineBookmark.cs b/Debugger/ILSpy.Debugger/Bookmarks/CurrentLineBookmark.cs index d1551f7f6..47caa0418 100644 --- a/Debugger/ILSpy.Debugger/Bookmarks/CurrentLineBookmark.cs +++ b/Debugger/ILSpy.Debugger/Bookmarks/CurrentLineBookmark.cs @@ -1,15 +1,31 @@ -// 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) +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Windows.Media; using ICSharpCode.NRefactory.CSharp; +using ILSpy.Debugger.AvalonEdit; using ILSpy.Debugger.Services; using Mono.CSharp; namespace ILSpy.Debugger.Bookmarks { - public class CurrentLineBookmark : BookmarkBase + public class CurrentLineBookmark : MarkerBookmark { static CurrentLineBookmark instance; @@ -73,5 +89,13 @@ namespace ILSpy.Debugger.Bookmarks // DebuggerService.CurrentDebugger.SetInstructionPointer(this.FileName, lineNumber, 1); // }); } + + public override ITextMarker CreateMarker(ITextMarkerService markerService, int offset, int length) + { + ITextMarker marker = markerService.Create(offset + startColumn - 1, Math.Max(endColumn - startColumn, 1)); + marker.BackgroundColor = Colors.Yellow; + marker.ForegroundColor = Colors.Blue; + return marker; + } } } diff --git a/Debugger/ILSpy.Debugger/Bookmarks/MarkerBookmark.cs b/Debugger/ILSpy.Debugger/Bookmarks/MarkerBookmark.cs new file mode 100644 index 000000000..aebc3d461 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Bookmarks/MarkerBookmark.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using ICSharpCode.NRefactory.CSharp; +using ILSpy.Debugger.AvalonEdit; + +namespace ILSpy.Debugger.Bookmarks +{ + /// + /// Description of MarkerBookmark. + /// + public abstract class MarkerBookmark : BookmarkBase + { + public MarkerBookmark(string typeName, AstLocation location) : base(typeName, location) + { + } + + public ITextMarker Marker { get; set; } + + public abstract ITextMarker CreateMarker(ITextMarkerService markerService, int offset, int length); + } +} diff --git a/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj b/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj index 9ae1302ab..e96788e23 100644 --- a/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj +++ b/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj @@ -52,9 +52,11 @@ + + @@ -62,6 +64,7 @@ + diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 71ff67b57..2f47b517f 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -63,6 +63,7 @@ namespace ICSharpCode.ILSpy.TextView CancellationTokenSource currentCancellationTokenSource; IconBarMargin iconMargin; + TextMarkerService textMarkerService; #region Constructor public DecompilerTextView() @@ -86,7 +87,11 @@ namespace ICSharpCode.ILSpy.TextView textEditor.TextArea.TextView.MouseHover += TextViewMouseHover; textEditor.TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped; - // add margin + // add marker service & margin + textMarkerService = new TextMarkerService(textEditor); + textEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService); + textEditor.TextArea.TextView.LineTransformers.Add(textMarkerService); + iconMargin = new IconBarMargin(); textEditor.TextArea.LeftMargins.Add(iconMargin);