Browse Source

Code cleanup.

pull/1/head
Daniel Grunwald 14 years ago
parent
commit
1093ba83df
  1. 2
      ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs
  2. 2
      ILSpy/AboutDialog.xaml
  3. 2
      ILSpy/AboutDialog.xaml.cs
  4. 93
      ILSpy/AssemblyList.cs
  5. 13
      ILSpy/AssemblyListManager.cs
  6. 4
      ILSpy/DecompilationOptions.cs
  7. 6
      ILSpy/ExtensionMethods.cs
  8. 21
      ILSpy/FilterSettings.cs
  9. 6
      ILSpy/GacInterop.cs
  10. 7
      ILSpy/ILLanguage.cs
  11. 3
      ILSpy/ILSpy.csproj
  12. 19
      ILSpy/ILSpySettings.cs
  13. 20
      ILSpy/ISmartTextOutput.cs
  14. 39
      ILSpy/Language.cs
  15. 4
      ILSpy/MainWindow.xaml
  16. 11
      ILSpy/MainWindow.xaml.cs
  17. 2
      ILSpy/SessionSettings.cs
  18. 91
      ILSpy/TextView/AvalonEditTextOutput.cs
  19. 1
      ILSpy/TextView/CaretHighlightAdorner.cs
  20. 121
      ILSpy/TextView/DecompilerTextView.cs
  21. 22
      ILSpy/TextView/ReferenceElementGenerator.cs
  22. 11
      ILSpy/TextView/UIElementGenerator.cs
  23. 3
      ILSpy/TreeNodes/AssemblyTreeNode.cs

2
ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs

@ -235,7 +235,7 @@ namespace ICSharpCode.Decompiler.Disassembler @@ -235,7 +235,7 @@ namespace ICSharpCode.Decompiler.Disassembler
case "System.Int32":
return "int32";
case "System.Int64":
return "int65";
return "int64";
case "System.Byte":
return "uint8";
case "System.UInt16":

2
ILSpy/AboutDialog.xaml

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
<Window x:Class="ICSharpCode.ILSpy.AboutDialog"
<Window x:Class="ICSharpCode.ILSpy.AboutDialog" x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ICSharpCode.ILSpy"

2
ILSpy/AboutDialog.xaml.cs

