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. 83
      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. 33
      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. 119
      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
case "System.Int32": case "System.Int32":
return "int32"; return "int32";
case "System.Int64": case "System.Int64":
return "int65"; return "int64";
case "System.Byte": case "System.Byte":
return "uint8"; return "uint8";
case "System.UInt16": case "System.UInt16":

2
ILSpy/AboutDialog.xaml

@ -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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ICSharpCode.ILSpy" xmlns:local="clr-namespace:ICSharpCode.ILSpy"

2
ILSpy/AboutDialog.xaml.cs

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

83
ILSpy/AssemblyList.cs

@ -32,24 +32,45 @@ namespace ICSharpCode.ILSpy
/// <summary> /// <summary>
/// A list of assemblies. /// A list of assemblies.
/// </summary> /// </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) public AssemblyList(string listName)
{ {
this.ListName = listName; this.listName = listName;
Assemblies.CollectionChanged += Assemblies_CollectionChanged; Assemblies.CollectionChanged += Assemblies_CollectionChanged;
} }
/// <summary>
/// Loads an assembly list from XML.
/// </summary>
public AssemblyList(XElement listElement) public AssemblyList(XElement listElement)
: this((string)listElement.Attribute("name")) : this((string)listElement.Attribute("name"))
{ {
foreach (var asm in listElement.Elements("Assembly")) { foreach (var asm in listElement.Elements("Assembly")) {
OpenAssembly((string)asm); 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( return new XElement(
"List", "List",
@ -58,34 +79,45 @@ namespace ICSharpCode.ILSpy
); );
} }
public bool Dirty { get; set; } /// <summary>
public string ListName { get; set; } /// Gets the name of this list.
/// </summary>
public readonly ObservableCollection<AssemblyTreeNode> Assemblies = new ObservableCollection<AssemblyTreeNode>(); public string ListName {
get { return listName; }
ConcurrentDictionary<TypeDefinition, TypeTreeNode> typeDict = new ConcurrentDictionary<TypeDefinition, TypeTreeNode>(); }
void Assemblies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) void Assemblies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
this.Dirty = true; // 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( App.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, DispatcherPriority.Background,
new Action( new Action(
delegate { delegate {
if (this.Dirty) { dirty = false;
this.Dirty = false;
AssemblyListManager.SaveList(this); 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) public void RegisterTypeNode(TypeTreeNode node)
{ {
// called on background loading thread, so we need to use a ConcurrentDictionary // called on background loading thread, so we need to use a ConcurrentDictionary
typeDict[node.TypeDefinition] = node; 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) public TypeTreeNode FindTypeNode(TypeDefinition def)
{ {
if (def == null) if (def == null)
@ -109,6 +141,10 @@ namespace ICSharpCode.ILSpy
return null; 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) public MethodTreeNode FindMethodNode(MethodDefinition def)
{ {
if (def == null) if (def == null)
@ -129,6 +165,10 @@ namespace ICSharpCode.ILSpy
return null; 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) public FieldTreeNode FindFieldNode(FieldDefinition def)
{ {
if (def == null) if (def == null)
@ -138,6 +178,10 @@ namespace ICSharpCode.ILSpy
return typeNode.VisibleChildren.OfType<FieldTreeNode>().FirstOrDefault(m => m.FieldDefinition == def); 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) public PropertyTreeNode FindPropertyNode(PropertyDefinition def)
{ {
if (def == null) if (def == null)
@ -147,6 +191,10 @@ namespace ICSharpCode.ILSpy
return typeNode.VisibleChildren.OfType<PropertyTreeNode>().FirstOrDefault(m => m.PropertyDefinition == def); 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) public EventTreeNode FindEventNode(EventDefinition def)
{ {
if (def == null) if (def == null)
@ -155,7 +203,12 @@ namespace ICSharpCode.ILSpy
typeNode.EnsureLazyChildren(); typeNode.EnsureLazyChildren();
return typeNode.VisibleChildren.OfType<EventTreeNode>().FirstOrDefault(m => m.EventDefinition == def); 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) public AssemblyTreeNode OpenAssembly(string file)
{ {
App.Current.Dispatcher.VerifyAccess(); App.Current.Dispatcher.VerifyAccess();

13
ILSpy/AssemblyListManager.cs

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

4
ILSpy/DecompilationOptions.cs

@ -35,6 +35,10 @@ namespace ICSharpCode.ILSpy
/// <summary> /// <summary>
/// Gets the cancellation token that is used to abort the decompiler. /// Gets the cancellation token that is used to abort the decompiler.
/// </summary> /// </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; } public CancellationToken CancellationToken { get; set; }
} }
} }

6
ILSpy/ExtensionMethods.cs

@ -25,7 +25,7 @@ using Mono.Cecil;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
/// <summary> /// <summary>
/// ExtensionMethods that help with WPF. /// ExtensionMethods used in ILSpy.
/// </summary> /// </summary>
public static class ExtensionMethods public static class ExtensionMethods
{ {
@ -42,7 +42,7 @@ namespace ICSharpCode.ILSpy
/// having a XAML file as context.</remarks> /// having a XAML file as context.</remarks>
public static void SetValueToExtension(this DependencyObject targetObject, DependencyProperty property, MarkupExtension markupExtension) 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) if (targetObject == null)
throw new ArgumentNullException("targetObject"); throw new ArgumentNullException("targetObject");
@ -57,7 +57,7 @@ namespace ICSharpCode.ILSpy
sealed class SetValueToExtensionServiceProvider : IServiceProvider, IProvideValueTarget 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 DependencyObject targetObject;
readonly DependencyProperty targetProperty; readonly DependencyProperty targetProperty;

21
ILSpy/FilterSettings.cs

@ -28,8 +28,8 @@ namespace ICSharpCode.ILSpy
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This class is mutable; but the ILSpyTreeNode filtering assumes that filter settings are immutable. /// 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 /// Thus, the main window will use one mutable instance (for data-binding), and will assign a new
/// mutable instance changes. /// clone to the ILSpyTreeNodes whenever the main mutable instance changes.
/// </remarks> /// </remarks>
public class FilterSettings : INotifyPropertyChanged public class FilterSettings : INotifyPropertyChanged
{ {
@ -50,6 +50,10 @@ namespace ICSharpCode.ILSpy
string searchTerm; string searchTerm;
/// <summary>
/// Gets/Sets the search term.
/// Only tree nodes containing the search term will be shown.
/// </summary>
public string SearchTerm { public string SearchTerm {
get { return searchTerm; } get { return searchTerm; }
set { set {
@ -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) public bool SearchTermMatches(string text)
{ {
if (string.IsNullOrEmpty(searchTerm)) if (string.IsNullOrEmpty(searchTerm))
@ -69,6 +76,9 @@ namespace ICSharpCode.ILSpy
bool showInternalApi; bool showInternalApi;
/// <summary>
/// Gets/Sets whether internal API members should be shown.
/// </summary>
public bool ShowInternalApi { public bool ShowInternalApi {
get { return showInternalApi; } get { return showInternalApi; }
set { set {
@ -81,6 +91,13 @@ namespace ICSharpCode.ILSpy
Language language; 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 { public Language Language {
get { return language; } get { return language; }
set { set {

6
ILSpy/GacInterop.cs

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

7
ILSpy/ILLanguage.cs

@ -26,6 +26,13 @@ using Mono.Cecil;
namespace ICSharpCode.ILSpy 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 public class ILLanguage : Language
{ {
bool detectControlStructure; bool detectControlStructure;

3
ILSpy/ILSpy.csproj

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

19
ILSpy/ILSpySettings.cs

@ -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() public static ILSpySettings Load()
{ {
using (new MutexProtector(ConfigFileMutex)) { using (new MutexProtector(ConfigFileMutex)) {
@ -62,6 +68,9 @@ namespace ICSharpCode.ILSpy
} }
} }
/// <summary>
/// Saves a setting section.
/// </summary>
public static void SaveSettings(XElement section) public static void SaveSettings(XElement section)
{ {
Update( Update(
@ -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) public static void Update(Action<XElement> action)
{ {
using (new MutexProtector(ConfigFileMutex)) { using (new MutexProtector(ConfigFileMutex)) {
@ -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"); return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ICSharpCode\\ILSpy.xml");
} }
const string ConfigFileMutex = "01A91708-49D1-410D-B8EB-4DE2662B3971"; 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 sealed class MutexProtector : IDisposable
{ {
Mutex mutex; Mutex mutex;

20
ILSpy/ISmartTextOutput.cs

@ -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);
}
}

33
ILSpy/Language.cs

@ -18,6 +18,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using Mono.Cecil; using Mono.Cecil;
@ -29,11 +30,23 @@ namespace ICSharpCode.ILSpy
/// </summary> /// </summary>
public abstract class Language public abstract class Language
{ {
/// <summary>
/// Gets the name of the language (as shown in the UI)
/// </summary>
public abstract string Name { get; } public abstract string Name { get; }
/// <summary>
/// Gets the file extension used by source code files in this language.
/// </summary>
public abstract string FileExtension { get; } public abstract string FileExtension { get; }
/// <summary>
/// Gets the syntax highlighting used for this language.
/// </summary>
public virtual ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition SyntaxHighlighting { 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) public virtual void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options)
@ -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) public virtual string TypeToString(TypeReference t)
{ {
return t.Name; return t.Name;
} }
/// <summary>
/// Used for WPF keyboard navigation.
/// </summary>
public override string ToString() public override string ToString()
{ {
return Name; return Name;
@ -77,12 +96,20 @@ namespace ICSharpCode.ILSpy
public static class Languages public static class Languages
{ {
public static readonly Language[] AllLanguages = { /// <summary>
/// A list of all languages.
/// </summary>
public static readonly ReadOnlyCollection<Language> AllLanguages = Array.AsReadOnly(
new Language[] {
new CSharpLanguage(), new CSharpLanguage(),
new ILLanguage(false), new ILLanguage(false),
new ILLanguage(true) 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) public static Language GetLanguage(string name)
{ {
return AllLanguages.FirstOrDefault(l => l.Name == name) ?? AllLanguages.First(); return AllLanguages.FirstOrDefault(l => l.Name == name) ?? AllLanguages.First();

4
ILSpy/MainWindow.xaml

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Window <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" xmlns:local="clr-namespace:ICSharpCode.ILSpy" xmlns:textView="clr-namespace:ICSharpCode.ILSpy.TextView"
Title="ILSpy" Title="ILSpy"
MinWidth="250" MinWidth="250"

11
ILSpy/MainWindow.xaml.cs

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

2
ILSpy/SessionSettings.cs

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

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

@ -18,19 +18,28 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
namespace ICSharpCode.ILSpy.TextView namespace ICSharpCode.ILSpy.TextView
{ {
/// <summary>
/// A text segment that references some object. Used for hyperlinks in the editor.
/// </summary>
sealed class ReferenceSegment : TextSegment sealed class ReferenceSegment : TextSegment
{ {
public object Reference; public object Reference;
} }
/// <summary>
/// Stores the positions of the definitions that were written to the text output.
/// </summary>
sealed class DefinitionLookup sealed class DefinitionLookup
{ {
Dictionary<object, int> definitions = new Dictionary<object, int>(); Dictionary<object, int> definitions = new Dictionary<object, int>();
@ -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(); readonly StringBuilder b = new StringBuilder();
/// <summary>Current indentation level</summary>
int indent; int indent;
/// <summary>Whether indentation should be inserted on the next write</summary>
bool needsIndent; bool needsIndent;
/// <summary>List of all references that were written to the output</summary>
TextSegmentCollection<ReferenceSegment> references = new TextSegmentCollection<ReferenceSegment>(); 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>(); 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 List<NewFolding> Foldings = new List<NewFolding>();
public readonly DefinitionLookup DefinitionLookup = new DefinitionLookup(); 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>>>(); 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 { public TextSegmentCollection<ReferenceSegment> References {
get { return 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 LengthLimit = int.MaxValue;
public int TextLength { public int TextLength {
get { return b.Length; } 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() public void Indent()
{ {
@ -89,6 +148,7 @@ namespace ICSharpCode.ILSpy.TextView
void WriteIndent() void WriteIndent()
{ {
Debug.Assert(textDocument == null);
if (needsIndent) { if (needsIndent) {
needsIndent = false; needsIndent = false;
for (int i = 0; i < indent; i++) { for (int i = 0; i < indent; i++) {
@ -111,9 +171,10 @@ namespace ICSharpCode.ILSpy.TextView
public void WriteLine() public void WriteLine()
{ {
Debug.Assert(textDocument == null);
b.AppendLine(); b.AppendLine();
needsIndent = true; needsIndent = true;
if (b.Length > LengthLimit) { if (this.TextLength > LengthLimit) {
throw new OutputLengthExceededException(); throw new OutputLengthExceededException();
} }
} }
@ -122,35 +183,43 @@ namespace ICSharpCode.ILSpy.TextView
{ {
WriteIndent(); WriteIndent();
b.Append(text); b.Append(text);
this.DefinitionLookup.AddDefinition(definition, b.Length); this.DefinitionLookup.AddDefinition(definition, this.TextLength);
} }
public void WriteReference(string text, object reference) public void WriteReference(string text, object reference)
{ {
WriteIndent(); WriteIndent();
int start = b.Length; int start = this.TextLength;
b.Append(text); b.Append(text);
int end = b.Length; int end = this.TextLength;
references.Add(new ReferenceSegment { StartOffset = start, EndOffset = end, Reference = reference }); references.Add(new ReferenceSegment { StartOffset = start, EndOffset = end, Reference = reference });
} }
public void MarkFoldStart(string collapsedText, bool defaultCollapsed) public void MarkFoldStart(string collapsedText, bool defaultCollapsed)
{ {
WriteIndent(); 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() public void MarkFoldEnd()
{ {
NewFolding f = openFoldings.Pop(); NewFolding f = openFoldings.Pop();
f.EndOffset = b.Length; f.EndOffset = this.TextLength;
this.Foldings.Add(f); this.Foldings.Add(f);
} }
public void AddUIElement(Func<UIElement> element) public void AddUIElement(Func<UIElement> element)
{ {
if (element != null) if (element != null) {
this.UIElements.Add(new KeyValuePair<int, Lazy<UIElement>>(b.Length, new Lazy<UIElement>(element))); 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
{ {
/// <summary> /// <summary>
/// Animated rectangle around the caret. /// Animated rectangle around the caret.
/// This is used after clicking links that lead to another location within the text view.
/// </summary> /// </summary>
sealed class CaretHighlightAdorner : Adorner sealed class CaretHighlightAdorner : Adorner
{ {

119
ILSpy/TextView/DecompilerTextView.cs

@ -43,12 +43,13 @@ namespace ICSharpCode.ILSpy.TextView
{ {
/// <summary> /// <summary>
/// Manages the TextEditor showing the decompiled code. /// Manages the TextEditor showing the decompiled code.
/// Contains all the threading logic that makes the decompiler work in the background.
/// </summary> /// </summary>
sealed partial class DecompilerTextView : UserControl sealed partial class DecompilerTextView : UserControl
{ {
readonly ReferenceElementGenerator referenceElementGenerator; readonly ReferenceElementGenerator referenceElementGenerator;
readonly UIElementGenerator uiElementGenerator; readonly UIElementGenerator uiElementGenerator;
readonly FoldingManager foldingManager; FoldingManager foldingManager;
internal MainWindow mainWindow; internal MainWindow mainWindow;
DefinitionLookup definitionLookup; DefinitionLookup definitionLookup;
@ -68,16 +69,23 @@ namespace ICSharpCode.ILSpy.TextView
}); });
InitializeComponent(); InitializeComponent();
this.referenceElementGenerator = new ReferenceElementGenerator(this); this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference);
textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator); textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator);
this.uiElementGenerator = new UIElementGenerator(); this.uiElementGenerator = new UIElementGenerator();
textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator); textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator);
textEditor.Text = "Welcome to ILSpy!"; textEditor.Text = "Welcome to ILSpy!";
foldingManager = FoldingManager.Install(textEditor.TextArea);
} }
#endregion #endregion
#region RunWithCancellation #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) void RunWithCancellation<T>(Func<CancellationToken, Task<T>> taskCreation, Action<Task<T>> taskCompleted)
{ {
if (waitAdorner.Visibility != Visibility.Visible) { if (waitAdorner.Visibility != Visibility.Visible) {
@ -114,22 +122,43 @@ namespace ICSharpCode.ILSpy.TextView
void cancelButton_Click(object sender, RoutedEventArgs e) void cancelButton_Click(object sender, RoutedEventArgs e)
{ {
if (currentCancellationTokenSource != null) if (currentCancellationTokenSource != null) {
currentCancellationTokenSource.Cancel(); currentCancellationTokenSource.Cancel();
currentCancellationTokenSource = null;
}
} }
#endregion #endregion
#region ShowOutput #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(); 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; uiElementGenerator.UIElements = textOutput.UIElements;
referenceElementGenerator.References = textOutput.References; referenceElementGenerator.References = textOutput.References;
definitionLookup = textOutput.DefinitionLookup; definitionLookup = textOutput.DefinitionLookup;
textEditor.SyntaxHighlighting = language != null ? language.SyntaxHighlighting : null; textEditor.SyntaxHighlighting = language != null ? language.SyntaxHighlighting : null;
textEditor.Text = textOutput.ToString();
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); foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1);
Debug.WriteLine(" Updating folding: {0}", w.Elapsed); w.Restart();
}
} }
#endregion #endregion
@ -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 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 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) public void Decompile(ILSpy.Language language, IEnumerable<ILSpyTreeNodeBase> treeNodes, DecompilationOptions options)
{ {
Decompile(language, treeNodes.ToArray(), defaultOutputLengthLimit, options); Decompile(language, treeNodes.ToArray(), defaultOutputLengthLimit, options);
@ -149,10 +182,9 @@ namespace ICSharpCode.ILSpy.TextView
options.CancellationToken = ct; options.CancellationToken = ct;
return RunDecompiler(language, treeNodes, options, outputLengthLimit); return RunDecompiler(language, treeNodes, options, outputLengthLimit);
}, },
delegate (Task<SmartTextOutput> task) { // handling the result delegate (Task<AvalonEditTextOutput> task) { // handling the result
try { try {
SmartTextOutput textOutput = task.Result; AvalonEditTextOutput textOutput = task.Result;
Debug.WriteLine("Decompiler finished; output size = {0} characters", textOutput.TextLength);
ShowOutput(textOutput, language); ShowOutput(textOutput, language);
} catch (AggregateException aggregateException) { } catch (AggregateException aggregateException) {
textEditor.SyntaxHighlighting = null; textEditor.SyntaxHighlighting = null;
@ -162,60 +194,67 @@ namespace ICSharpCode.ILSpy.TextView
Exception ex = aggregateException; Exception ex = aggregateException;
while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1) while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1)
ex = ex.InnerException; ex = ex.InnerException;
AvalonEditTextOutput output = new AvalonEditTextOutput();
if (ex is OutputLengthExceededException) { if (ex is OutputLengthExceededException) {
ShowOutputLengthExceededMessage(language, treeNodes, options, outputLengthLimit == defaultOutputLengthLimit); WriteOutputLengthExceededMessage(output, language, treeNodes, options, outputLengthLimit == defaultOutputLengthLimit);
} else { } else {
SmartTextOutput output = new SmartTextOutput();
output.WriteLine(ex.ToString()); 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); Debug.WriteLine("Start decompilation of {0} nodes", nodes.Length);
if (nodes.Length == 0) { if (nodes.Length == 0) {
// If there's nothing to be decompiled, don't bother starting up a thread. // 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) // (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>(); TaskCompletionSource<AvalonEditTextOutput> tcs = new TaskCompletionSource<AvalonEditTextOutput>();
tcs.SetResult(new SmartTextOutput()); tcs.SetResult(new AvalonEditTextOutput());
return tcs.Task; return tcs.Task;
} }
return Task.Factory.StartNew( return Task.Factory.StartNew(
delegate { delegate {
SmartTextOutput textOutput = new SmartTextOutput(); AvalonEditTextOutput textOutput = new AvalonEditTextOutput();
textOutput.LengthLimit = outputLengthLimit; textOutput.LengthLimit = outputLengthLimit;
DecompileNodes(language, nodes, options, textOutput); DecompileNodes(language, nodes, options, textOutput);
textOutput.PrepareDocument();
return textOutput; return textOutput;
}); });
} }
static void DecompileNodes(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, ITextOutput textOutput) static void DecompileNodes(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, ITextOutput textOutput)
{ {
bool first = true; for (int i = 0; i < nodes.Length; i++) {
foreach (var node in nodes) { if (i > 0)
if (first) first = false; else textOutput.WriteLine(); textOutput.WriteLine();
options.CancellationToken.ThrowIfCancellationRequested(); options.CancellationToken.ThrowIfCancellationRequested();
node.Decompile(language, textOutput, options); nodes[i].Decompile(language, textOutput, options);
} }
} }
#endregion #endregion
#region ShowOutputLengthExceededMessage #region WriteOutputLengthExceededMessage
void ShowOutputLengthExceededMessage(ILSpy.Language language, ILSpyTreeNodeBase[] treeNodes, DecompilationOptions options, bool wasNormalLimit) /// <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) { if (wasNormalLimit) {
output.WriteLine("You have selected too much code for it to be displayed automatically."); output.WriteLine("You have selected too much code for it to be displayed automatically.");
} else { } else {
output.WriteLine("You have selected too much code; it cannot be displayed here."); output.WriteLine("You have selected too much code; it cannot be displayed here.");
} }
output.WriteLine(); output.WriteLine();
Button button;
if (wasNormalLimit) { if (wasNormalLimit) {
output.AddUIElement(MakeButton( output.AddUIElement(MakeButton(
Images.ViewCode, "Display Code", Images.ViewCode, "Display Code",
@ -231,11 +270,12 @@ namespace ICSharpCode.ILSpy.TextView
SaveToDisk(language, treeNodes, options); SaveToDisk(language, treeNodes, options);
})); }));
output.WriteLine(); 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 () => { return () => {
Button button = new Button(); Button button = new Button();
@ -259,6 +299,9 @@ namespace ICSharpCode.ILSpy.TextView
#endregion #endregion
#region JumpToReference #region JumpToReference
/// <summary>
/// Jumps to the definition referred to by the <see cref="ReferenceSegment"/>.
/// </summary>
internal void JumpToReference(ReferenceSegment referenceSegment) internal void JumpToReference(ReferenceSegment referenceSegment)
{ {
object reference = referenceSegment.Reference; object reference = referenceSegment.Reference;
@ -293,6 +336,9 @@ namespace ICSharpCode.ILSpy.TextView
#endregion #endregion
#region SaveToDisk #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) public void SaveToDisk(ILSpy.Language language, IEnumerable<ILSpyTreeNodeBase> treeNodes, DecompilationOptions options)
{ {
if (!treeNodes.Any()) if (!treeNodes.Any())
@ -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( RunWithCancellation(
delegate (CancellationToken ct) { delegate (CancellationToken ct) {
@ -323,7 +373,7 @@ namespace ICSharpCode.ILSpy.TextView
throw; throw;
} }
} }
SmartTextOutput output = new SmartTextOutput(); AvalonEditTextOutput output = new AvalonEditTextOutput();
output.WriteLine("Decompilation complete."); output.WriteLine("Decompilation complete.");
output.WriteLine(); output.WriteLine();
output.AddUIElement(MakeButton( output.AddUIElement(MakeButton(
@ -336,7 +386,7 @@ namespace ICSharpCode.ILSpy.TextView
return output; return output;
}); });
}, },
delegate (Task<SmartTextOutput> task) { delegate (Task<AvalonEditTextOutput> task) {
try { try {
ShowOutput(task.Result); ShowOutput(task.Result);
} catch (AggregateException aggregateException) { } catch (AggregateException aggregateException) {
@ -347,14 +397,17 @@ namespace ICSharpCode.ILSpy.TextView
Exception ex = aggregateException; Exception ex = aggregateException;
while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1) while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1)
ex = ex.InnerException; ex = ex.InnerException;
SmartTextOutput output = new SmartTextOutput(); AvalonEditTextOutput output = new AvalonEditTextOutput();
output.WriteLine(ex.ToString()); output.WriteLine(ex.ToString());
ShowOutput(output); 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(':'); int pos = text.IndexOf(':');
if (pos > 0) if (pos > 0)

22
ILSpy/TextView/ReferenceElementGenerator.cs

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

11
ILSpy/TextView/UIElementGenerator.cs

@ -29,8 +29,13 @@ namespace ICSharpCode.ILSpy.TextView
/// <summary> /// <summary>
/// Embeds UIElements in the text output. /// Embeds UIElements in the text output.
/// </summary> /// </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 List<Pair> UIElements;
public override int GetFirstInterestedOffset(int startOffset) public override int GetFirstInterestedOffset(int startOffset)
@ -38,6 +43,8 @@ namespace ICSharpCode.ILSpy.TextView
if (this.UIElements == null) if (this.UIElements == null)
return -1; return -1;
int r = this.UIElements.BinarySearch(new Pair(startOffset, null), this); 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) if (r < 0)
r = ~r; r = ~r;
if (r < this.UIElements.Count) if (r < this.UIElements.Count)
@ -59,6 +66,8 @@ namespace ICSharpCode.ILSpy.TextView
int IComparer<Pair>.Compare(Pair x, Pair y) 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); return x.Key.CompareTo(y.Key);
} }
} }

3
ILSpy/TreeNodes/AssemblyTreeNode.cs

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

Loading…
Cancel
Save