From 8c3717518901ea9cea1c72f55a826bfb161ab2c7 Mon Sep 17 00:00:00 2001 From: Eusebiu Marcu Date: Tue, 17 May 2011 18:33:09 +0300 Subject: [PATCH] add icon margin & bookmarks --- .../Ast/TextOutputFormatter.cs | 20 +- ILSpy.sln | 2 +- ILSpy/AvalonEdit/ITextMarker.cs | 123 ++++++++ ILSpy/AvalonEdit/IconBarManager.cs | 83 +++++ ILSpy/AvalonEdit/IconBarMargin.cs | 239 ++++++++++++++ .../AvalonEdit/TextEditorWeakEventManager.cs | 67 ++++ ILSpy/AvalonEdit/TextMarkerService.cs | 292 ++++++++++++++++++ ILSpy/Bookmarks/BookmarkBase.cs | 97 ++++++ ILSpy/Bookmarks/BookmarkEventHandler.cs | 28 ++ ILSpy/Bookmarks/BookmarkManager.cs | 116 +++++++ ILSpy/Bookmarks/IBookmark.cs | 69 +++++ ILSpy/Bookmarks/MarkerBookmark.cs | 21 ++ ILSpy/Bookmarks/MemberBookmark.cs | 162 ++++++++++ ILSpy/CSharpLanguage.cs | 8 +- ILSpy/ILLanguage.cs | 24 +- ILSpy/ILSpy.csproj | 16 +- ILSpy/Language.cs | 74 ++++- ILSpy/TextView/DecompilerTextView.cs | 37 ++- .../CSharp/Ast/CompilationUnit.cs | 18 ++ 19 files changed, 1479 insertions(+), 17 deletions(-) create mode 100644 ILSpy/AvalonEdit/ITextMarker.cs create mode 100644 ILSpy/AvalonEdit/IconBarManager.cs create mode 100644 ILSpy/AvalonEdit/IconBarMargin.cs create mode 100644 ILSpy/AvalonEdit/TextEditorWeakEventManager.cs create mode 100644 ILSpy/AvalonEdit/TextMarkerService.cs create mode 100644 ILSpy/Bookmarks/BookmarkBase.cs create mode 100644 ILSpy/Bookmarks/BookmarkEventHandler.cs create mode 100644 ILSpy/Bookmarks/BookmarkManager.cs create mode 100644 ILSpy/Bookmarks/IBookmark.cs create mode 100644 ILSpy/Bookmarks/MarkerBookmark.cs create mode 100644 ILSpy/Bookmarks/MemberBookmark.cs diff --git a/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs b/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs index 1e6915872..f31455d73 100644 --- a/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs +++ b/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs @@ -123,12 +123,11 @@ namespace ICSharpCode.Decompiler.Ast public void StartNode(AstNode node) { + // code mappings var ranges = node.Annotation>(); - if (ranges != null && ranges.Count > 0) - { + if (ranges != null && ranges.Count > 0) { // find the ancestor that has method mapping as annotation - if (node.Ancestors != null && node.Ancestors.Count() > 0) - { + if (node.Ancestors != null && node.Ancestors.Count() > 0) { var n = node.Ancestors.FirstOrDefault(a => a.Annotation() != null); if (n != null) { MemberMapping mapping = n.Annotation(); @@ -145,6 +144,19 @@ namespace ICSharpCode.Decompiler.Ast } } + // definitions of types and their members + Predicate predicate = n => n is TypeDeclaration || n is DelegateDeclaration || + n is FieldDeclaration || n is PropertyDeclaration || n is EventDeclaration ||n is MethodDeclaration || n is ConstructorDeclaration || + n is IndexerDeclaration || n is OperatorDeclaration; + + if (predicate(node)) { + var n = node as AttributedNode; + int c = 0; + if (n != null) + c = n.Attributes.Count; + node.AddAnnotation(Tuple.Create(output.CurrentLine + c, 0)); + } + nodeStack.Push(node); } diff --git a/ILSpy.sln b/ILSpy.sln index 7af0dee1b..78bafdde2 100644 --- a/ILSpy.sln +++ b/ILSpy.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 -# SharpDevelop 4.0.1.7146 +# SharpDevelop 4.1.0.7384-alpha Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{F45DB999-7E72-4000-B5AD-3A7B485A0896}" ProjectSection(SolutionItems) = postProject doc\Command Line.txt = doc\Command Line.txt diff --git a/ILSpy/AvalonEdit/ITextMarker.cs b/ILSpy/AvalonEdit/ITextMarker.cs new file mode 100644 index 000000000..8046bb6a9 --- /dev/null +++ b/ILSpy/AvalonEdit/ITextMarker.cs @@ -0,0 +1,123 @@ +// 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.Windows.Media; + +using ICSharpCode.ILSpy.Bookmarks; + +namespace ICSharpCode.ILSpy.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; } + + /// + /// Gets or sets if the marker is visible or not. + /// + Predicate IsVisible { get; set; } + + /// + /// Gets or sets the bookmark. + /// + IBookmark Bookmark { 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/ILSpy/AvalonEdit/IconBarManager.cs b/ILSpy/AvalonEdit/IconBarManager.cs new file mode 100644 index 000000000..3677df10b --- /dev/null +++ b/ILSpy/AvalonEdit/IconBarManager.cs @@ -0,0 +1,83 @@ +// 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.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +using ICSharpCode.ILSpy.Bookmarks; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.ILSpy.AvalonEdit +{ + /// + /// Stores the entries in the icon bar margin. Multiple icon bar margins + /// can use the same manager if split view is used. + /// + public class IconBarManager : IBookmarkMargin + { + ObservableCollection bookmarks = new ObservableCollection(); + + public IconBarManager() + { + bookmarks.CollectionChanged += bookmarks_CollectionChanged; + } + + public IList Bookmarks { + get { return bookmarks; } + } + + void bookmarks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + Redraw(); + } + + public void Redraw() + { + if (RedrawRequested != null) + RedrawRequested(this, EventArgs.Empty); + } + + public event EventHandler RedrawRequested; + + internal void UpdateClassMemberBookmarks(IEnumerable nodes) + { + this.bookmarks.Clear(); + + if (nodes == null || nodes.Count() == 0) + return; + + foreach (var n in nodes) { + switch (n.NodeType) { + case NodeType.TypeDeclaration: + case NodeType.TypeReference: + this.bookmarks.Add(new TypeBookmark(n)); + break; + case NodeType.Member: + this.bookmarks.Add(new MemberBookmark(n)); + break; + default: + // do nothing + break; + } + } + } + } +} diff --git a/ILSpy/AvalonEdit/IconBarMargin.cs b/ILSpy/AvalonEdit/IconBarMargin.cs new file mode 100644 index 000000000..f0f83848d --- /dev/null +++ b/ILSpy/AvalonEdit/IconBarMargin.cs @@ -0,0 +1,239 @@ +// 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.ComponentModel.Composition; +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; + +using ICSharpCode.AvalonEdit.Editing; +using ICSharpCode.AvalonEdit.Rendering; +using ICSharpCode.AvalonEdit.Utils; +using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.Bookmarks; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.AvalonEdit +{ + public class IconBarMargin : AbstractMargin, IDisposable + { + readonly IconBarManager manager; + + public IconBarMargin(IconBarManager manager) + { + BookmarkManager.Added += delegate { InvalidateVisual(); }; + BookmarkManager.Removed += delegate { InvalidateVisual(); }; + + this.manager = manager; + } + + public IList DecompiledMembers { get; set; } + + public virtual void Dispose() + { + this.TextView = null; // detach from TextView (will also detach from manager) + } + + /// + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) + { + // accept clicks even when clicking on the background + return new PointHitTestResult(this, hitTestParameters.HitPoint); + } + + /// + protected override Size MeasureOverride(Size availableSize) + { + return new Size(18, 0); + } + + protected override void OnRender(DrawingContext drawingContext) + { + Size renderSize = this.RenderSize; + drawingContext.DrawRectangle(SystemColors.ControlBrush, null, + new Rect(0, 0, renderSize.Width, renderSize.Height)); + drawingContext.DrawLine(new Pen(SystemColors.ControlDarkBrush, 1), + new Point(renderSize.Width - 0.5, 0), + new Point(renderSize.Width - 0.5, renderSize.Height)); + + ICSharpCode.AvalonEdit.Rendering.TextView textView = this.TextView; + if (textView != null && textView.VisualLinesValid) { + // create a dictionary line number => first bookmark + Dictionary bookmarkDict = new Dictionary(); + foreach (var bm in BookmarkManager.Bookmarks) { + if (!DecompiledMembers.Contains(bm.MemberReference)) + continue; + int line = bm.LineNumber; + IBookmark existingBookmark; + if (!bookmarkDict.TryGetValue(line, out existingBookmark) || bm.ZOrder > existingBookmark.ZOrder) + bookmarkDict[line] = bm; + } + + foreach (var bm in manager.Bookmarks) { + int line = bm.LineNumber; + IBookmark existingBookmark; + if (!bookmarkDict.TryGetValue(line, out existingBookmark) || bm.ZOrder > existingBookmark.ZOrder) + bookmarkDict[line] = bm; + } + + Size pixelSize = PixelSnapHelpers.GetPixelSize(this); + foreach (VisualLine line in textView.VisualLines) { + int lineNumber = line.FirstDocumentLine.LineNumber; + IBookmark bm; + if (bookmarkDict.TryGetValue(lineNumber, out bm)) { + Rect rect = new Rect(0, PixelSnapHelpers.Round(line.VisualTop - textView.VerticalOffset, pixelSize.Height), 16, 16); + if (dragDropBookmark == bm && dragStarted) + drawingContext.PushOpacity(0.5); + drawingContext.DrawImage(bm.Image, rect); + if (dragDropBookmark == bm && dragStarted) + drawingContext.Pop(); + } + } + if (dragDropBookmark != null && dragStarted) { + Rect rect = new Rect(0, PixelSnapHelpers.Round(dragDropCurrentPoint - 8, pixelSize.Height), 16, 16); + drawingContext.DrawImage(dragDropBookmark.Image, rect); + } + } + } + + IBookmark dragDropBookmark; // bookmark being dragged (!=null if drag'n'drop is active) + double dragDropStartPoint; + double dragDropCurrentPoint; + bool dragStarted; // whether drag'n'drop operation has started (mouse was moved minimum distance) + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + int line = GetLineFromMousePosition(e); + if (!e.Handled && line > 0) { + IBookmark bm = GetBookmarkFromLine(line); + if (bm != null) { + bm.MouseDown(e); + if (!e.Handled) { + if (e.ChangedButton == MouseButton.Left && bm.CanDragDrop && CaptureMouse()) { + StartDragDrop(bm, e); + e.Handled = true; + } + } + } + } + // don't allow selecting text through the IconBarMargin + if (e.ChangedButton == MouseButton.Left) + e.Handled = true; + } + + IBookmark GetBookmarkFromLine(int line) + { + BookmarkBase result = null; + foreach (BookmarkBase bm in BookmarkManager.Bookmarks) { + if (bm.LineNumber == line && + this.DecompiledMembers != null && this.DecompiledMembers.Contains(bm.MemberReference)) { + if (result == null || bm.ZOrder > result.ZOrder) + return result; + } + } + + return manager.Bookmarks.FirstOrDefault(b => b.LineNumber == line); + } + + protected override void OnLostMouseCapture(MouseEventArgs e) + { + CancelDragDrop(); + base.OnLostMouseCapture(e); + } + + void StartDragDrop(IBookmark bm, MouseEventArgs e) + { + dragDropBookmark = bm; + dragDropStartPoint = dragDropCurrentPoint = e.GetPosition(this).Y; + if (TextView != null) { + TextArea area = TextView.Services.GetService(typeof(TextArea)) as TextArea; + if (area != null) + area.PreviewKeyDown += TextArea_PreviewKeyDown; + } + } + + void CancelDragDrop() + { + if (dragDropBookmark != null) { + dragDropBookmark = null; + dragStarted = false; + if (TextView != null) { + TextArea area = TextView.Services.GetService(typeof(TextArea)) as TextArea; + if (area != null) + area.PreviewKeyDown -= TextArea_PreviewKeyDown; + } + ReleaseMouseCapture(); + InvalidateVisual(); + } + } + + void TextArea_PreviewKeyDown(object sender, KeyEventArgs e) + { + // any key press cancels drag'n'drop + CancelDragDrop(); + if (e.Key == Key.Escape) + e.Handled = true; + } + + int GetLineFromMousePosition(MouseEventArgs e) + { + ICSharpCode.AvalonEdit.Rendering.TextView textView = this.TextView; + if (textView == null) + return 0; + VisualLine vl = textView.GetVisualLineFromVisualTop(e.GetPosition(textView).Y + textView.ScrollOffset.Y); + if (vl == null) + return 0; + return vl.FirstDocumentLine.LineNumber; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + if (dragDropBookmark != null) { + dragDropCurrentPoint = e.GetPosition(this).Y; + if (Math.Abs(dragDropCurrentPoint - dragDropStartPoint) > SystemParameters.MinimumVerticalDragDistance) + dragStarted = true; + InvalidateVisual(); + } + } + + protected override void OnMouseUp(MouseButtonEventArgs e) + { + base.OnMouseUp(e); + int line = GetLineFromMousePosition(e); + if (!e.Handled && dragDropBookmark != null) { + if (dragStarted) { + if (line != 0) + dragDropBookmark.Drop(line); + e.Handled = true; + } + CancelDragDrop(); + } + if (!e.Handled && line != 0) { + var bm = GetBookmarkFromLine(line); + if (bm != null) { + bm.MouseUp(e); + + if (bm is BookmarkBase) { + if ((bm as BookmarkBase).CanToggle) { + BookmarkManager.RemoveMark(bm as BookmarkBase); + InvalidateVisual(); + } + } + if (e.Handled) + return; + } + if (e.ChangedButton == MouseButton.Left) { + // TODO: notify subscribers + } + InvalidateVisual(); + } + } + } +} diff --git a/ILSpy/AvalonEdit/TextEditorWeakEventManager.cs b/ILSpy/AvalonEdit/TextEditorWeakEventManager.cs new file mode 100644 index 000000000..2074763d8 --- /dev/null +++ b/ILSpy/AvalonEdit/TextEditorWeakEventManager.cs @@ -0,0 +1,67 @@ +// 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; +using ICSharpCode.AvalonEdit; +using ICSharpCode.AvalonEdit.Utils; + +namespace ICSharpCode.ILSpy.AvalonEdit +{ + public static class TextEditorWeakEventManager + { + public sealed class MouseHover : WeakEventManagerBase + { + protected override void StopListening(TextEditor source) + { + source.MouseHover -= DeliverEvent; + } + + protected override void StartListening(TextEditor source) + { + source.MouseHover += DeliverEvent; + } + } + + public sealed class MouseHoverStopped : WeakEventManagerBase + { + protected override void StopListening(TextEditor source) + { + source.MouseHoverStopped -= DeliverEvent; + } + + protected override void StartListening(TextEditor source) + { + source.MouseHoverStopped += DeliverEvent; + } + } + + public sealed class MouseDown : WeakEventManagerBase + { + protected override void StopListening(TextEditor source) + { + source.MouseDown -= DeliverEvent; + } + + protected override void StartListening(TextEditor source) + { + source.MouseDown += DeliverEvent; + } + } + } +} diff --git a/ILSpy/AvalonEdit/TextMarkerService.cs b/ILSpy/AvalonEdit/TextMarkerService.cs new file mode 100644 index 000000000..6b97b5650 --- /dev/null +++ b/ILSpy/AvalonEdit/TextMarkerService.cs @@ -0,0 +1,292 @@ +// 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.ComponentModel.Composition; +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 ICSharpCode.ILSpy.Bookmarks; + +namespace ICSharpCode.ILSpy.AvalonEdit +{ + /// + /// Handles the text markers for a code editor. + /// + public sealed class TextMarkerService : DocumentColorizingTransformer, IBackgroundRenderer, ITextMarkerService + { + TextEditor codeEditor; + + TextSegmentCollection markers = new TextSegmentCollection(); + + public TextMarkerService() + { + } + + public TextEditor CodeEditor { + get { return codeEditor; } + set { codeEditor = value; } + } + + #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) + return; + + 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).Reverse()) { + if (!marker.IsVisible(marker.Bookmark)) + continue; + + 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(ICSharpCode.AvalonEdit.Rendering.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).Reverse()) { + if (!marker.IsVisible(marker.Bookmark)) + continue; + + 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; } + + /// + public Predicate IsVisible { get; set; } + + /// + public IBookmark Bookmark { get; set; } + } +} diff --git a/ILSpy/Bookmarks/BookmarkBase.cs b/ILSpy/Bookmarks/BookmarkBase.cs new file mode 100644 index 000000000..f6b5ece5d --- /dev/null +++ b/ILSpy/Bookmarks/BookmarkBase.cs @@ -0,0 +1,97 @@ +// 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.Windows.Input; +using System.Windows.Media; + +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.Bookmarks +{ + /// + /// A bookmark that can be attached to an AvalonEdit TextDocument. + /// + public class BookmarkBase : IBookmark + { + AstLocation location; + + protected virtual void RemoveMark() + { + + } + + public AstLocation Location { + get { return location; } + set { location = value; } + } + + public event EventHandler DocumentChanged; + + protected virtual void OnDocumentChanged(EventArgs e) + { + if (DocumentChanged != null) { + DocumentChanged(this, e); + } + } + + protected virtual void Redraw() + { + + } + + public MemberReference MemberReference { get; set; } + + public int LineNumber { + get { return location.Line; } + } + + public int ColumnNumber { + get { return location.Column; } + } + + public virtual int ZOrder { + get { return 0; } + } + + /// + /// Gets if the bookmark can be toggled off using the 'set/unset bookmark' command. + /// + public virtual bool CanToggle { + get { + return true; + } + } + + public BookmarkBase(MemberReference member, AstLocation location) + { + this.MemberReference = member; + this.Location = location; + } + + public virtual ImageSource Image { + get { return null; } + } + + public virtual void MouseDown(MouseButtonEventArgs e) + { + } + + public virtual void MouseUp(MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Left && CanToggle) { + RemoveMark(); + e.Handled = true; + } + } + + public virtual bool CanDragDrop { + get { return false; } + } + + public virtual void Drop(int lineNumber) + { + } + } +} diff --git a/ILSpy/Bookmarks/BookmarkEventHandler.cs b/ILSpy/Bookmarks/BookmarkEventHandler.cs new file mode 100644 index 000000000..5dae1beac --- /dev/null +++ b/ILSpy/Bookmarks/BookmarkEventHandler.cs @@ -0,0 +1,28 @@ +// 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; + +namespace ICSharpCode.ILSpy.Bookmarks +{ + public delegate void BookmarkEventHandler(object sender, BookmarkEventArgs e); + + /// + /// Description of BookmarkEventHandler. + /// + public class BookmarkEventArgs : EventArgs + { + BookmarkBase bookmark; + + public BookmarkBase Bookmark { + get { + return bookmark; + } + } + + public BookmarkEventArgs(BookmarkBase bookmark) + { + this.bookmark = bookmark; + } + } +} diff --git a/ILSpy/Bookmarks/BookmarkManager.cs b/ILSpy/Bookmarks/BookmarkManager.cs new file mode 100644 index 000000000..65a769617 --- /dev/null +++ b/ILSpy/Bookmarks/BookmarkManager.cs @@ -0,0 +1,116 @@ +// 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 ICSharpCode.Decompiler; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; +using Mono.CSharp; + +namespace ICSharpCode.ILSpy.Bookmarks +{ + /// + /// Static class that maintains the list of bookmarks and breakpoints. + /// + public static partial class BookmarkManager + { + static List bookmarks = new List(); + + public static List Bookmarks { + get { + return bookmarks; + } + } + + public static List GetBookmarks(string typeName) + { + if (typeName == null) + throw new ArgumentNullException("typeName"); + + List marks = new List(); + + foreach (BookmarkBase mark in bookmarks) { + if (typeName == mark.MemberReference.FullName) { + marks.Add(mark); + } + } + + return marks; + } + + public static void AddMark(BookmarkBase bookmark) + { + if (bookmark == null) return; + if (bookmarks.Contains(bookmark)) return; + if (bookmarks.Exists(b => IsEqualBookmark(b, bookmark))) return; + bookmarks.Add(bookmark); + OnAdded(new BookmarkEventArgs(bookmark)); + } + + static bool IsEqualBookmark(BookmarkBase a, BookmarkBase b) + { + if (a == b) + return true; + if (a == null || b == null) + return false; + if (a.GetType() != b.GetType()) + return false; + if (a.MemberReference.FullName != b.MemberReference.FullName) + return false; + return a.LineNumber == b.LineNumber; + } + + public static void RemoveMark(BookmarkBase bookmark) + { + bookmarks.Remove(bookmark); + OnRemoved(new BookmarkEventArgs(bookmark)); + } + + public static void Clear() + { + while (bookmarks.Count > 0) { + var b = bookmarks[bookmarks.Count - 1]; + bookmarks.RemoveAt(bookmarks.Count - 1); + OnRemoved(new BookmarkEventArgs(b)); + } + } + + internal static void Initialize() + { + + } + + static void OnRemoved(BookmarkEventArgs e) + { + if (Removed != null) { + Removed(null, e); + } + } + + static void OnAdded(BookmarkEventArgs e) + { + if (Added != null) { + Added(null, e); + } + } + + public static void ToggleBookmark(string typeName, int line, + Predicate canToggle, + Func bookmarkFactory) + { + foreach (BookmarkBase bookmark in GetBookmarks(typeName)) { + if (canToggle(bookmark) && bookmark.LineNumber == line) { + BookmarkManager.RemoveMark(bookmark); + return; + } + } + + // no bookmark at that line: create a new bookmark + BookmarkManager.AddMark(bookmarkFactory(new AstLocation(line, 0))); + } + + public static event BookmarkEventHandler Removed; + public static event BookmarkEventHandler Added; + } +} diff --git a/ILSpy/Bookmarks/IBookmark.cs b/ILSpy/Bookmarks/IBookmark.cs new file mode 100644 index 000000000..37733789a --- /dev/null +++ b/ILSpy/Bookmarks/IBookmark.cs @@ -0,0 +1,69 @@ +// 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.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Windows.Input; +using System.Windows.Media; + +namespace ICSharpCode.ILSpy.Bookmarks +{ + /// + /// The bookmark margin. + /// + public interface IBookmarkMargin + { + /// + /// Gets the list of bookmarks. + /// + IList Bookmarks { get; } + + /// + /// Redraws the bookmark margin. Bookmarks need to call this method when the Image changes. + /// + void Redraw(); + } + + /// + /// Represents a bookmark in the bookmark margin. + /// + public interface IBookmark + { + /// + /// Gets the line number of the bookmark. + /// + int LineNumber { get; } + + /// + /// Gets the image. + /// + ImageSource Image { get; } + + /// + /// Gets the Z-Order of the bookmark icon. + /// + int ZOrder { get; } + + /// + /// Handles the mouse down event. + /// + void MouseDown(MouseButtonEventArgs e); + + /// + /// Handles the mouse up event. + /// + void MouseUp(MouseButtonEventArgs e); + + /// + /// Gets whether this bookmark can be dragged around. + /// + bool CanDragDrop { get; } + + /// + /// Notifies the bookmark that it was dropped on the specified line. + /// + void Drop(int lineNumber); + } +} diff --git a/ILSpy/Bookmarks/MarkerBookmark.cs b/ILSpy/Bookmarks/MarkerBookmark.cs new file mode 100644 index 000000000..4a587b855 --- /dev/null +++ b/ILSpy/Bookmarks/MarkerBookmark.cs @@ -0,0 +1,21 @@ +// 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 ICSharpCode.NRefactory.CSharp; +using ICSharpCode.ILSpy.AvalonEdit; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.Bookmarks +{ + public abstract class MarkerBookmark : BookmarkBase + { + public MarkerBookmark(MemberReference member, AstLocation location) : base(member, location) + { + } + + public ITextMarker Marker { get; set; } + + public abstract ITextMarker CreateMarker(ITextMarkerService markerService, int offset, int length); + } +} diff --git a/ILSpy/Bookmarks/MemberBookmark.cs b/ILSpy/Bookmarks/MemberBookmark.cs new file mode 100644 index 000000000..27ff141cb --- /dev/null +++ b/ILSpy/Bookmarks/MemberBookmark.cs @@ -0,0 +1,162 @@ +// 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.Windows; +using System.Windows.Input; +using System.Windows.Media; + +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.TypeSystem; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.Bookmarks +{ + /// + /// Bookmark used to give additional operations for class members. + /// Does not derive from SDBookmark because it is not stored in the central BookmarkManager, + /// but only in the document's BookmarkManager. + /// + public class MemberBookmark : IBookmark + { + AstNode node; + + public AstNode Node { + get { + return node; + } + } + + public MemberBookmark(AstNode node) + { + this.node = node; + } + + public virtual ImageSource Image { + get { + var attrNode = (AttributedNode)node; + if (node is FieldDeclaration) + return GetMemberOverlayedImage(attrNode, MemberIcon.Field); + + if (node is PropertyDeclaration) + return GetMemberOverlayedImage(attrNode, MemberIcon.Property); + + if (node is EventDeclaration) + return GetMemberOverlayedImage(attrNode, MemberIcon.Event); + + if (node is IndexerDeclaration) + return GetMemberOverlayedImage(attrNode, MemberIcon.Indexer); + + if (node is OperatorDeclaration) + return GetMemberOverlayedImage(attrNode, MemberIcon.Operator); + + return GetMemberOverlayedImage(attrNode, MemberIcon.Method); + } + } + + ImageSource GetMemberOverlayedImage(AttributedNode attrNode, MemberIcon icon) + { + switch (attrNode.Modifiers & Modifiers.VisibilityMask) { + case Modifiers.Protected: + return Images.GetIcon(icon, AccessOverlayIcon.Protected, (attrNode.Modifiers & Modifiers.Static) == Modifiers.Static); + case Modifiers.Private: + return Images.GetIcon(icon, AccessOverlayIcon.Private, (attrNode.Modifiers & Modifiers.Static) == Modifiers.Static); + case Modifiers.Internal: + return Images.GetIcon(icon, AccessOverlayIcon.Internal, (attrNode.Modifiers & Modifiers.Static) == Modifiers.Static); + } + + return Images.GetIcon(icon, AccessOverlayIcon.Public, (attrNode.Modifiers & Modifiers.Static) == Modifiers.Static); + } + + public int LineNumber { + get { + var t = node.Annotation>(); + if (t != null) + return t.Item1; + return 0; + } + } + + public virtual void MouseDown(MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Left) { + // TODO: menu items + } + } + + public virtual void MouseUp(MouseButtonEventArgs e) + { + } + + int IBookmark.ZOrder { + get { return -10; } + } + + bool IBookmark.CanDragDrop { + get { return false; } + } + + void IBookmark.Drop(int lineNumber) + { + throw new NotSupportedException(); + } + } + + public class TypeBookmark : MemberBookmark + { + public TypeBookmark(AstNode node) : base (node) + { + } + + public override ImageSource Image { + get { + var attrNode = (AttributedNode)Node; + + if (Node is DelegateDeclaration) + return GetTypeOverlayedImage(attrNode, TypeIcon.Delegate); + + if (Node is TypeDeclaration) { + var n = Node as TypeDeclaration; + switch (n.ClassType) + { + case ClassType.Delegate: + return GetTypeOverlayedImage(attrNode, TypeIcon.Delegate); + case ClassType.Enum: + return GetTypeOverlayedImage(attrNode, TypeIcon.Enum); + case ClassType.Struct: + return GetTypeOverlayedImage(attrNode, TypeIcon.Struct); + case ClassType.Interface: + return GetTypeOverlayedImage(attrNode, TypeIcon.Interface); + } + } + + return GetTypeOverlayedImage(attrNode, TypeIcon.Class); + } + } + + public override void MouseDown(MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Left) { + // TODO: menu items + } + } + + public override void MouseUp(MouseButtonEventArgs e) + { + } + + ImageSource GetTypeOverlayedImage(AttributedNode attrNode, TypeIcon icon) + { + switch (attrNode.Modifiers & Modifiers.VisibilityMask) { + case Modifiers.Protected: + return Images.GetIcon(icon, AccessOverlayIcon.Protected); + case Modifiers.Private: + return Images.GetIcon(icon, AccessOverlayIcon.Private); + case Modifiers.Internal: + return Images.GetIcon(icon, AccessOverlayIcon.Internal); + } + + return Images.GetIcon(icon, AccessOverlayIcon.Public); + } + } +} diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index 50afed4ea..c0fee6954 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -23,11 +23,11 @@ using System.ComponentModel.Composition; using System.IO; using System.Linq; using System.Resources; -using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xaml; using System.Xml; + using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Ast; using ICSharpCode.Decompiler.Ast.Transforms; @@ -91,6 +91,7 @@ namespace ICSharpCode.ILSpy AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: method.DeclaringType, isSingleMember: true); codeDomBuilder.AddMethod(method); RunTransformsAndGenerateCode(codeDomBuilder, output, options); + NotifyDecompilationFinished(codeDomBuilder); } public override void DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options) @@ -99,6 +100,7 @@ namespace ICSharpCode.ILSpy AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: property.DeclaringType, isSingleMember: true); codeDomBuilder.AddProperty(property); RunTransformsAndGenerateCode(codeDomBuilder, output, options); + NotifyDecompilationFinished(codeDomBuilder); } public override void DecompileField(FieldDefinition field, ITextOutput output, DecompilationOptions options) @@ -107,6 +109,7 @@ namespace ICSharpCode.ILSpy AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: field.DeclaringType, isSingleMember: true); codeDomBuilder.AddField(field); RunTransformsAndGenerateCode(codeDomBuilder, output, options); + NotifyDecompilationFinished(codeDomBuilder); } public override void DecompileEvent(EventDefinition ev, ITextOutput output, DecompilationOptions options) @@ -115,6 +118,7 @@ namespace ICSharpCode.ILSpy AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: ev.DeclaringType, isSingleMember: true); codeDomBuilder.AddEvent(ev); RunTransformsAndGenerateCode(codeDomBuilder, output, options); + NotifyDecompilationFinished(codeDomBuilder); } public override void DecompileType(TypeDefinition type, ITextOutput output, DecompilationOptions options) @@ -122,6 +126,7 @@ namespace ICSharpCode.ILSpy AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: type); codeDomBuilder.AddType(type); RunTransformsAndGenerateCode(codeDomBuilder, output, options); + NotifyDecompilationFinished(codeDomBuilder); } void RunTransformsAndGenerateCode(AstBuilder astBuilder, ITextOutput output, DecompilationOptions options) @@ -149,6 +154,7 @@ namespace ICSharpCode.ILSpy codeDomBuilder.GenerateCode(output); } } + OnDecompilationFinished(null); } #region WriteProjectFile diff --git a/ILSpy/ILLanguage.cs b/ILSpy/ILLanguage.cs index e77497fa5..5033635ff 100644 --- a/ILSpy/ILLanguage.cs +++ b/ILSpy/ILLanguage.cs @@ -18,8 +18,6 @@ using System; using System.Collections.Generic; -using System.IO; - using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Disassembler; using Mono.Cecil; @@ -52,32 +50,43 @@ namespace ICSharpCode.ILSpy public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) { - new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleMethod(method); + var dis = new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken); + dis.DisassembleMethod(method); + NotifyDecompilationFinished(dis); } public override void DecompileField(FieldDefinition field, ITextOutput output, DecompilationOptions options) { - new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleField(field); + var dis = new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken); + dis.DisassembleField(field); + NotifyDecompilationFinished(dis); } public override void DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options) { - new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleProperty(property); + var dis = new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken); + dis.DisassembleProperty(property); + NotifyDecompilationFinished(dis); } public override void DecompileEvent(EventDefinition ev, ITextOutput output, DecompilationOptions options) { - new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleEvent(ev); + var dis = new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken); + dis.DisassembleEvent(ev); + NotifyDecompilationFinished(dis); } public override void DecompileType(TypeDefinition type, ITextOutput output, DecompilationOptions options) { - new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleType(type); + var dis = new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken); + dis.DisassembleType(type); + NotifyDecompilationFinished(dis); } public override void DecompileNamespace(string nameSpace, IEnumerable types, ITextOutput output, DecompilationOptions options) { new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleNamespace(nameSpace, types); + OnDecompilationFinished(null); } public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options) @@ -86,6 +95,7 @@ namespace ICSharpCode.ILSpy output.WriteLine(); new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).WriteAssemblyHeader(assembly.AssemblyDefinition); + OnDecompilationFinished(null); } public override string TypeToString(TypeReference t, bool includeNamespace, ICustomAttributeProvider attributeProvider) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index e14465833..8452df456 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -93,7 +93,18 @@ + + + + + + + + + + + @@ -304,6 +315,9 @@ ICSharpCode.TreeView - + + + + \ No newline at end of file diff --git a/ILSpy/Language.cs b/ILSpy/Language.cs index 539de51c8..0a2523d17 100644 --- a/ILSpy/Language.cs +++ b/ILSpy/Language.cs @@ -17,20 +17,57 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.Composition.Hosting; using System.Linq; + using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Ast; +using ICSharpCode.Decompiler.Disassembler; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; using Mono.Cecil; namespace ICSharpCode.ILSpy { + /// + /// Decompilation event arguments. + /// + public sealed class DecompileEventArgs : EventArgs + { + /// + /// Gets ot sets the code mappings + /// + public Dictionary> CodeMappings { get; set; } + + /// + /// Gets or sets the local variables. + /// + public ConcurrentDictionary> LocalVariables { get; set; } + + /// + /// Gets the list of MembeReferences that are decompiled (TypeDefinitions, MethodDefinitions, etc) + /// + public Dictionary DecompiledMemberReferences { get; set; } + + /// + /// Gets (or internal sets) the AST nodes. + /// + public IEnumerable AstNodes { get; internal set; } + } + /// /// Base class for language-specific decompiler implementations. /// public abstract class Language { + /// + /// Decompile finished event. + /// + public event EventHandler DecompileFinished; + /// /// Gets the name of the language (as shown in the UI) /// @@ -82,6 +119,7 @@ namespace ICSharpCode.ILSpy public virtual void DecompileNamespace(string nameSpace, IEnumerable types, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, nameSpace); + OnDecompilationFinished(null); } public virtual void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options) @@ -137,6 +175,40 @@ namespace ICSharpCode.ILSpy { return true; } + + protected virtual void OnDecompilationFinished(DecompileEventArgs e) + { + if (DecompileFinished != null) { + DecompileFinished(this, e); + } + } + + protected void NotifyDecompilationFinished(BaseCodeMappings b) + { + if (b is AstBuilder) { + var builder = b as AstBuilder; + OnDecompilationFinished(new DecompileEventArgs { + CodeMappings = builder.CodeMappings, + LocalVariables = builder.LocalVariables, + DecompiledMemberReferences = builder.DecompiledMemberReferences, + AstNodes = builder.CompilationUnit.GetNodesWithLineNumbers(n => + n is TypeDeclaration || n is DelegateDeclaration || + n is FieldDeclaration || n is PropertyDeclaration || + n is EventDeclaration || n is MethodDeclaration || + n is ConstructorDeclaration || + n is IndexerDeclaration || n is OperatorDeclaration) + }); + } + + if (b is ReflectionDisassembler) { + var dis = b as ReflectionDisassembler; + OnDecompilationFinished(new DecompileEventArgs { + CodeMappings = dis.CodeMappings, + DecompiledMemberReferences = dis.DecompiledMemberReferences, + AstNodes = null // TODO: how can I find the AST nodes? + }); + } + } } public static class Languages @@ -174,4 +246,4 @@ namespace ICSharpCode.ILSpy return AllLanguages.FirstOrDefault(l => l.Name == name) ?? AllLanguages.First(); } } -} +} \ No newline at end of file diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 1241037b1..2d355e006 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -34,12 +34,14 @@ using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using System.Xml; + using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting.Xshd; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.AvalonEdit; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.XmlDoc; using ICSharpCode.NRefactory.Documentation; @@ -64,6 +66,10 @@ namespace ICSharpCode.ILSpy.TextView DefinitionLookup definitionLookup; CancellationTokenSource currentCancellationTokenSource; + readonly IconBarManager manager; + readonly IconBarMargin iconMargin; + readonly TextMarkerService textMarkerService; + #region Constructor public DecompilerTextView() { @@ -85,6 +91,16 @@ namespace ICSharpCode.ILSpy.TextView textEditor.Options.RequireControlModifierForHyperlinkClick = false; textEditor.TextArea.TextView.MouseHover += TextViewMouseHover; textEditor.TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped; + + // add marker service & margin + iconMargin = new IconBarMargin((manager = new IconBarManager())); + textMarkerService = new TextMarkerService(); + textMarkerService.CodeEditor = textEditor; + textEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService); + textEditor.TextArea.TextView.LineTransformers.Add(textMarkerService); + + textEditor.TextArea.LeftMargins.Add(iconMargin); + textEditor.TextArea.TextView.VisualLinesChanged += delegate { iconMargin.InvalidateVisual(); }; } #endregion @@ -339,11 +355,13 @@ namespace ICSharpCode.ILSpy.TextView output.WriteLine(ex.ToString()); } ShowOutput(output); + } finally { + iconMargin.InvalidateVisual(); } }); } - static Task DecompileAsync(DecompilationContext context, int outputLengthLimit) + Task DecompileAsync(DecompilationContext context, int outputLengthLimit) { Debug.WriteLine("Start decompilation of {0} tree nodes", context.TreeNodes.Length); @@ -388,9 +406,10 @@ namespace ICSharpCode.ILSpy.TextView return tcs.Task; } - static void DecompileNodes(DecompilationContext context, ITextOutput textOutput) + void DecompileNodes(DecompilationContext context, ITextOutput textOutput) { var nodes = context.TreeNodes; + context.Language.DecompileFinished += Language_DecompileFinished; for (int i = 0; i < nodes.Length; i++) { if (i > 0) textOutput.WriteLine(); @@ -398,6 +417,20 @@ namespace ICSharpCode.ILSpy.TextView context.Options.CancellationToken.ThrowIfCancellationRequested(); nodes[i].Decompile(context.Language, textOutput, context.Options); } + context.Language.DecompileFinished -= Language_DecompileFinished; + } + + void Language_DecompileFinished(object sender, DecompileEventArgs e) + { + if (e != null) { + manager.UpdateClassMemberBookmarks(e.AstNodes); + if (iconMargin.DecompiledMembers == null) { + iconMargin.DecompiledMembers = new List(); + } + iconMargin.DecompiledMembers.AddRange(e.DecompiledMemberReferences.Values.AsEnumerable()); + } else { + manager.UpdateClassMemberBookmarks(null); + } } #endregion diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/CompilationUnit.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/CompilationUnit.cs index 9a5047719..94e168140 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/CompilationUnit.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/CompilationUnit.cs @@ -25,6 +25,9 @@ // THE SOFTWARE. using System; using System.Collections.Generic; +using System.Linq; + +using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.NRefactory.CSharp { @@ -101,6 +104,21 @@ namespace ICSharpCode.NRefactory.CSharp } } + /// + /// Gets all nodes in that are satisfing a predicate. + /// + /// Predicate to filter the nodes. + /// + public IEnumerable GetNodesWithLineNumbers(Predicate predicate) + { + if (predicate == null) + throw new ArgumentNullException("predicate"); + + return TreeTraversal + .PreOrder((AstNode)this, n => n.Children) + .Where(n => predicate(n) && n.Annotation>() != null); + } + public override S AcceptVisitor (IAstVisitor visitor, T data) { return visitor.VisitCompilationUnit (this, data);