@ -31,7 +31,7 @@ namespace ICSharpCode.ILSpy @@ -31,7 +31,7 @@ namespace ICSharpCode.ILSpy
/// <summary>
/// Interaction logic for AboutDialog.xaml
/// </summary>
public partial class AboutDialog : Window
partial class AboutDialog : Window
{
public AboutDialog()
{

93
ILSpy/AssemblyList.cs

@ -32,24 +32,45 @@ namespace ICSharpCode.ILSpy @@ -32,24 +32,45 @@ namespace ICSharpCode.ILSpy
/// <summary>
/// A list of assemblies.
/// </summary>
class AssemblyList
sealed class AssemblyList
{
readonly string listName;
/// <summary>Dirty flag, used to mark modifications so that the list is saved later</summary>
bool dirty;
/// <summary>
/// The assemblies in this list.
/// </summary>
public readonly ObservableCollection<AssemblyTreeNode> Assemblies = new ObservableCollection<AssemblyTreeNode>();
/// <summary>
/// Dictionary for quickly finding types (used in hyperlink navigation)
/// </summary>
readonly ConcurrentDictionary<TypeDefinition, TypeTreeNode> typeDict = new ConcurrentDictionary<TypeDefinition, TypeTreeNode>();
public AssemblyList(string listName)
{
this.ListName = listName;
this.listName = listName;
Assemblies.CollectionChanged += Assemblies_CollectionChanged;
}
/// <summary>
/// Loads an assembly list from XML.
/// </summary>
public AssemblyList(XElement listElement)
: this((string)listElement.Attribute("name"))
{
foreach (var asm in listElement.Elements("Assembly")) {
OpenAssembly((string)asm);
}
this.Dirty = false; // OpenAssembly() sets dirty, so reset it since we just loaded...
this.dirty = false; // OpenAssembly() sets dirty, so reset it afterwards
}
public XElement Save()
/// <summary>
/// Saves this assembly list to XML.
/// </summary>
public XElement SaveAsXml()
{
return new XElement(
"List",
@ -58,34 +79,45 @@ namespace ICSharpCode.ILSpy @@ -58,34 +79,45 @@ namespace ICSharpCode.ILSpy
);
}
public bool Dirty { get; set; }
public string ListName { get; set; }
public readonly ObservableCollection<AssemblyTreeNode> Assemblies = new ObservableCollection<AssemblyTreeNode>();
/// <summary>
/// Gets the name of this list.
/// </summary>
public string ListName {
get { return listName; }
}
ConcurrentDictionary<TypeDefinition, TypeTreeNode> typeDict = new ConcurrentDictionary<TypeDefinition, TypeTreeNode>();
void Assemblies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.Dirty = true;
App.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(
delegate {
if (this.Dirty) {
this.Dirty = false;
// Whenever the assembly list is modified, mark it as dirty
// and enqueue a task that saves it once the UI has finished modifying the assembly list.
if (!dirty) {
dirty = true;
App.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(
delegate {
dirty = false;
AssemblyListManager.SaveList(this);
}
})
);
})
);
}
}
/// <summary>
/// Registers a type node in the dictionary for quick type lookup.
/// </summary>
/// <remarks>This method is called by the assembly loading code (on a background thread)</remarks>
public void RegisterTypeNode(TypeTreeNode node)
{
// called on background loading thread, so we need to use a ConcurrentDictionary
typeDict[node.TypeDefinition] = node;
}
#region Find*Node
/// <summary>
/// Looks up the type node corresponding to the type definition.
/// Returns null if no matching node is found.
/// </summary>
public TypeTreeNode FindTypeNode(TypeDefinition def)
{
if (def == null)
@ -109,6 +141,10 @@ namespace ICSharpCode.ILSpy @@ -109,6 +141,10 @@ namespace ICSharpCode.ILSpy
return null;
}
/// <summary>
/// Looks up the method node corresponding to the method definition.
/// Returns null if no matching node is found.
/// </summary>
public MethodTreeNode FindMethodNode(MethodDefinition def)
{
if (def == null)
@ -129,6 +165,10 @@ namespace ICSharpCode.ILSpy @@ -129,6 +165,10 @@ namespace ICSharpCode.ILSpy
return null;
}
/// <summary>
/// Looks up the field node corresponding to the field definition.
/// Returns null if no matching node is found.
/// </summary>
public FieldTreeNode FindFieldNode(FieldDefinition def)
{
if (def == null)
@ -138,6 +178,10 @@ namespace ICSharpCode.ILSpy @@ -138,6 +178,10 @@ namespace ICSharpCode.ILSpy
return typeNode.VisibleChildren.OfType<FieldTreeNode>().FirstOrDefault(m => m.FieldDefinition == def);
}
/// <summary>
/// Looks up the property node corresponding to the property definition.
/// Returns null if no matching node is found.
/// </summary>
public PropertyTreeNode FindPropertyNode(PropertyDefinition def)
{
if (def == null)
@ -147,6 +191,10 @@ namespace ICSharpCode.ILSpy @@ -147,6 +191,10 @@ namespace ICSharpCode.ILSpy
return typeNode.VisibleChildren.OfType<PropertyTreeNode>().FirstOrDefault(m => m.PropertyDefinition == def);
}
/// <summary>
/// Looks up the event node corresponding to the event definition.
/// Returns null if no matching node is found.
/// </summary>
public EventTreeNode FindEventNode(EventDefinition def)
{
if (def == null)
@ -155,7 +203,12 @@ namespace ICSharpCode.ILSpy @@ -155,7 +203,12 @@ namespace ICSharpCode.ILSpy
typeNode.EnsureLazyChildren();
return typeNode.VisibleChildren.OfType<EventTreeNode>().FirstOrDefault(m => m.EventDefinition == def);
}
#endregion
/// <summary>
/// Opens an assembly from disk.
/// Returns the existing assembly node if it is already loaded.
/// </summary>
public AssemblyTreeNode OpenAssembly(string file)
{
App.Current.Dispatcher.VerifyAccess();

13
ILSpy/AssemblyListManager.cs

@ -25,6 +25,8 @@ namespace ICSharpCode.ILSpy @@ -25,6 +25,8 @@ namespace ICSharpCode.ILSpy
{
/// <summary>
/// Manages the available assembly lists.
///
/// Contains the list of list names; and provides methods for loading/saving and creating/deleting lists.
/// </summary>
sealed class AssemblyListManager
{
@ -38,6 +40,10 @@ namespace ICSharpCode.ILSpy @@ -38,6 +40,10 @@ namespace ICSharpCode.ILSpy
public readonly ObservableCollection<string> AssemblyLists = new ObservableCollection<string>();
/// <summary>
/// Loads an assembly list from the ILSpySettings.
/// If no list with the specified name is found, the default list is loaded instead.
/// </summary>
public AssemblyList LoadList(ILSpySettings spySettings, string listName)
{
AssemblyList list = DoLoadList(spySettings, listName);
@ -63,6 +69,9 @@ namespace ICSharpCode.ILSpy @@ -63,6 +69,9 @@ namespace ICSharpCode.ILSpy
return new AssemblyList(listName ?? "(Default)");
}
/// <summary>
/// Saves the specifies assembly list into the config file.
/// </summary>
public static void SaveList(AssemblyList list)
{
ILSpySettings.Update(
@ -74,9 +83,9 @@ namespace ICSharpCode.ILSpy @@ -74,9 +83,9 @@ namespace ICSharpCode.ILSpy
}
XElement listElement = doc.Elements("List").FirstOrDefault(e => (string)e.Attribute("name") == list.ListName);
if (listElement != null)
listElement.ReplaceWith(list.Save());
listElement.ReplaceWith(list.SaveAsXml());
else
doc.Add(list.Save());
doc.Add(list.SaveAsXml());
});
}
}

4
ILSpy/DecompilationOptions.cs

@ -35,6 +35,10 @@ namespace ICSharpCode.ILSpy @@ -35,6 +35,10 @@ namespace ICSharpCode.ILSpy
/// <summary>
/// Gets the cancellation token that is used to abort the decompiler.
/// </summary>
/// <remarks>
/// Decompilers should regularly call <c>options.CancellationToken.ThrowIfCancellationRequested();</c>
/// to allow for cooperative cancellation of the decompilation task.
/// </remarks>
public CancellationToken CancellationToken { get; set; }
}
}

6
ILSpy/ExtensionMethods.cs

@ -25,7 +25,7 @@ using Mono.Cecil; @@ -25,7 +25,7 @@ using Mono.Cecil;
namespace ICSharpCode.ILSpy
{
/// <summary>
/// ExtensionMethods that help with WPF.
/// ExtensionMethods used in ILSpy.
/// </summary>
public static class ExtensionMethods
{
@ -42,7 +42,7 @@ namespace ICSharpCode.ILSpy @@ -42,7 +42,7 @@ namespace ICSharpCode.ILSpy
/// having a XAML file as context.</remarks>
public static void SetValueToExtension(this DependencyObject targetObject, DependencyProperty property, MarkupExtension markupExtension)
{
// This method was copied from ICSharpCode.Core.Presentation
// This method was copied from ICSharpCode.Core.Presentation (with permission to switch license to X11)
if (targetObject == null)
throw new ArgumentNullException("targetObject");
@ -57,7 +57,7 @@ namespace ICSharpCode.ILSpy @@ -57,7 +57,7 @@ namespace ICSharpCode.ILSpy
sealed class SetValueToExtensionServiceProvider : IServiceProvider, IProvideValueTarget
{
// This class was copied from ICSharpCode.Core.Presentation
// This class was copied from ICSharpCode.Core.Presentation (with permission to switch license to X11)
readonly DependencyObject targetObject;
readonly DependencyProperty targetProperty;

21
ILSpy/FilterSettings.cs

@ -28,8 +28,8 @@ namespace ICSharpCode.ILSpy @@ -28,8 +28,8 @@ namespace ICSharpCode.ILSpy
/// </summary>
/// <remarks>
/// This class is mutable; but the ILSpyTreeNode filtering assumes that filter settings are immutable.
/// Thus, the main window will use one mutable instance (for data-binding), and assign a new clone to the ILSpyTreeNodes whenever the main
/// mutable instance changes.
/// Thus, the main window will use one mutable instance (for data-binding), and will assign a new
/// clone to the ILSpyTreeNodes whenever the main mutable instance changes.
/// </remarks>
public class FilterSettings : INotifyPropertyChanged
{
@ -50,6 +50,10 @@ namespace ICSharpCode.ILSpy @@ -50,6 +50,10 @@ namespace ICSharpCode.ILSpy
string searchTerm;
/// <summary>
/// Gets/Sets the search term.
/// Only tree nodes containing the search term will be shown.
/// </summary>
public string SearchTerm {
get { return searchTerm; }
set {
@ -60,6 +64,9 @@ namespace ICSharpCode.ILSpy @@ -60,6 +64,9 @@ namespace ICSharpCode.ILSpy
}
}
/// <summary>
/// Gets whether a node with the specified text is matched by the current search term.
/// </summary>
public bool SearchTermMatches(string text)
{
if (string.IsNullOrEmpty(searchTerm))
@ -69,6 +76,9 @@ namespace ICSharpCode.ILSpy @@ -69,6 +76,9 @@ namespace ICSharpCode.ILSpy
bool showInternalApi;
/// <summary>
/// Gets/Sets whether internal API members should be shown.
/// </summary>
public bool ShowInternalApi {
get { return showInternalApi; }
set {
@ -81,6 +91,13 @@ namespace ICSharpCode.ILSpy @@ -81,6 +91,13 @@ namespace ICSharpCode.ILSpy
Language language;
/// <summary>
/// Gets/Sets the current language.
/// </summary>
/// <remarks>
/// While this isn't related to filtering, having it as part of the FilterSettings
/// makes it easy to pass it down into all tree nodes.
/// </remarks>
public Language Language {
get { return language; }
set {

6
ILSpy/GacInterop.cs

@ -30,6 +30,9 @@ namespace ICSharpCode.ILSpy @@ -30,6 +30,9 @@ namespace ICSharpCode.ILSpy
/// </summary>
public static class GacInterop
{
/// <summary>
/// Gets the names of all assemblies in the GAC.
/// </summary>
public static IEnumerable<AssemblyNameReference> GetGacAssemblyFullNames()
{
IApplicationContext applicationContext = null;
@ -88,6 +91,9 @@ namespace ICSharpCode.ILSpy @@ -88,6 +91,9 @@ namespace ICSharpCode.ILSpy
static readonly string[] gacs = { "GAC_MSIL", "GAC_32", "GAC" };
static readonly string[] prefixes = { string.Empty, "v4.0_" };
/// <summary>
/// Gets the file name for an assembly stored in the GAC.
/// </summary>
public static string FindAssemblyInNetGac (AssemblyNameReference reference)
{
// without public key, it can't be in the GAC

7
ILSpy/ILLanguage.cs

@ -26,6 +26,13 @@ using Mono.Cecil; @@ -26,6 +26,13 @@ using Mono.Cecil;
namespace ICSharpCode.ILSpy
{
/// <summary>
/// IL language support.
/// </summary>
/// <remarks>
/// Currently comes in two versions:
/// flat IL (detectControlStructure=false) and structured IL (detectControlStructure=true).
/// </remarks>
public class ILLanguage : Language
{
bool detectControlStructure;

3
ILSpy/ILSpy.csproj

@ -91,6 +91,7 @@ @@ -91,6 +91,7 @@
<Compile Include="GacInterop.cs" />
<Compile Include="ILLanguage.cs" />
<Compile Include="ILSpySettings.cs" />
<Compile Include="ISmartTextOutput.cs" />
<Compile Include="Language.cs" />
<Compile Include="Images\Images.cs" />
<Compile Include="Mono.Cecil.Rocks\MethodBodyRocks.cs" />
@ -115,7 +116,7 @@ @@ -115,7 +116,7 @@
<Compile Include="TextView\DecompilerTextView.cs" />
<Compile Include="TextView\OutputLengthExceededException.cs" />
<Compile Include="TextView\ReferenceElementGenerator.cs" />
<Compile Include="TextView\SmartTextOutput.cs" />
<Compile Include="TextView\AvalonEditTextOutput.cs" />
<Compile Include="TextView\UIElementGenerator.cs" />
<Compile Include="TreeNodes\AssemblyListTreeNode.cs" />
<Compile Include="TreeNodes\AssemblyReferenceTreeNode.cs" />

19
ILSpy/ILSpySettings.cs

@ -48,6 +48,12 @@ namespace ICSharpCode.ILSpy @@ -48,6 +48,12 @@ namespace ICSharpCode.ILSpy
}
}
/// <summary>
/// Loads the settings file from disk.
/// </summary>
/// <returns>
/// An instance used to access the loaded settings.
/// </returns>
public static ILSpySettings Load()
{
using (new MutexProtector(ConfigFileMutex)) {
@ -62,6 +68,9 @@ namespace ICSharpCode.ILSpy @@ -62,6 +68,9 @@ namespace ICSharpCode.ILSpy
}
}
/// <summary>
/// Saves a setting section.
/// </summary>
public static void SaveSettings(XElement section)
{
Update(
@ -74,6 +83,11 @@ namespace ICSharpCode.ILSpy @@ -74,6 +83,11 @@ namespace ICSharpCode.ILSpy
});
}
/// <summary>
/// Updates the saved settings.
/// We always reload the file on updates to ensure we aren't overwriting unrelated changes performed
/// by another ILSpy instance.
/// </summary>
public static void Update(Action<XElement> action)
{
using (new MutexProtector(ConfigFileMutex)) {
@ -94,13 +108,16 @@ namespace ICSharpCode.ILSpy @@ -94,13 +108,16 @@ namespace ICSharpCode.ILSpy
}
}
public static string GetConfigFile()
static string GetConfigFile()
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ICSharpCode\\ILSpy.xml");
}
const string ConfigFileMutex = "01A91708-49D1-410D-B8EB-4DE2662B3971";
/// <summary>
/// Helper class for serializing access to the config file when multiple ILSpy instances are running.
/// </summary>
sealed class MutexProtector : IDisposable
{
Mutex mutex;

20
ILSpy/ISmartTextOutput.cs

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Windows;
using ICSharpCode.Decompiler;
namespace ICSharpCode.ILSpy
{
/// <summary>
/// Adds additional WPF-specific output features to <see cref="ITextOutput"/>.
/// </summary>
public interface ISmartTextOutput : ITextOutput
{
/// <summary>
/// Inserts an interactive UI element at the current position in the text output.
/// </summary>
void AddUIElement(Func<UIElement> element);
}
}

39
ILSpy/Language.cs

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using ICSharpCode.Decompiler;
using Mono.Cecil;
@ -29,11 +30,23 @@ namespace ICSharpCode.ILSpy @@ -29,11 +30,23 @@ namespace ICSharpCode.ILSpy
/// </summary>
public abstract class Language
{
/// <summary>
/// Gets the name of the language (as shown in the UI)
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Gets the file extension used by source code files in this language.
/// </summary>
public abstract string FileExtension { get; }
/// <summary>
/// Gets the syntax highlighting used for this language.
/// </summary>
public virtual ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition SyntaxHighlighting {
get { return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinitionByExtension(this.FileExtension); }
get {
return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinitionByExtension(this.FileExtension);
}
}
public virtual void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options)
@ -64,11 +77,17 @@ namespace ICSharpCode.ILSpy @@ -64,11 +77,17 @@ namespace ICSharpCode.ILSpy
{
}
/// <summary>
/// Converts a type reference into a string. This method is used by the member tree node for parameter and return types.
/// </summary>
public virtual string TypeToString(TypeReference t)
{
return t.Name;
}
/// <summary>
/// Used for WPF keyboard navigation.
/// </summary>
public override string ToString()
{
return Name;
@ -77,12 +96,20 @@ namespace ICSharpCode.ILSpy @@ -77,12 +96,20 @@ namespace ICSharpCode.ILSpy
public static class Languages
{
public static readonly Language[] AllLanguages = {
new CSharpLanguage(),
new ILLanguage(false),
new ILLanguage(true)
};
/// <summary>
/// A list of all languages.
/// </summary>
public static readonly ReadOnlyCollection<Language> AllLanguages = Array.AsReadOnly(
new Language[] {
new CSharpLanguage(),
new ILLanguage(false),
new ILLanguage(true)
});
/// <summary>
/// Gets a language using its name.
/// If the language is not found, C# is returned instead.
/// </summary>
public static Language GetLanguage(string name)
{
return AllLanguages.FirstOrDefault(l => l.Name == name) ?? AllLanguages.First();

4
ILSpy/MainWindow.xaml

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="ICSharpCode.ILSpy.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:tv="clr-namespace:ICSharpCode.TreeView;assembly=ICSharpCode.TreeView"
x:Class="ICSharpCode.ILSpy.MainWindow"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:tv="clr-namespace:ICSharpCode.TreeView;assembly=ICSharpCode.TreeView"
xmlns:local="clr-namespace:ICSharpCode.ILSpy" xmlns:textView="clr-namespace:ICSharpCode.ILSpy.TextView"
Title="ILSpy"
MinWidth="250"

11
ILSpy/MainWindow.xaml.cs

@ -38,7 +38,7 @@ namespace ICSharpCode.ILSpy @@ -38,7 +38,7 @@ namespace ICSharpCode.ILSpy
/// <summary>
/// The main window of the application.
/// </summary>
public partial class MainWindow : Window
partial class MainWindow : Window
{
ILSpySettings spySettings;
SessionSettings sessionSettings;
@ -95,6 +95,8 @@ namespace ICSharpCode.ILSpy @@ -95,6 +95,8 @@ namespace ICSharpCode.ILSpy
void LoadInitialAssemblies()
{
// Called when loading an empty assembly list; so that
// the user can see something initially.
System.Reflection.Assembly[] initialAssemblies = {
typeof(object).Assembly,
typeof(Uri).Assembly,
@ -133,11 +135,15 @@ namespace ICSharpCode.ILSpy @@ -133,11 +135,15 @@ namespace ICSharpCode.ILSpy
internal void SelectNode(SharpTreeNode obj)
{
if (obj != null) {
// Set both the selection and focus to ensure that keyboard navigation works as expected.
treeView.FocusNode(obj);
treeView.SelectedItem = obj;
}
}
/// <summary>
/// Retrieves a node using the .ToString() representations of its ancestors.
/// </summary>
SharpTreeNode FindNodeByPath(string[] path, bool returnBestMatch)
{
if (path == null)
@ -157,6 +163,9 @@ namespace ICSharpCode.ILSpy @@ -157,6 +163,9 @@ namespace ICSharpCode.ILSpy
return node;
}
/// <summary>
/// Gets the .ToString() representation of the node's ancestors.
/// </summary>
string[] GetPathForNode(SharpTreeNode node)
{
if (node == null)

2
ILSpy/SessionSettings.cs

@ -28,7 +28,7 @@ namespace ICSharpCode.ILSpy @@ -28,7 +28,7 @@ namespace ICSharpCode.ILSpy
/// Per-session setting:
/// Loaded at startup; saved at exit.
/// </summary>
public class SessionSettings : INotifyPropertyChanged
sealed class SessionSettings : INotifyPropertyChanged
{
public SessionSettings(ILSpySettings spySettings)
{

91
ILSpy/TextView/SmartTextOutput.cs → ILSpy/TextView/AvalonEditTextOutput.cs

@ -18,19 +18,28 @@ @@ -18,19 +18,28 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler;
namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// A text segment that references some object. Used for hyperlinks in the editor.
/// </summary>
sealed class ReferenceSegment : TextSegment
{
public object Reference;
}
/// <summary>
/// Stores the positions of the definitions that were written to the text output.
/// </summary>
sealed class DefinitionLookup
{
Dictionary<object, int> definitions = new Dictionary<object, int>();
@ -50,32 +59,82 @@ namespace ICSharpCode.ILSpy.TextView @@ -50,32 +59,82 @@ namespace ICSharpCode.ILSpy.TextView
}
}
sealed class SmartTextOutput : ITextOutput
/// <summary>
/// Text output implementation for AvalonEdit.
/// </summary>
sealed class AvalonEditTextOutput : ISmartTextOutput
{
readonly StringBuilder b = new StringBuilder();
/// <summary>Current indentation level</summary>
int indent;
/// <summary>Whether indentation should be inserted on the next write</summary>
bool needsIndent;
/// <summary>List of all references that were written to the output</summary>
TextSegmentCollection<ReferenceSegment> references = new TextSegmentCollection<ReferenceSegment>();
/// <summary>Stack of the fold markers that are open but not closed yet</summary>
Stack<NewFolding> openFoldings = new Stack<NewFolding>();
/// <summary>List of all foldings that were written to the output</summary>
public readonly List<NewFolding> Foldings = new List<NewFolding>();
public readonly DefinitionLookup DefinitionLookup = new DefinitionLookup();
/// <summary>Embedded UIElements, see <see cref="UIElementGenerator"/>.</summary>
public readonly List<KeyValuePair<int, Lazy<UIElement>>> UIElements = new List<KeyValuePair<int, Lazy<UIElement>>>();
/// <summary>
/// Gets the list of references (hyperlinks).
/// </summary>
public TextSegmentCollection<ReferenceSegment> References {
get { return references; }
}
/// <summary>
/// Controls the maximum length of the text.
/// When this length is exceeded, an <see cref="OutputLengthExceededException"/> will be thrown,
/// thus aborting the decompilation.
/// </summary>
public int LengthLimit = int.MaxValue;
public int TextLength {
get { return b.Length; }
}
public override string ToString()
#region Text Document
TextDocument textDocument;
/// <summary>
/// Prepares the TextDocument.
/// This method may be called by the background thread writing to the output.
/// Once the document is prepared, it can no longer be written to.
/// </summary>
/// <remarks>
/// Calling this method on the background thread ensures the TextDocument's line tokenization
/// runs in the background and does not block the GUI.
/// </remarks>
public void PrepareDocument()
{
if (textDocument == null) {
textDocument = new TextDocument(b.ToString());
textDocument.SetOwnerThread(null); // release ownership
}
}
/// <summary>
/// Retrieves the TextDocument.
/// Once the document is retrieved, it can no longer be written to.
/// </summary>
public TextDocument GetDocument()
{
return b.ToString();
PrepareDocument();
textDocument.SetOwnerThread(System.Threading.Thread.CurrentThread); // acquire ownership
return textDocument;
}
#endregion
public void Indent()
{
@ -89,6 +148,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -89,6 +148,7 @@ namespace ICSharpCode.ILSpy.TextView
void WriteIndent()
{
Debug.Assert(textDocument == null);
if (needsIndent) {
needsIndent = false;
for (int i = 0; i < indent; i++) {
@ -111,9 +171,10 @@ namespace ICSharpCode.ILSpy.TextView @@ -111,9 +171,10 @@ namespace ICSharpCode.ILSpy.TextView
public void WriteLine()
{
Debug.Assert(textDocument == null);
b.AppendLine();
needsIndent = true;
if (b.Length > LengthLimit) {
if (this.TextLength > LengthLimit) {
throw new OutputLengthExceededException();
}
}
@ -122,35 +183,43 @@ namespace ICSharpCode.ILSpy.TextView @@ -122,35 +183,43 @@ namespace ICSharpCode.ILSpy.TextView
{
WriteIndent();
b.Append(text);
this.DefinitionLookup.AddDefinition(definition, b.Length);
this.DefinitionLookup.AddDefinition(definition, this.TextLength);
}
public void WriteReference(string text, object reference)
{
WriteIndent();
int start = b.Length;
int start = this.TextLength;
b.Append(text);
int end = b.Length;
int end = this.TextLength;
references.Add(new ReferenceSegment { StartOffset = start, EndOffset = end, Reference = reference });
}
public void MarkFoldStart(string collapsedText, bool defaultCollapsed)
{
WriteIndent();
openFoldings.Push(new NewFolding { StartOffset = b.Length, Name = collapsedText, DefaultClosed = defaultCollapsed });
openFoldings.Push(
new NewFolding {
StartOffset = this.TextLength,
Name = collapsedText,
DefaultClosed = defaultCollapsed
});
}
public void MarkFoldEnd()
{
NewFolding f = openFoldings.Pop();
f.EndOffset = b.Length;
f.EndOffset = this.TextLength;
this.Foldings.Add(f);
}
public void AddUIElement(Func<UIElement> element)
{
if (element != null)
this.UIElements.Add(new KeyValuePair<int, Lazy<UIElement>>(b.Length, new Lazy<UIElement>(element)));
if (element != null) {
if (this.UIElements.Count > 0 && this.UIElements.Last().Key == this.TextLength)
throw new InvalidOperationException("Only one UIElement is allowed for each position in the document");
this.UIElements.Add(new KeyValuePair<int, Lazy<UIElement>>(this.TextLength, new Lazy<UIElement>(element)));
}
}
}
}

1
ILSpy/TextView/CaretHighlightAdorner.cs

@ -29,6 +29,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -29,6 +29,7 @@ namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// Animated rectangle around the caret.
/// This is used after clicking links that lead to another location within the text view.
/// </summary>
sealed class CaretHighlightAdorner : Adorner
{

121
ILSpy/TextView/DecompilerTextView.cs

@ -43,12 +43,13 @@ namespace ICSharpCode.ILSpy.TextView @@ -43,12 +43,13 @@ namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// Manages the TextEditor showing the decompiled code.
/// Contains all the threading logic that makes the decompiler work in the background.
/// </summary>
sealed partial class DecompilerTextView : UserControl
{
readonly ReferenceElementGenerator referenceElementGenerator;
readonly UIElementGenerator uiElementGenerator;
readonly FoldingManager foldingManager;
FoldingManager foldingManager;
internal MainWindow mainWindow;
DefinitionLookup definitionLookup;
@ -68,16 +69,23 @@ namespace ICSharpCode.ILSpy.TextView @@ -68,16 +69,23 @@ namespace ICSharpCode.ILSpy.TextView
});
InitializeComponent();
this.referenceElementGenerator = new ReferenceElementGenerator(this);
this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference);
textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator);
this.uiElementGenerator = new UIElementGenerator();
textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator);
textEditor.Text = "Welcome to ILSpy!";
foldingManager = FoldingManager.Install(textEditor.TextArea);
}
#endregion
#region RunWithCancellation
/// <summary>
/// Switches the GUI into "waiting" mode, then calls <paramref name="taskCreation"/> to create
/// the task.
/// When the task completes without being cancelled, the <paramref name="taskCompleted"/>
/// callback is called on the GUI thread.
/// When the task is cancelled before completing, the callback is not called; and any result
/// of the task (including exceptions) are ignored.
/// </summary>
void RunWithCancellation<T>(Func<CancellationToken, Task<T>> taskCreation, Action<Task<T>> taskCompleted)
{
if (waitAdorner.Visibility != Visibility.Visible) {
@ -114,22 +122,43 @@ namespace ICSharpCode.ILSpy.TextView @@ -114,22 +122,43 @@ namespace ICSharpCode.ILSpy.TextView
void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (currentCancellationTokenSource != null)
if (currentCancellationTokenSource != null) {
currentCancellationTokenSource.Cancel();
currentCancellationTokenSource = null;
}
}
#endregion
#region ShowOutput
void ShowOutput(SmartTextOutput textOutput, ILSpy.Language language = null)
/// <summary>
/// Shows the given output in the text view.
/// </summary>
/// <param name="textOutput">The output to show.</param>
/// <param name="language">The language used for syntax highlighting.</param>
void ShowOutput(AvalonEditTextOutput textOutput, ILSpy.Language language = null)
{
Debug.WriteLine("Showing {0} characters of output", textOutput.TextLength);
Stopwatch w = Stopwatch.StartNew();
textEditor.ScrollToHome();
foldingManager.Clear();
if (foldingManager != null) {
FoldingManager.Uninstall(foldingManager);
foldingManager = null;
}
textEditor.Document = null; // clear old document while we're changing the highlighting
uiElementGenerator.UIElements = textOutput.UIElements;
referenceElementGenerator.References = textOutput.References;
definitionLookup = textOutput.DefinitionLookup;
textEditor.SyntaxHighlighting = language != null ? language.SyntaxHighlighting : null;
textEditor.Text = textOutput.ToString();
foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1);
Debug.WriteLine(" Set-up: {0}", w.Elapsed); w.Restart();
textEditor.Document = textOutput.GetDocument();
Debug.WriteLine(" Assigning document: {0}", w.Elapsed); w.Restart();
if (textOutput.Foldings.Count > 0) {
foldingManager = FoldingManager.Install(textEditor.TextArea);
foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1);
Debug.WriteLine(" Updating folding: {0}", w.Elapsed); w.Restart();
}
}
#endregion
@ -137,6 +166,10 @@ namespace ICSharpCode.ILSpy.TextView @@ -137,6 +166,10 @@ namespace ICSharpCode.ILSpy.TextView
const int defaultOutputLengthLimit = 5000000; // more than 5M characters is too slow to output (when user browses treeview)
const int extendedOutputLengthLimit = 75000000; // more than 75M characters can get us into trouble with memory usage
/// <summary>
/// Starts the decompilation of the given nodes.
/// The result is displayed in the text view.
/// </summary>
public void Decompile(ILSpy.Language language, IEnumerable<ILSpyTreeNodeBase> treeNodes, DecompilationOptions options)
{
Decompile(language, treeNodes.ToArray(), defaultOutputLengthLimit, options);
@ -149,10 +182,9 @@ namespace ICSharpCode.ILSpy.TextView @@ -149,10 +182,9 @@ namespace ICSharpCode.ILSpy.TextView
options.CancellationToken = ct;
return RunDecompiler(language, treeNodes, options, outputLengthLimit);
},
delegate (Task<SmartTextOutput> task) { // handling the result
delegate (Task<AvalonEditTextOutput> task) { // handling the result
try {
SmartTextOutput textOutput = task.Result;
Debug.WriteLine("Decompiler finished; output size = {0} characters", textOutput.TextLength);
AvalonEditTextOutput textOutput = task.Result;
ShowOutput(textOutput, language);
} catch (AggregateException aggregateException) {
textEditor.SyntaxHighlighting = null;
@ -162,60 +194,67 @@ namespace ICSharpCode.ILSpy.TextView @@ -162,60 +194,67 @@ namespace ICSharpCode.ILSpy.TextView
Exception ex = aggregateException;
while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1)
ex = ex.InnerException;
AvalonEditTextOutput output = new AvalonEditTextOutput();
if (ex is OutputLengthExceededException) {
ShowOutputLengthExceededMessage(language, treeNodes, options, outputLengthLimit == defaultOutputLengthLimit);
WriteOutputLengthExceededMessage(output, language, treeNodes, options, outputLengthLimit == defaultOutputLengthLimit);
} else {
SmartTextOutput output = new SmartTextOutput();
output.WriteLine(ex.ToString());
ShowOutput(output);
}
ShowOutput(output);
}
});
}
static Task<SmartTextOutput> RunDecompiler(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, int outputLengthLimit)
static Task<AvalonEditTextOutput> RunDecompiler(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, int outputLengthLimit)
{
Debug.WriteLine("Start decompilation of {0} nodes", nodes.Length);
if (nodes.Length == 0) {
// If there's nothing to be decompiled, don't bother starting up a thread.
// (Improves perf in some cases since we don't have to wait for the thread-pool to accept our task)
TaskCompletionSource<SmartTextOutput> tcs = new TaskCompletionSource<SmartTextOutput>();
tcs.SetResult(new SmartTextOutput());
TaskCompletionSource<AvalonEditTextOutput> tcs = new TaskCompletionSource<AvalonEditTextOutput>();
tcs.SetResult(new AvalonEditTextOutput());
return tcs.Task;
}
return Task.Factory.StartNew(
delegate {
SmartTextOutput textOutput = new SmartTextOutput();
AvalonEditTextOutput textOutput = new AvalonEditTextOutput();
textOutput.LengthLimit = outputLengthLimit;
DecompileNodes(language, nodes, options, textOutput);
textOutput.PrepareDocument();
return textOutput;
});
}
static void DecompileNodes(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, ITextOutput textOutput)
{
bool first = true;
foreach (var node in nodes) {
if (first) first = false; else textOutput.WriteLine();
for (int i = 0; i < nodes.Length; i++) {
if (i > 0)
textOutput.WriteLine();
options.CancellationToken.ThrowIfCancellationRequested();
node.Decompile(language, textOutput, options);
nodes[i].Decompile(language, textOutput, options);
}
}
#endregion
#region ShowOutputLengthExceededMessage
void ShowOutputLengthExceededMessage(ILSpy.Language language, ILSpyTreeNodeBase[] treeNodes, DecompilationOptions options, bool wasNormalLimit)
#region WriteOutputLengthExceededMessage
/// <summary>
/// Creates a message that the decompiler output was too long.
/// The message contains buttons that allow re-trying (with larger limit) or saving to a file.
/// </summary>
void WriteOutputLengthExceededMessage(
ISmartTextOutput output,
ILSpy.Language language, ILSpyTreeNodeBase[] treeNodes, DecompilationOptions options,
bool wasNormalLimit)
{
SmartTextOutput output = new SmartTextOutput();
if (wasNormalLimit) {
output.WriteLine("You have selected too much code for it to be displayed automatically.");
} else {
output.WriteLine("You have selected too much code; it cannot be displayed here.");
}
output.WriteLine();
Button button;
if (wasNormalLimit) {
output.AddUIElement(MakeButton(
Images.ViewCode, "Display Code",
@ -231,11 +270,12 @@ namespace ICSharpCode.ILSpy.TextView @@ -231,11 +270,12 @@ namespace ICSharpCode.ILSpy.TextView
SaveToDisk(language, treeNodes, options);
}));
output.WriteLine();
ShowOutput(output);
}
Func<Button> MakeButton(ImageSource icon, string text, RoutedEventHandler click)
/// <summary>
/// Creates a button for use with <see cref="ISmartTextOutput.AddUIElement"/>.
/// </summary>
static Func<Button> MakeButton(ImageSource icon, string text, RoutedEventHandler click)
{
return () => {
Button button = new Button();
@ -259,6 +299,9 @@ namespace ICSharpCode.ILSpy.TextView @@ -259,6 +299,9 @@ namespace ICSharpCode.ILSpy.TextView
#endregion
#region JumpToReference
/// <summary>
/// Jumps to the definition referred to by the <see cref="ReferenceSegment"/>.
/// </summary>
internal void JumpToReference(ReferenceSegment referenceSegment)
{
object reference = referenceSegment.Reference;
@ -293,6 +336,9 @@ namespace ICSharpCode.ILSpy.TextView @@ -293,6 +336,9 @@ namespace ICSharpCode.ILSpy.TextView
#endregion
#region SaveToDisk
/// <summary>
/// Shows the 'save file dialog', prompting the user to save the decompiled nodes to disk.
/// </summary>
public void SaveToDisk(ILSpy.Language language, IEnumerable<ILSpyTreeNodeBase> treeNodes, DecompilationOptions options)
{
if (!treeNodes.Any())
@ -307,7 +353,11 @@ namespace ICSharpCode.ILSpy.TextView @@ -307,7 +353,11 @@ namespace ICSharpCode.ILSpy.TextView
}
}
void SaveToDisk(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, string fileName)
/// <summary>
/// Starts the decompilation of the given nodes.
/// The result will be saved to the given file name.
/// </summary>
public void SaveToDisk(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, string fileName)
{
RunWithCancellation(
delegate (CancellationToken ct) {
@ -323,7 +373,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -323,7 +373,7 @@ namespace ICSharpCode.ILSpy.TextView
throw;
}
}
SmartTextOutput output = new SmartTextOutput();
AvalonEditTextOutput output = new AvalonEditTextOutput();
output.WriteLine("Decompilation complete.");
output.WriteLine();
output.AddUIElement(MakeButton(
@ -336,7 +386,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -336,7 +386,7 @@ namespace ICSharpCode.ILSpy.TextView
return output;
});
},
delegate (Task<SmartTextOutput> task) {
delegate (Task<AvalonEditTextOutput> task) {
try {
ShowOutput(task.Result);
} catch (AggregateException aggregateException) {
@ -347,14 +397,17 @@ namespace ICSharpCode.ILSpy.TextView @@ -347,14 +397,17 @@ namespace ICSharpCode.ILSpy.TextView
Exception ex = aggregateException;
while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1)
ex = ex.InnerException;
SmartTextOutput output = new SmartTextOutput();
AvalonEditTextOutput output = new AvalonEditTextOutput();
output.WriteLine(ex.ToString());
ShowOutput(output);
}
});
}
string CleanUpName(string text)
/// <summary>
/// Cleans up a node name for use as a file name.
/// </summary>
static string CleanUpName(string text)
{
int pos = text.IndexOf(':');
if (pos > 0)

22
ILSpy/TextView/ReferenceElementGenerator.cs

@ -23,22 +23,30 @@ using ICSharpCode.AvalonEdit.Rendering; @@ -23,22 +23,30 @@ using ICSharpCode.AvalonEdit.Rendering;
namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// Creates hyperlinks in the text view.
/// </summary>
sealed class ReferenceElementGenerator : VisualLineElementGenerator
{
DecompilerTextView decompilerTextView;
Action<ReferenceSegment> referenceClicked;
/// <summary>
/// The collection of references (hyperlinks).
/// </summary>
public TextSegmentCollection<ReferenceSegment> References { get; set; }
public ReferenceElementGenerator(DecompilerTextView decompilerTextView)
public ReferenceElementGenerator(Action<ReferenceSegment> referenceClicked)
{
if (decompilerTextView == null)
throw new ArgumentNullException("decompilerTextView");
this.decompilerTextView = decompilerTextView;
if (referenceClicked == null)
throw new ArgumentNullException("referenceClicked");
this.referenceClicked = referenceClicked;
}
public override int GetFirstInterestedOffset(int startOffset)
{
if (this.References == null)
return -1;
// inform AvalonEdit about the next position where we want to build a hyperlink
var segment = this.References.FindFirstSegmentWithStartAfter(startOffset);
return segment != null ? segment.StartOffset : -1;
}
@ -48,7 +56,9 @@ namespace ICSharpCode.ILSpy.TextView @@ -48,7 +56,9 @@ namespace ICSharpCode.ILSpy.TextView
if (this.References == null)
return null;
foreach (var segment in this.References.FindSegmentsContaining(offset)) {
// ensure that hyperlinks don't span several lines (VisualLineElements can't contain line breaks)
int endOffset = Math.Min(segment.EndOffset, CurrentContext.VisualLine.LastDocumentLine.EndOffset);
// don't create hyperlinks with length 0
if (offset < endOffset) {
return new VisualLineReferenceText(CurrentContext.VisualLine, endOffset - offset, this, segment);
}
@ -58,7 +68,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -58,7 +68,7 @@ namespace ICSharpCode.ILSpy.TextView
internal void JumpToReference(ReferenceSegment referenceSegment)
{
decompilerTextView.JumpToReference(referenceSegment);
referenceClicked(referenceSegment);
}
}

11
ILSpy/TextView/UIElementGenerator.cs

@ -29,8 +29,13 @@ namespace ICSharpCode.ILSpy.TextView @@ -29,8 +29,13 @@ namespace ICSharpCode.ILSpy.TextView
/// <summary>
/// Embeds UIElements in the text output.
/// </summary>
public class UIElementGenerator : VisualLineElementGenerator, IComparer<Pair>
sealed class UIElementGenerator : VisualLineElementGenerator, IComparer<Pair>
{
/// <summary>
/// The list of embedded UI elements to be displayed.
/// We store this as a sorted list of (offset, Lazy&lt;UIElement&gt;) pairs.
/// The "Lazy" part is used to create UIElements on demand (and thus on the UI thread, not on the decompiler thread).
/// </summary>
public List<Pair> UIElements;
public override int GetFirstInterestedOffset(int startOffset)
@ -38,6 +43,8 @@ namespace ICSharpCode.ILSpy.TextView @@ -38,6 +43,8 @@ namespace ICSharpCode.ILSpy.TextView
if (this.UIElements == null)
return -1;
int r = this.UIElements.BinarySearch(new Pair(startOffset, null), this);
// If the element isn't found, BinarySearch returns the complement of "insertion position".
// We use this to find the next element (if there wasn't any exact match).
if (r < 0)
r = ~r;
if (r < this.UIElements.Count)
@ -59,6 +66,8 @@ namespace ICSharpCode.ILSpy.TextView @@ -59,6 +66,8 @@ namespace ICSharpCode.ILSpy.TextView
int IComparer<Pair>.Compare(Pair x, Pair y)
{
// Compare (offset,Lazy<UIElement>) pairs by the offset.
// Used in BinarySearch()
return x.Key.CompareTo(y.Key);
}
}

3
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -153,7 +153,8 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -153,7 +153,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
{
try {
assemblyTask.Wait();
} catch (AggregateException ex) {
} catch (AggregateException) {
// if we crashed on loading, then we don't have any children
return;
}
ModuleDefinition mainModule = assemblyTask.Result.MainModule;

Loading…
Cancel
Save