From b85d14ac5576f1166cb3e4677d3612d8a8db9dd7 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Fri, 21 Jun 2019 14:40:08 +0200
Subject: [PATCH 01/10] Add extension method for DependencyObject
---
ILSpy/Controls/ExtensionMethods.cs | 34 ++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/ILSpy/Controls/ExtensionMethods.cs b/ILSpy/Controls/ExtensionMethods.cs
index 095ddc41e..1c5e9f794 100644
--- a/ILSpy/Controls/ExtensionMethods.cs
+++ b/ILSpy/Controls/ExtensionMethods.cs
@@ -19,6 +19,7 @@
using System;
using System.Windows;
using System.Windows.Markup;
+using System.Windows.Media;
namespace ICSharpCode.ILSpy.Controls
{
@@ -27,6 +28,39 @@ namespace ICSharpCode.ILSpy.Controls
///
public static class ExtensionMethods
{
+ ///
+ /// Checks if the current is contained in the visual tree of the
+ /// object.
+ ///
+ /// The object to check, may be null.
+ /// The object whose visual tree will be inspected.
+ ///
+ /// true if this object is contained in the visual tree of the ;
+ /// otherwise, false.
+ ///
+ /// Thrown when is null.
+ public static bool IsInVisualTreeOf(this DependencyObject thisObject, DependencyObject dependencyObject)
+ {
+ if (dependencyObject == null) {
+ throw new ArgumentNullException(nameof(dependencyObject));
+ }
+
+ if (thisObject is null) {
+ return false;
+ }
+
+ var parent = VisualTreeHelper.GetParent(thisObject);
+ while (parent != null) {
+ if (parent == dependencyObject) {
+ return true;
+ }
+
+ parent = VisualTreeHelper.GetParent(parent);
+ }
+
+ return false;
+ }
+
///
/// Sets the value of a dependency property on using a markup extension.
///
From 864672c07cc9d709d7840ec72950bb4000c761e0 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Fri, 21 Jun 2019 14:41:18 +0200
Subject: [PATCH 02/10] Enable the save code command only when appropriate
---
ILSpy/MainWindow.xaml | 1 +
ILSpy/MainWindow.xaml.cs | 15 ++++++++++++++-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml
index a168d2e66..166c8ac80 100644
--- a/ILSpy/MainWindow.xaml
+++ b/ILSpy/MainWindow.xaml
@@ -28,6 +28,7 @@
Executed="RefreshCommandExecuted" />
n is AssemblyTreeNode);
+ }
+
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
if (this.SelectedNodes.Count() == 1) {
From 46dfa7295442e3f828d98582a99613c67ec847ce Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Fri, 21 Jun 2019 15:16:36 +0200
Subject: [PATCH 03/10] Add 'Save Code' context menu item
---
ILSpy/Commands/SaveCodeContextMenuEntry.cs | 40 ++++++++++++++++++++++
ILSpy/ILSpy.csproj | 1 +
2 files changed, 41 insertions(+)
create mode 100644 ILSpy/Commands/SaveCodeContextMenuEntry.cs
diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs
new file mode 100644
index 000000000..85e5e301f
--- /dev/null
+++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs
@@ -0,0 +1,40 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Windows.Input;
+using ICSharpCode.ILSpy.Properties;
+
+namespace ICSharpCode.ILSpy.TextView
+{
+ [ExportContextMenuEntry(Header = nameof(Resources._SaveCode), Category = nameof(Resources.Save), Icon = "Images/SaveFile.png")]
+ sealed class SaveCodeContextMenuEntry : IContextMenuEntry
+ {
+ private readonly ICommand saveCommand;
+
+ public SaveCodeContextMenuEntry()
+ {
+ saveCommand = ApplicationCommands.Save;
+ }
+
+ public void Execute(TextViewContext context) => saveCommand.Execute(parameter: null);
+
+ public bool IsEnabled(TextViewContext context) => true;
+
+ public bool IsVisible(TextViewContext context) => context.TreeView != null && saveCommand.CanExecute(parameter: null);
+ }
+}
diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj
index c7f0658d6..e994dddff 100644
--- a/ILSpy/ILSpy.csproj
+++ b/ILSpy/ILSpy.csproj
@@ -207,6 +207,7 @@
+
From 4ceb8f62d061ff9d3f1f82111dcb6afa9aeb4a15 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Fri, 21 Jun 2019 19:00:25 +0200
Subject: [PATCH 04/10] Implement decompilation of multiple selected assemblies
icsharpcode#972
---
ILSpy/ILSpy.csproj | 1 +
ILSpy/MainWindow.xaml.cs | 29 +++-
ILSpy/SolutionWriter.cs | 198 ++++++++++++++++++++++++++++
ILSpy/TreeNodes/AssemblyTreeNode.cs | 53 +++++---
ILSpy/TreeNodes/ILSpyTreeNode.cs | 9 ++
5 files changed, 263 insertions(+), 27 deletions(-)
create mode 100644 ILSpy/SolutionWriter.cs
diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj
index e994dddff..24f0d0b59 100644
--- a/ILSpy/ILSpy.csproj
+++ b/ILSpy/ILSpy.csproj
@@ -204,6 +204,7 @@
+
diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs
index 6e6ea8607..238c74307 100644
--- a/ILSpy/MainWindow.xaml.cs
+++ b/ILSpy/MainWindow.xaml.cs
@@ -907,19 +907,34 @@ namespace ICSharpCode.ILSpy
e.CanExecute = true;
return;
}
- var selectedNodes = SelectedNodes.ToArray();
- e.CanExecute = selectedNodes.Length == 1 || Array.TrueForAll(selectedNodes, n => n is AssemblyTreeNode);
+ var selectedNodes = SelectedNodes.ToList();
+ e.CanExecute = selectedNodes.Count == 1 || selectedNodes.TrueForAll(n => n is AssemblyTreeNode);
}
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
- if (this.SelectedNodes.Count() == 1) {
- if (this.SelectedNodes.Single().Save(this.TextView))
+ var selectedNodes = SelectedNodes.ToList();
+ if (selectedNodes.Count > 1) {
+ var assemblyNodes = selectedNodes
+ .OfType()
+ .Where(n => n.Language is CSharpLanguage)
+ .ToList();
+
+ if (assemblyNodes.Count == selectedNodes.Count) {
+ var initialPath = Path.GetDirectoryName(assemblyNodes[0].LoadedAssembly.FileName);
+ var selectedPath = SolutionWriter.SelectSolutionFile(initialPath);
+
+ if (!string.IsNullOrEmpty(selectedPath)) {
+ SolutionWriter.CreateSolution(TextView, selectedPath, assemblyNodes);
+ }
return;
+ }
+ }
+
+ if (selectedNodes.Count != 1 || !selectedNodes[0].Save(TextView)) {
+ var options = new DecompilationOptions() { FullDecompilation = true };
+ TextView.SaveToDisk(CurrentLanguage, selectedNodes, options);
}
- this.TextView.SaveToDisk(this.CurrentLanguage,
- this.SelectedNodes,
- new DecompilationOptions() { FullDecompilation = true });
}
public void RefreshDecompiledView()
diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs
new file mode 100644
index 000000000..a4ebf5779
--- /dev/null
+++ b/ILSpy/SolutionWriter.cs
@@ -0,0 +1,198 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Security;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using ICSharpCode.Decompiler;
+using ICSharpCode.ILSpy.TextView;
+using ICSharpCode.ILSpy.TreeNodes;
+using Microsoft.Win32;
+
+namespace ICSharpCode.ILSpy
+{
+ ///
+ /// An utility class that creates a Visual Studio solution containing projects for the
+ /// decompiled assemblies.
+ ///
+ internal static class SolutionWriter
+ {
+ private const string SolutionExtension = ".sln";
+ private const string DefaultSolutionName = "Solution";
+
+ ///
+ /// Shows a File Selection dialog where the user can select the target file for the solution.
+ ///
+ /// The initial path to show in the dialog. If not specified, the 'Documents' directory
+ /// will be used.
+ ///
+ /// The full path of the selected target file, or null if the user canceled.
+ public static string SelectSolutionFile(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path)) {
+ path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ }
+
+ SaveFileDialog dlg = new SaveFileDialog();
+ dlg.InitialDirectory = path;
+ dlg.FileName = Path.Combine(path, DefaultSolutionName + SolutionExtension);
+ dlg.Filter = "Visual Studio Solution file|*" + SolutionExtension;
+
+ bool targetInvalid;
+ do {
+ if (dlg.ShowDialog() != true) {
+ return null;
+ }
+
+ string selectedPath = Path.GetDirectoryName(dlg.FileName);
+ try {
+ targetInvalid = Directory.EnumerateFileSystemEntries(selectedPath).Any();
+ } catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is SecurityException) {
+ MessageBox.Show(
+ "The directory cannot be accessed. Please ensure it exists and you have sufficient rights to access it.",
+ "Solution directory not accessible",
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ targetInvalid = true;
+ continue;
+ }
+
+ if (targetInvalid) {
+ MessageBox.Show(
+ "The directory is not empty. Please select an empty directory.",
+ "Solution directory not empty",
+ MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ } while (targetInvalid);
+
+ return dlg.FileName;
+ }
+
+ ///
+ /// Creates a Visual Studio solution that contains projects with decompiled code
+ /// of the specified . The solution file will be saved
+ /// to the . The directory of this file must either
+ /// be empty or not exist.
+ ///
+ /// A reference to the instance.
+ /// The target file path of the solution file.
+ /// The assembly nodes to decompile.
+ ///
+ /// Thrown when is null,
+ /// an empty or a whitespace string.
+ /// Thrown when > or
+ /// is null.
+ public static void CreateSolution(DecompilerTextView textView, string solutionFilePath, IEnumerable assemblyNodes)
+ {
+ if (textView == null) {
+ throw new ArgumentNullException(nameof(textView));
+ }
+
+ if (string.IsNullOrWhiteSpace(solutionFilePath)) {
+ throw new ArgumentException("The solution file path cannot be null or empty.", nameof(solutionFilePath));
+ }
+
+ if (assemblyNodes == null) {
+ throw new ArgumentNullException(nameof(assemblyNodes));
+ }
+
+ textView
+ .RunWithCancellation(ct => CreateSolution(solutionFilePath, assemblyNodes, ct))
+ .Then(output => textView.ShowText(output))
+ .HandleExceptions();
+ }
+
+ private static async Task CreateSolution(
+ string solutionFilePath,
+ IEnumerable assemblyNodes,
+ CancellationToken ct)
+ {
+ var solutionDirectory = Path.GetDirectoryName(solutionFilePath);
+ var statusOutput = new ConcurrentBag();
+ var result = new AvalonEditTextOutput();
+
+ var duplicates = new HashSet();
+ if (assemblyNodes.Any(n => !duplicates.Add(n.LoadedAssembly.ShortName))) {
+ result.WriteLine("Duplicate assembly names selected, cannot generate a solution.");
+ return result;
+ }
+
+ Stopwatch stopwatch = Stopwatch.StartNew();
+
+ await Task.Run(() => Parallel.ForEach(assemblyNodes, n => WriteProject(n, solutionDirectory, statusOutput, ct)))
+ .ConfigureAwait(false);
+
+
+ foreach (var item in statusOutput) {
+ result.WriteLine(item);
+ }
+
+ if (statusOutput.Count == 0) {
+ result.WriteLine("Successfully decompiled the following assemblies to a Visual Studio Solution:");
+ foreach (var item in assemblyNodes.Select(n => n.Text.ToString())) {
+ result.WriteLine(item);
+ }
+
+ result.WriteLine();
+ result.WriteLine("Elapsed time: " + stopwatch.Elapsed.TotalSeconds.ToString("F1") + " seconds.");
+ result.WriteLine();
+ result.AddButton(null, "Open Explorer", delegate { Process.Start("explorer", "/select,\"" + solutionFilePath + "\""); });
+ }
+
+ return result;
+ }
+
+ private static void WriteProject(AssemblyTreeNode assemblyNode, string targetDirectory, ConcurrentBag statusOutput, CancellationToken ct)
+ {
+ var loadedAssembly = assemblyNode.LoadedAssembly;
+
+ targetDirectory = Path.Combine(targetDirectory, loadedAssembly.ShortName);
+ string projectFileName = Path.Combine(targetDirectory, loadedAssembly.ShortName + assemblyNode.Language.ProjectFileExtension);
+
+ if (!Directory.Exists(targetDirectory)) {
+ try {
+ Directory.CreateDirectory(targetDirectory);
+ } catch (Exception e) {
+ statusOutput.Add($"Failed to create a directory '{targetDirectory}':{Environment.NewLine}{e}");
+ return;
+ }
+ }
+
+ try {
+ using (var projectFileWriter = new StreamWriter(projectFileName)) {
+ var projectFileOutput = new PlainTextOutput(projectFileWriter);
+ var options = new DecompilationOptions() {
+ FullDecompilation = true,
+ CancellationToken = ct,
+ SaveAsProjectDirectory = targetDirectory };
+
+ assemblyNode.Decompile(assemblyNode.Language, projectFileOutput, options);
+ }
+ } catch (Exception e) {
+ statusOutput.Add($"Failed to decompile the assembly '{loadedAssembly.FileName}':{Environment.NewLine}{e}");
+ return;
+ }
+ }
+ }
+}
diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs
index 51aa65bdf..60f34e6b2 100644
--- a/ILSpy/TreeNodes/AssemblyTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs
@@ -279,31 +279,44 @@ namespace ICSharpCode.ILSpy.TreeNodes
public override bool Save(DecompilerTextView textView)
{
Language language = this.Language;
- if (string.IsNullOrEmpty(language.ProjectFileExtension))
+ if (string.IsNullOrEmpty(language.ProjectFileExtension)) {
return false;
+ }
+
SaveFileDialog dlg = new SaveFileDialog();
dlg.FileName = DecompilerTextView.CleanUpName(LoadedAssembly.ShortName) + language.ProjectFileExtension;
- dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*";
- if (dlg.ShowDialog() == true) {
- DecompilationOptions options = new DecompilationOptions();
- options.FullDecompilation = true;
- if (dlg.FilterIndex == 1) {
- options.SaveAsProjectDirectory = Path.GetDirectoryName(dlg.FileName);
- foreach (string entry in Directory.GetFileSystemEntries(options.SaveAsProjectDirectory)) {
- if (!string.Equals(entry, dlg.FileName, StringComparison.OrdinalIgnoreCase)) {
- var result = MessageBox.Show(
- "The directory is not empty. File will be overwritten." + Environment.NewLine +
- "Are you sure you want to continue?",
- "Project Directory not empty",
- MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
- if (result == MessageBoxResult.No)
- return true; // don't save, but mark the Save operation as handled
- break;
- }
- }
+ dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension;
+ if (dlg.ShowDialog() != true) {
+ return true;
+ }
+
+ var targetDirectory = Path.GetDirectoryName(dlg.FileName);
+ var existingFiles = Directory.GetFileSystemEntries(targetDirectory);
+
+ if (existingFiles.Any(e => !string.Equals(e, dlg.FileName, StringComparison.OrdinalIgnoreCase))) {
+ var result = MessageBox.Show(
+ "The directory is not empty. File will be overwritten." + Environment.NewLine +
+ "Are you sure you want to continue?",
+ "Project Directory not empty",
+ MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
+ if (result == MessageBoxResult.No) {
+ return true; // don't save, but mark the Save operation as handled
}
- textView.SaveToDisk(language, new[] { this }, options, dlg.FileName);
}
+
+ Save(textView, dlg.FileName);
+ return true;
+ }
+
+ public override bool Save(DecompilerTextView textView, string fileName)
+ {
+ var targetDirectory = Path.GetDirectoryName(fileName);
+ DecompilationOptions options = new DecompilationOptions {
+ FullDecompilation = true,
+ SaveAsProjectDirectory = targetDirectory
+ };
+
+ textView.SaveToDisk(Language, new[] { this }, options, fileName);
return true;
}
diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs
index fa1e663e4..11fe3b278 100644
--- a/ILSpy/TreeNodes/ILSpyTreeNode.cs
+++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs
@@ -79,6 +79,15 @@ namespace ICSharpCode.ILSpy.TreeNodes
return false;
}
+ ///
+ /// Saves the content this node represents to the specified .
+ /// The file will be silently overwritten.
+ ///
+ /// A reference to a instance.
+ /// The target full path to save the content to.
+ /// true on success; otherwise, false.
+ public virtual bool Save(TextView.DecompilerTextView textView, string fileName) => Save(textView);
+
protected override void OnChildrenChanged(NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null) {
From 5e6a261b8607c12af71d48e03754cbba90daaba0 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Fri, 21 Jun 2019 23:17:42 +0200
Subject: [PATCH 05/10] Implement Visual Studio solution generation for
icsharpcode#972
---
.../CSharp/SolutionCreator.cs | 275 ++++++++++++++++++
.../CSharp/WholeProjectDecompiler.cs | 9 +-
.../ICSharpCode.Decompiler.csproj | 1 +
ILSpy/Languages/CSharpLanguage.cs | 6 +-
ILSpy/Languages/ILLanguage.cs | 4 +-
ILSpy/Languages/Language.cs | 6 +-
ILSpy/SolutionWriter.cs | 54 +++-
ILSpy/TreeNodes/AssemblyListTreeNode.cs | 4 +-
ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs | 4 +-
ILSpy/TreeNodes/AssemblyTreeNode.cs | 12 +-
ILSpy/TreeNodes/BaseTypesEntryNode.cs | 3 +-
ILSpy/TreeNodes/BaseTypesTreeNode.cs | 4 +-
ILSpy/TreeNodes/DerivedTypesEntryNode.cs | 3 +-
ILSpy/TreeNodes/DerivedTypesTreeNode.cs | 3 +-
ILSpy/TreeNodes/EventTreeNode.cs | 3 +-
ILSpy/TreeNodes/FieldTreeNode.cs | 3 +-
ILSpy/TreeNodes/ILSpyTreeNode.cs | 2 +-
ILSpy/TreeNodes/MethodTreeNode.cs | 3 +-
ILSpy/TreeNodes/ModuleReferenceTreeNode.cs | 3 +-
ILSpy/TreeNodes/NamespaceTreeNode.cs | 3 +-
ILSpy/TreeNodes/PropertyTreeNode.cs | 3 +-
ILSpy/TreeNodes/ReferenceFolderTreeNode.cs | 4 +-
ILSpy/TreeNodes/ResourceListTreeNode.cs | 4 +-
.../ImageListResourceEntryNode.cs | 3 +-
.../ResourceNodes/ResourceEntryNode.cs | 3 +-
.../ResourceNodes/ResourceTreeNode.cs | 4 +-
.../ResourceNodes/ResourcesFileTreeNode.cs | 4 +-
ILSpy/TreeNodes/ThreadingSupport.cs | 6 +-
ILSpy/TreeNodes/TypeTreeNode.cs | 3 +-
29 files changed, 394 insertions(+), 45 deletions(-)
create mode 100644 ICSharpCode.Decompiler/CSharp/SolutionCreator.cs
diff --git a/ICSharpCode.Decompiler/CSharp/SolutionCreator.cs b/ICSharpCode.Decompiler/CSharp/SolutionCreator.cs
new file mode 100644
index 000000000..7c3f26e0e
--- /dev/null
+++ b/ICSharpCode.Decompiler/CSharp/SolutionCreator.cs
@@ -0,0 +1,275 @@
+// Copyright (c) 2016 Daniel Grunwald
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace ICSharpCode.Decompiler.CSharp
+{
+ ///
+ /// A container class that holds information about a Visual Studio project.
+ ///
+ public sealed class ProjectItem : ProjectId
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The full path of the project file.
+ /// The project platform.
+ /// The project GUID.
+ ///
+ /// Thrown when
+ /// or is null or empty.
+ public ProjectItem(string projectFile, string projectPlatform, Guid projectGuid)
+ : base(projectPlatform, projectGuid)
+ {
+ ProjectName = Path.GetFileNameWithoutExtension(projectFile);
+ FilePath = projectFile;
+ }
+
+ ///
+ /// Gets the name of the project.
+ ///
+ public string ProjectName { get; }
+
+ ///
+ /// Gets the full path to the project file.
+ ///
+ public string FilePath { get; }
+ }
+
+ ///
+ /// A container class that holds platform and GUID information about a Visual Studio project.
+ ///
+ public class ProjectId
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The project platform.
+ /// The project GUID.
+ ///
+ /// Thrown when
+ /// or is null or empty.
+ public ProjectId(string projectPlatform, Guid projectGuid)
+ {
+ if (string.IsNullOrWhiteSpace(projectPlatform)) {
+ throw new ArgumentException("The platform cannot be null or empty.", nameof(projectPlatform));
+ }
+
+ Guid = projectGuid;
+ PlatformName = projectPlatform;
+ }
+
+ ///
+ /// Gets the GUID of this project.
+ ///
+ public Guid Guid { get; }
+
+ ///
+ /// Gets the platform name of this project. Only single platform per project is supported.
+ ///
+ public string PlatformName { get; }
+ }
+
+ ///
+ /// A helper class that can write a Visual Studio Solution file for the provided projects.
+ ///
+ public static class SolutionCreator
+ {
+ private static readonly XNamespace ProjectFileNamespace = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");
+
+ ///
+ /// Writes a solution file to the specified .
+ ///
+ /// The full path of the file to write.
+ /// The projects contained in this solution.
+ ///
+ /// Thrown when is null or empty.
+ /// Thrown when is null.
+ /// Thrown when contains no items.
+ public static void WriteSolutionFile(string targetFile, IEnumerable projects)
+ {
+ if (string.IsNullOrWhiteSpace(targetFile)) {
+ throw new ArgumentException("The target file cannot be null or empty.", nameof(targetFile));
+ }
+
+ if (projects == null) {
+ throw new ArgumentNullException(nameof(projects));
+ }
+
+ if (!projects.Any()) {
+ throw new InvalidOperationException("At least one project is expected.");
+ }
+
+ using (var writer = new StreamWriter(targetFile)) {
+ WriteSolutionFile(writer, projects, Path.GetDirectoryName(targetFile));
+ }
+
+ FixProjectReferences(projects);
+ }
+
+ private static void WriteSolutionFile(TextWriter writer, IEnumerable projects, string solutionPath)
+ {
+ WriteHeader(writer);
+ WriteProjects(writer, projects, solutionPath);
+
+ writer.WriteLine("Global");
+
+ var platforms = WriteSolutionConfigurations(writer, projects);
+ WriteProjectConfigurations(writer, projects, platforms);
+
+ writer.WriteLine("\tGlobalSection(SolutionProperties) = preSolution");
+ writer.WriteLine("\t\tHideSolutionNode = FALSE");
+ writer.WriteLine("\tEndGlobalSection");
+
+ writer.WriteLine("EndGlobal");
+ }
+
+ private static void WriteHeader(TextWriter writer)
+ {
+ writer.WriteLine("Microsoft Visual Studio Solution File, Format Version 12.00");
+ writer.WriteLine("# Visual Studio 14");
+ writer.WriteLine("VisualStudioVersion = 14.0.24720.0");
+ writer.WriteLine("MinimumVisualStudioVersion = 10.0.40219.1");
+ }
+
+ private static void WriteProjects(TextWriter writer, IEnumerable projects, string solutionPath)
+ {
+ var solutionGuid = Guid.NewGuid().ToString("B").ToUpperInvariant();
+
+ foreach (var project in projects) {
+ var projectRelativePath = GetRelativePath(solutionPath, project.FilePath);
+ var projectGuid = project.Guid.ToString("B").ToUpperInvariant();
+
+ writer.WriteLine($"Project(\"{solutionGuid}\") = \"{project.ProjectName}\", \"{projectRelativePath}\", \"{projectGuid}\"");
+ writer.WriteLine("EndProject");
+ }
+ }
+
+ private static IEnumerable WriteSolutionConfigurations(TextWriter writer, IEnumerable projects)
+ {
+ var platforms = projects.GroupBy(p => p.PlatformName).Select(g => g.Key).ToList();
+
+ platforms.Sort();
+
+ writer.WriteLine("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
+ foreach (var platform in platforms) {
+ writer.WriteLine($"\t\tDebug|{platform} = Debug|{platform}");
+ }
+
+ foreach (var platform in platforms) {
+ writer.WriteLine($"\t\tRelease|{platform} = Release|{platform}");
+ }
+
+ writer.WriteLine("\tEndGlobalSection");
+
+ return platforms;
+ }
+
+ private static void WriteProjectConfigurations(
+ TextWriter writer,
+ IEnumerable projects,
+ IEnumerable solutionPlatforms)
+ {
+ writer.WriteLine("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
+
+ foreach (var project in projects) {
+ var projectGuid = project.Guid.ToString("B").ToUpperInvariant();
+
+ foreach (var platform in solutionPlatforms) {
+ writer.WriteLine($"\t\t{projectGuid}.Debug|{platform}.ActiveCfg = Debug|{project.PlatformName}");
+ writer.WriteLine($"\t\t{projectGuid}.Debug|{platform}.Build.0 = Debug|{project.PlatformName}");
+ }
+
+ foreach (var platform in solutionPlatforms) {
+ writer.WriteLine($"\t\t{projectGuid}.Release|{platform}.ActiveCfg = Release|{project.PlatformName}");
+ writer.WriteLine($"\t\t{projectGuid}.Release|{platform}.Build.0 = Release|{project.PlatformName}");
+ }
+ }
+
+ writer.WriteLine("\tEndGlobalSection");
+ }
+
+ private static void FixProjectReferences(IEnumerable projects)
+ {
+ var projectsMap = projects.ToDictionary(p => p.ProjectName, p => p);
+
+ foreach (var project in projects) {
+ var projectDirectory = Path.GetDirectoryName(project.FilePath);
+ XDocument projectDoc = XDocument.Load(project.FilePath);
+
+ var referencesItemGroups = projectDoc.Root
+ .Elements(ProjectFileNamespace + "ItemGroup")
+ .Where(e => e.Elements(ProjectFileNamespace + "Reference").Any());
+
+ foreach (var itemGroup in referencesItemGroups) {
+ FixProjectReferences(projectDirectory, itemGroup, projectsMap);
+ }
+
+ projectDoc.Save(project.FilePath);
+ }
+ }
+
+ private static void FixProjectReferences(string projectDirectory, XElement itemGroup, IDictionary projects)
+ {
+ foreach (var item in itemGroup.Elements(ProjectFileNamespace + "Reference").ToList()) {
+ var assemblyName = item.Attribute("Include")?.Value;
+ if (assemblyName != null && projects.TryGetValue(assemblyName, out var referencedProject)) {
+ item.Remove();
+
+ var projectReference = new XElement(ProjectFileNamespace + "ProjectReference",
+ new XElement(ProjectFileNamespace + "Project", referencedProject.Guid.ToString("B").ToUpperInvariant()),
+ new XElement(ProjectFileNamespace + "Name", referencedProject.ProjectName));
+ projectReference.SetAttributeValue("Include", GetRelativePath(projectDirectory, referencedProject.FilePath));
+
+ itemGroup.Add(projectReference);
+ }
+ }
+ }
+
+ private static string GetRelativePath(string fromPath, string toPath)
+ {
+ Uri fromUri = new Uri(AppendDirectorySeparatorChar(fromPath));
+ Uri toUri = new Uri(AppendDirectorySeparatorChar(toPath));
+
+ if (fromUri.Scheme != toUri.Scheme) {
+ return toPath;
+ }
+
+ Uri relativeUri = fromUri.MakeRelativeUri(toUri);
+ string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
+
+ if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase)) {
+ relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+ }
+
+ return relativePath;
+ }
+
+ private static string AppendDirectorySeparatorChar(string path)
+ {
+ return Path.HasExtension(path) || path.EndsWith(Path.DirectorySeparatorChar.ToString())
+ ? path
+ : path + Path.DirectorySeparatorChar;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
index d467ba883..6ec5d6802 100644
--- a/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
@@ -101,7 +101,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
- public void DecompileProject(PEFile moduleDefinition, string targetDirectory, TextWriter projectFileWriter, CancellationToken cancellationToken = default(CancellationToken))
+ public ProjectId DecompileProject(PEFile moduleDefinition, string targetDirectory, TextWriter projectFileWriter, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(targetDirectory)) {
throw new InvalidOperationException("Must set TargetDirectory");
@@ -110,7 +110,7 @@ namespace ICSharpCode.Decompiler.CSharp
directories.Clear();
var files = WriteCodeFilesInProject(moduleDefinition, cancellationToken).ToList();
files.AddRange(WriteResourceFilesInProject(moduleDefinition));
- WriteProjectFile(projectFileWriter, files, moduleDefinition);
+ return WriteProjectFile(projectFileWriter, files, moduleDefinition);
}
enum LanguageTargets
@@ -120,11 +120,12 @@ namespace ICSharpCode.Decompiler.CSharp
}
#region WriteProjectFile
- void WriteProjectFile(TextWriter writer, IEnumerable> files, Metadata.PEFile module)
+ ProjectId WriteProjectFile(TextWriter writer, IEnumerable> files, Metadata.PEFile module)
{
const string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
string platformName = GetPlatformName(module);
Guid guid = this.ProjectGuid ?? Guid.NewGuid();
+
using (XmlTextWriter w = new XmlTextWriter(writer)) {
w.Formatting = Formatting.Indented;
w.WriteStartDocument();
@@ -281,6 +282,8 @@ namespace ICSharpCode.Decompiler.CSharp
w.WriteEndDocument();
}
+
+ return new ProjectId(platformName, guid);
}
protected virtual bool IsGacAssembly(Metadata.IAssemblyReference r, Metadata.PEFile asm)
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 8ab2fbd69..6efca3120 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -61,6 +61,7 @@
+
diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs
index 2441e0eb2..57cab0ee8 100644
--- a/ILSpy/Languages/CSharpLanguage.cs
+++ b/ILSpy/Languages/CSharpLanguage.cs
@@ -343,12 +343,12 @@ namespace ICSharpCode.ILSpy
}
}
- public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
+ public override object DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
var module = assembly.GetPEFileOrNull();
if (options.FullDecompilation && options.SaveAsProjectDirectory != null) {
var decompiler = new ILSpyWholeProjectDecompiler(assembly, options);
- decompiler.DecompileProject(module, options.SaveAsProjectDirectory, new TextOutputWriter(output), options.CancellationToken);
+ return decompiler.DecompileProject(module, options.SaveAsProjectDirectory, new TextOutputWriter(output), options.CancellationToken);
} else {
AddReferenceAssemblyWarningMessage(module, output);
AddReferenceWarningMessage(module, output);
@@ -415,6 +415,8 @@ namespace ICSharpCode.ILSpy
}
WriteCode(output, options.DecompilerSettings, st, decompiler.TypeSystem);
}
+
+ return true;
}
}
diff --git a/ILSpy/Languages/ILLanguage.cs b/ILSpy/Languages/ILLanguage.cs
index e2ab9d1f3..da26caeb2 100644
--- a/ILSpy/Languages/ILLanguage.cs
+++ b/ILSpy/Languages/ILLanguage.cs
@@ -150,7 +150,7 @@ namespace ICSharpCode.ILSpy
dis.DisassembleNamespace(nameSpace, module, types.Select(t => (TypeDefinitionHandle)t.MetadataToken));
}
- public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
+ public override object DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
output.WriteLine("// " + assembly.FileName);
output.WriteLine();
@@ -174,6 +174,8 @@ namespace ICSharpCode.ILSpy
dis.WriteModuleContents(module);
}
}
+
+ return true;
}
}
}
diff --git a/ILSpy/Languages/Language.cs b/ILSpy/Languages/Language.cs
index a1aad8d64..76f435244 100644
--- a/ILSpy/Languages/Language.cs
+++ b/ILSpy/Languages/Language.cs
@@ -131,11 +131,11 @@ namespace ICSharpCode.ILSpy
WriteCommentLine(output, nameSpace);
}
- public virtual void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
+ public virtual object DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
WriteCommentLine(output, assembly.FileName);
var asm = assembly.GetPEFileOrNull();
- if (asm == null) return;
+ if (asm == null) return false;
var metadata = asm.Metadata;
if (metadata.IsAssembly) {
var name = metadata.GetAssemblyDefinition();
@@ -147,6 +147,8 @@ namespace ICSharpCode.ILSpy
} else {
WriteCommentLine(output, metadata.GetString(metadata.GetModuleDefinition().Name));
}
+
+ return true;
}
public virtual void WriteCommentLine(ITextOutput output, string comment)
diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs
index a4ebf5779..45fd3ce67 100644
--- a/ILSpy/SolutionWriter.cs
+++ b/ILSpy/SolutionWriter.cs
@@ -27,6 +27,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes;
using Microsoft.Win32;
@@ -130,6 +131,8 @@ namespace ICSharpCode.ILSpy
{
var solutionDirectory = Path.GetDirectoryName(solutionFilePath);
var statusOutput = new ConcurrentBag();
+ var projects = new ConcurrentBag();
+
var result = new AvalonEditTextOutput();
var duplicates = new HashSet();
@@ -140,20 +143,45 @@ namespace ICSharpCode.ILSpy
Stopwatch stopwatch = Stopwatch.StartNew();
- await Task.Run(() => Parallel.ForEach(assemblyNodes, n => WriteProject(n, solutionDirectory, statusOutput, ct)))
- .ConfigureAwait(false);
+ try {
+ await Task.Run(() => Parallel.ForEach(assemblyNodes, n => WriteProject(n, solutionDirectory, statusOutput, projects, ct)))
+ .ConfigureAwait(false);
+
+ await Task.Run(() => SolutionCreator.WriteSolutionFile(solutionFilePath, projects))
+ .ConfigureAwait(false);
+ } catch (AggregateException ae) {
+ if (ae.Flatten().InnerExceptions.All(e => e is OperationCanceledException)) {
+ result.WriteLine();
+ result.WriteLine("Generation was cancelled.");
+ return result;
+ }
+ result.WriteLine();
+ result.WriteLine("Failed to generate the Visual Studio Solution. Errors:");
+ ae.Handle(e => {
+ result.WriteLine(e.Message);
+ return true;
+ });
+
+ return result;
+ }
foreach (var item in statusOutput) {
result.WriteLine(item);
}
if (statusOutput.Count == 0) {
- result.WriteLine("Successfully decompiled the following assemblies to a Visual Studio Solution:");
+ result.WriteLine("Successfully decompiled the following assemblies into Visual Studio projects:");
foreach (var item in assemblyNodes.Select(n => n.Text.ToString())) {
result.WriteLine(item);
}
+ result.WriteLine();
+
+ if (assemblyNodes.Count() == projects.Count) {
+ result.WriteLine("Created the Visual Studio Solution file.");
+ }
+
result.WriteLine();
result.WriteLine("Elapsed time: " + stopwatch.Elapsed.TotalSeconds.ToString("F1") + " seconds.");
result.WriteLine();
@@ -163,7 +191,12 @@ namespace ICSharpCode.ILSpy
return result;
}
- private static void WriteProject(AssemblyTreeNode assemblyNode, string targetDirectory, ConcurrentBag statusOutput, CancellationToken ct)
+ private static void WriteProject(
+ AssemblyTreeNode assemblyNode,
+ string targetDirectory,
+ ConcurrentBag statusOutput,
+ ConcurrentBag targetContainer,
+ CancellationToken ct)
{
var loadedAssembly = assemblyNode.LoadedAssembly;
@@ -186,12 +219,17 @@ namespace ICSharpCode.ILSpy
FullDecompilation = true,
CancellationToken = ct,
SaveAsProjectDirectory = targetDirectory };
-
- assemblyNode.Decompile(assemblyNode.Language, projectFileOutput, options);
+
+ if (assemblyNode.Decompile(assemblyNode.Language, projectFileOutput, options) is ProjectId projectInfo) {
+ targetContainer.Add(new ProjectItem(projectFileName, projectInfo.PlatformName, projectInfo.Guid));
+ }
}
- } catch (Exception e) {
+ }
+ catch (OperationCanceledException) {
+ throw;
+ }
+ catch (Exception e) {
statusOutput.Add($"Failed to decompile the assembly '{loadedAssembly.FileName}':{Environment.NewLine}{e}");
- return;
}
}
}
diff --git a/ILSpy/TreeNodes/AssemblyListTreeNode.cs b/ILSpy/TreeNodes/AssemblyListTreeNode.cs
index e53a3a883..3b8e5a519 100644
--- a/ILSpy/TreeNodes/AssemblyListTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyListTreeNode.cs
@@ -141,7 +141,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
public Action Select = delegate { };
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, "List: " + assemblyList.ListName);
output.WriteLine();
@@ -150,6 +150,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
output.WriteLine();
asm.Decompile(language, output, options);
}
+
+ return true;
}
#region Find*Node
diff --git a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
index 8aa8f8c0d..192820246 100644
--- a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
@@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
var loaded = parentAssembly.LoadedAssembly.LoadedAssemblyReferencesInfo.TryGetInfo(r.FullName, out var info);
if (r.IsWindowsRuntime) {
@@ -98,6 +98,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
output.Unindent();
output.WriteLine();
}
+
+ return true;
}
}
}
diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs
index 60f34e6b2..39669c15f 100644
--- a/ILSpy/TreeNodes/AssemblyTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs
@@ -240,7 +240,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Recurse;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
void HandleException(Exception ex, string message)
{
@@ -259,21 +259,21 @@ namespace ICSharpCode.ILSpy.TreeNodes
switch (ex.InnerException) {
case BadImageFormatException badImage:
HandleException(badImage, "This file does not contain a managed assembly.");
- return;
+ return null;
case FileNotFoundException fileNotFound:
HandleException(fileNotFound, "The file was not found.");
- return;
+ return null;
case DirectoryNotFoundException dirNotFound:
HandleException(dirNotFound, "The directory was not found.");
- return;
+ return null;
case PEFileNotSupportedException notSupported:
HandleException(notSupported, notSupported.Message);
- return;
+ return null;
default:
throw;
}
}
- language.DecompileAssembly(LoadedAssembly, output, options);
+ return language.DecompileAssembly(LoadedAssembly, output, options);
}
public override bool Save(DecompilerTextView textView)
diff --git a/ILSpy/TreeNodes/BaseTypesEntryNode.cs b/ILSpy/TreeNodes/BaseTypesEntryNode.cs
index 3c96c40dd..41edd0f8c 100644
--- a/ILSpy/TreeNodes/BaseTypesEntryNode.cs
+++ b/ILSpy/TreeNodes/BaseTypesEntryNode.cs
@@ -97,9 +97,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
return false;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, language.TypeToString(type, includeNamespace: true));
+ return true;
}
IEntity IMemberTreeNode.Member {
diff --git a/ILSpy/TreeNodes/BaseTypesTreeNode.cs b/ILSpy/TreeNodes/BaseTypesTreeNode.cs
index 8cbdb2304..58565a938 100644
--- a/ILSpy/TreeNodes/BaseTypesTreeNode.cs
+++ b/ILSpy/TreeNodes/BaseTypesTreeNode.cs
@@ -69,12 +69,14 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnsureLazyChildren));
foreach (ILSpyTreeNode child in this.Children) {
child.Decompile(language, output, options);
}
+
+ return true;
}
}
}
\ No newline at end of file
diff --git a/ILSpy/TreeNodes/DerivedTypesEntryNode.cs b/ILSpy/TreeNodes/DerivedTypesEntryNode.cs
index 91da3e033..f3d9fe29f 100644
--- a/ILSpy/TreeNodes/DerivedTypesEntryNode.cs
+++ b/ILSpy/TreeNodes/DerivedTypesEntryNode.cs
@@ -90,9 +90,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
e.Handled = BaseTypesEntryNode.ActivateItem(this, type);
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, language.TypeToString(type, includeNamespace: true));
+ return true;
}
IEntity IMemberTreeNode.Member => type;
diff --git a/ILSpy/TreeNodes/DerivedTypesTreeNode.cs b/ILSpy/TreeNodes/DerivedTypesTreeNode.cs
index 3b6e50a30..8518e08e3 100644
--- a/ILSpy/TreeNodes/DerivedTypesTreeNode.cs
+++ b/ILSpy/TreeNodes/DerivedTypesTreeNode.cs
@@ -92,9 +92,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
return typeRef.GetFullTypeName(referenceMetadata) == typeDef.GetFullTypeName(definitionMetadata);
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
threading.Decompile(language, output, options, EnsureLazyChildren);
+ return true;
}
}
}
\ No newline at end of file
diff --git a/ILSpy/TreeNodes/EventTreeNode.cs b/ILSpy/TreeNodes/EventTreeNode.cs
index 1aadff896..c1797f794 100644
--- a/ILSpy/TreeNodes/EventTreeNode.cs
+++ b/ILSpy/TreeNodes/EventTreeNode.cs
@@ -67,9 +67,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileEvent(EventDefinition, output, options);
+ return true;
}
public override bool IsPublicAPI {
diff --git a/ILSpy/TreeNodes/FieldTreeNode.cs b/ILSpy/TreeNodes/FieldTreeNode.cs
index 5d4d2f7b0..5d3323beb 100644
--- a/ILSpy/TreeNodes/FieldTreeNode.cs
+++ b/ILSpy/TreeNodes/FieldTreeNode.cs
@@ -68,9 +68,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileField(FieldDefinition, output, options);
+ return true;
}
public override bool IsPublicAPI {
diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs
index 11fe3b278..515188374 100644
--- a/ILSpy/TreeNodes/ILSpyTreeNode.cs
+++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs
@@ -57,7 +57,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public abstract void Decompile(Language language, ITextOutput output, DecompilationOptions options);
+ public abstract object Decompile(Language language, ITextOutput output, DecompilationOptions options);
///
/// Used to implement special view logic for some items.
diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs
index 9e4d95e45..89c12ec10 100644
--- a/ILSpy/TreeNodes/MethodTreeNode.cs
+++ b/ILSpy/TreeNodes/MethodTreeNode.cs
@@ -83,9 +83,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileMethod(MethodDefinition, output, options);
+ return true;
}
public override FilterResult Filter(FilterSettings settings)
diff --git a/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs b/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs
index 4d51f46af..a363c46db 100644
--- a/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs
+++ b/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs
@@ -72,10 +72,11 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, moduleName);
language.WriteCommentLine(output, containsMetadata ? "contains metadata" : "contains no metadata");
+ return true;
}
}
}
diff --git a/ILSpy/TreeNodes/NamespaceTreeNode.cs b/ILSpy/TreeNodes/NamespaceTreeNode.cs
index d054603dc..3b9ff4845 100644
--- a/ILSpy/TreeNodes/NamespaceTreeNode.cs
+++ b/ILSpy/TreeNodes/NamespaceTreeNode.cs
@@ -56,9 +56,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Recurse;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileNamespace(name, this.Children.OfType().Select(t => t.TypeDefinition), output, options);
+ return true;
}
}
}
diff --git a/ILSpy/TreeNodes/PropertyTreeNode.cs b/ILSpy/TreeNodes/PropertyTreeNode.cs
index 14769ea66..3267a1e1a 100644
--- a/ILSpy/TreeNodes/PropertyTreeNode.cs
+++ b/ILSpy/TreeNodes/PropertyTreeNode.cs
@@ -74,9 +74,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileProperty(PropertyDefinition, output, options);
+ return true;
}
public override bool IsPublicAPI {
diff --git a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
index 331f8da08..9121e213f 100644
--- a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
+++ b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
@@ -62,7 +62,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
this.Children.Add(new ModuleReferenceTreeNode(parentAssembly, r, metadata));
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, $"Detected Target-Framework-Id: {parentAssembly.LoadedAssembly.GetTargetFrameworkIdAsync().Result}");
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnsureLazyChildren));
@@ -86,7 +86,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
output.Unindent();
output.WriteLine();
}
-
+ return true;
}
}
}
diff --git a/ILSpy/TreeNodes/ResourceListTreeNode.cs b/ILSpy/TreeNodes/ResourceListTreeNode.cs
index c67c9d4b1..79cc1df50 100644
--- a/ILSpy/TreeNodes/ResourceListTreeNode.cs
+++ b/ILSpy/TreeNodes/ResourceListTreeNode.cs
@@ -64,13 +64,15 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Recurse;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnsureLazyChildren));
foreach (ILSpyTreeNode child in this.Children) {
child.Decompile(language, output, options);
output.WriteLine();
}
+
+ return true;
}
}
}
diff --git a/ILSpy/TreeNodes/ResourceNodes/ImageListResourceEntryNode.cs b/ILSpy/TreeNodes/ResourceNodes/ImageListResourceEntryNode.cs
index fb8b2199f..3805db739 100644
--- a/ILSpy/TreeNodes/ResourceNodes/ImageListResourceEntryNode.cs
+++ b/ILSpy/TreeNodes/ResourceNodes/ImageListResourceEntryNode.cs
@@ -75,9 +75,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
EnsureLazyChildren();
+ return true;
}
}
}
diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs
index ec74ad193..cf5a3d5ac 100644
--- a/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs
+++ b/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs
@@ -73,9 +73,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
return result;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, string.Format("{0} = {1}", key, data));
+ return true;
}
public override bool Save(DecompilerTextView textView)
diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs
index 3433f70d7..c9adaa49c 100644
--- a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs
+++ b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs
@@ -67,7 +67,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, string.Format("{0} ({1}, {2})", r.Name, r.ResourceType, r.Attributes));
@@ -76,6 +76,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
smartOutput.AddButton(Images.Save, Resources.Save, delegate { Save(MainWindow.Instance.TextView); });
output.WriteLine();
}
+
+ return true;
}
public override bool View(DecompilerTextView textView)
diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs
index c1d908481..a44edeae4 100644
--- a/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs
+++ b/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs
@@ -141,7 +141,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return true;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
EnsureLazyChildren();
base.Decompile(language, output, options);
@@ -168,6 +168,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
output.WriteLine();
}
+
+ return true;
}
internal class SerializedObjectRepresentation
diff --git a/ILSpy/TreeNodes/ThreadingSupport.cs b/ILSpy/TreeNodes/ThreadingSupport.cs
index e95787efc..8df078cad 100644
--- a/ILSpy/TreeNodes/ThreadingSupport.cs
+++ b/ILSpy/TreeNodes/ThreadingSupport.cs
@@ -128,8 +128,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Match;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
+ return false;
}
}
@@ -151,8 +152,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Match;
}
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
+ return false;
}
}
diff --git a/ILSpy/TreeNodes/TypeTreeNode.cs b/ILSpy/TreeNodes/TypeTreeNode.cs
index 3ef91c411..4464a3521 100644
--- a/ILSpy/TreeNodes/TypeTreeNode.cs
+++ b/ILSpy/TreeNodes/TypeTreeNode.cs
@@ -103,9 +103,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
public override bool CanExpandRecursively => true;
- public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileType(TypeDefinition, output, options);
+ return true;
}
public override object Icon => GetIcon(TypeDefinition);
From a63e94e5b4bc22d0ea3687b6bfc13c983c95c725 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer
Date: Wed, 24 Jul 2019 00:56:54 +0200
Subject: [PATCH 06/10] Refactor Solution decompilation to use Language instead
of AssemblyTreeNode.
---
.../CSharp/WholeProjectDecompiler.cs | 1 +
.../ICSharpCode.Decompiler.csproj | 4 +-
ICSharpCode.Decompiler/Solution/ProjectId.cs | 56 +++++++
.../Solution/ProjectItem.cs | 55 +++++++
.../{CSharp => Solution}/SolutionCreator.cs | 70 +--------
ILSpy/Languages/CSharpLanguage.cs | 8 +-
ILSpy/Languages/ILLanguage.cs | 7 +-
ILSpy/Languages/Language.cs | 8 +-
ILSpy/MainWindow.xaml.cs | 85 ++++++++---
ILSpy/SolutionWriter.cs | 140 ++++++------------
ILSpy/TreeNodes/AssemblyListTreeNode.cs | 4 +-
ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs | 4 +-
ILSpy/TreeNodes/AssemblyTreeNode.cs | 12 +-
ILSpy/TreeNodes/BaseTypesEntryNode.cs | 3 +-
ILSpy/TreeNodes/BaseTypesTreeNode.cs | 4 +-
ILSpy/TreeNodes/DerivedTypesEntryNode.cs | 3 +-
ILSpy/TreeNodes/DerivedTypesTreeNode.cs | 3 +-
ILSpy/TreeNodes/EventTreeNode.cs | 3 +-
ILSpy/TreeNodes/FieldTreeNode.cs | 3 +-
ILSpy/TreeNodes/ILSpyTreeNode.cs | 2 +-
ILSpy/TreeNodes/MethodTreeNode.cs | 3 +-
ILSpy/TreeNodes/ModuleReferenceTreeNode.cs | 3 +-
ILSpy/TreeNodes/NamespaceTreeNode.cs | 3 +-
ILSpy/TreeNodes/PropertyTreeNode.cs | 3 +-
ILSpy/TreeNodes/ReferenceFolderTreeNode.cs | 3 +-
ILSpy/TreeNodes/ResourceListTreeNode.cs | 4 +-
.../ImageListResourceEntryNode.cs | 3 +-
.../ResourceNodes/ResourceEntryNode.cs | 3 +-
.../ResourceNodes/ResourceTreeNode.cs | 4 +-
.../ResourceNodes/ResourcesFileTreeNode.cs | 4 +-
ILSpy/TreeNodes/ThreadingSupport.cs | 6 +-
ILSpy/TreeNodes/TypeTreeNode.cs | 3 +-
32 files changed, 268 insertions(+), 249 deletions(-)
create mode 100644 ICSharpCode.Decompiler/Solution/ProjectId.cs
create mode 100644 ICSharpCode.Decompiler/Solution/ProjectItem.cs
rename ICSharpCode.Decompiler/{CSharp => Solution}/SolutionCreator.cs (78%)
diff --git a/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
index 6ec5d6802..377a617d5 100644
--- a/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
@@ -35,6 +35,7 @@ using System.Reflection.Metadata;
using static ICSharpCode.Decompiler.Metadata.DotNetCorePathFinderExtensions;
using static ICSharpCode.Decompiler.Metadata.MetadataExtensions;
using ICSharpCode.Decompiler.Metadata;
+using ICSharpCode.Decompiler.Solution;
namespace ICSharpCode.Decompiler.CSharp
{
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 6efca3120..afb73dcf4 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -61,7 +61,9 @@
-
+
+
+
diff --git a/ICSharpCode.Decompiler/Solution/ProjectId.cs b/ICSharpCode.Decompiler/Solution/ProjectId.cs
new file mode 100644
index 000000000..65a4e8d9e
--- /dev/null
+++ b/ICSharpCode.Decompiler/Solution/ProjectId.cs
@@ -0,0 +1,56 @@
+// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+
+namespace ICSharpCode.Decompiler.Solution
+{
+ ///
+ /// A container class that holds platform and GUID information about a Visual Studio project.
+ ///
+ public class ProjectId
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The project platform.
+ /// The project GUID.
+ ///
+ /// Thrown when
+ /// or is null or empty.
+ public ProjectId(string projectPlatform, Guid projectGuid)
+ {
+ if (string.IsNullOrWhiteSpace(projectPlatform)) {
+ throw new ArgumentException("The platform cannot be null or empty.", nameof(projectPlatform));
+ }
+
+ Guid = projectGuid;
+ PlatformName = projectPlatform;
+ }
+
+ ///
+ /// Gets the GUID of this project.
+ ///
+ public Guid Guid { get; }
+
+ ///
+ /// Gets the platform name of this project. Only single platform per project is supported.
+ ///
+ public string PlatformName { get; }
+ }
+}
diff --git a/ICSharpCode.Decompiler/Solution/ProjectItem.cs b/ICSharpCode.Decompiler/Solution/ProjectItem.cs
new file mode 100644
index 000000000..bf1222368
--- /dev/null
+++ b/ICSharpCode.Decompiler/Solution/ProjectItem.cs
@@ -0,0 +1,55 @@
+// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.IO;
+
+namespace ICSharpCode.Decompiler.Solution
+{
+ ///
+ /// A container class that holds information about a Visual Studio project.
+ ///
+ public sealed class ProjectItem : ProjectId
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The full path of the project file.
+ /// The project platform.
+ /// The project GUID.
+ ///
+ /// Thrown when
+ /// or is null or empty.
+ public ProjectItem(string projectFile, string projectPlatform, Guid projectGuid)
+ : base(projectPlatform, projectGuid)
+ {
+ ProjectName = Path.GetFileNameWithoutExtension(projectFile);
+ FilePath = projectFile;
+ }
+
+ ///
+ /// Gets the name of the project.
+ ///
+ public string ProjectName { get; }
+
+ ///
+ /// Gets the full path to the project file.
+ ///
+ public string FilePath { get; }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/SolutionCreator.cs b/ICSharpCode.Decompiler/Solution/SolutionCreator.cs
similarity index 78%
rename from ICSharpCode.Decompiler/CSharp/SolutionCreator.cs
rename to ICSharpCode.Decompiler/Solution/SolutionCreator.cs
index 7c3f26e0e..fc63beca5 100644
--- a/ICSharpCode.Decompiler/CSharp/SolutionCreator.cs
+++ b/ICSharpCode.Decompiler/Solution/SolutionCreator.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2016 Daniel Grunwald
+// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
@@ -22,74 +22,8 @@ using System.IO;
using System.Linq;
using System.Xml.Linq;
-namespace ICSharpCode.Decompiler.CSharp
+namespace ICSharpCode.Decompiler.Solution
{
- ///
- /// A container class that holds information about a Visual Studio project.
- ///
- public sealed class ProjectItem : ProjectId
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The full path of the project file.
- /// The project platform.
- /// The project GUID.
- ///
- /// Thrown when
- /// or is null or empty.
- public ProjectItem(string projectFile, string projectPlatform, Guid projectGuid)
- : base(projectPlatform, projectGuid)
- {
- ProjectName = Path.GetFileNameWithoutExtension(projectFile);
- FilePath = projectFile;
- }
-
- ///
- /// Gets the name of the project.
- ///
- public string ProjectName { get; }
-
- ///
- /// Gets the full path to the project file.
- ///
- public string FilePath { get; }
- }
-
- ///
- /// A container class that holds platform and GUID information about a Visual Studio project.
- ///
- public class ProjectId
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The project platform.
- /// The project GUID.
- ///
- /// Thrown when
- /// or is null or empty.
- public ProjectId(string projectPlatform, Guid projectGuid)
- {
- if (string.IsNullOrWhiteSpace(projectPlatform)) {
- throw new ArgumentException("The platform cannot be null or empty.", nameof(projectPlatform));
- }
-
- Guid = projectGuid;
- PlatformName = projectPlatform;
- }
-
- ///
- /// Gets the GUID of this project.
- ///
- public Guid Guid { get; }
-
- ///
- /// Gets the platform name of this project. Only single platform per project is supported.
- ///
- public string PlatformName { get; }
- }
-
///
/// A helper class that can write a Visual Studio Solution file for the provided projects.
///
diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs
index 57cab0ee8..35f614764 100644
--- a/ILSpy/Languages/CSharpLanguage.cs
+++ b/ILSpy/Languages/CSharpLanguage.cs
@@ -34,6 +34,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Output;
+using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpy.TreeNodes;
@@ -343,7 +344,7 @@ namespace ICSharpCode.ILSpy
}
}
- public override object DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
+ public override ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
var module = assembly.GetPEFileOrNull();
if (options.FullDecompilation && options.SaveAsProjectDirectory != null) {
@@ -389,7 +390,7 @@ namespace ICSharpCode.ILSpy
}
if (metadata.IsAssembly) {
var asm = metadata.GetAssemblyDefinition();
- if (asm.HashAlgorithm != System.Reflection.AssemblyHashAlgorithm.None)
+ if (asm.HashAlgorithm != AssemblyHashAlgorithm.None)
output.WriteLine("// Hash algorithm: " + asm.HashAlgorithm.ToString().ToUpper());
if (!asm.PublicKey.IsNil) {
output.Write("// Public key: ");
@@ -415,8 +416,7 @@ namespace ICSharpCode.ILSpy
}
WriteCode(output, options.DecompilerSettings, st, decompiler.TypeSystem);
}
-
- return true;
+ return null;
}
}
diff --git a/ILSpy/Languages/ILLanguage.cs b/ILSpy/Languages/ILLanguage.cs
index da26caeb2..3dd1ec1ab 100644
--- a/ILSpy/Languages/ILLanguage.cs
+++ b/ILSpy/Languages/ILLanguage.cs
@@ -27,6 +27,8 @@ using System.Reflection.Metadata.Ecma335;
using System.Linq;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.Util;
+using ICSharpCode.Decompiler.Solution;
namespace ICSharpCode.ILSpy
{
@@ -150,7 +152,7 @@ namespace ICSharpCode.ILSpy
dis.DisassembleNamespace(nameSpace, module, types.Select(t => (TypeDefinitionHandle)t.MetadataToken));
}
- public override object DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
+ public override ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
output.WriteLine("// " + assembly.FileName);
output.WriteLine();
@@ -174,8 +176,7 @@ namespace ICSharpCode.ILSpy
dis.WriteModuleContents(module);
}
}
-
- return true;
+ return null;
}
}
}
diff --git a/ILSpy/Languages/Language.cs b/ILSpy/Languages/Language.cs
index 76f435244..e97696fea 100644
--- a/ILSpy/Languages/Language.cs
+++ b/ILSpy/Languages/Language.cs
@@ -23,6 +23,7 @@ using System.Reflection.PortableExecutable;
using System.Text;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
+using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
@@ -131,11 +132,11 @@ namespace ICSharpCode.ILSpy
WriteCommentLine(output, nameSpace);
}
- public virtual object DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
+ public virtual ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
WriteCommentLine(output, assembly.FileName);
var asm = assembly.GetPEFileOrNull();
- if (asm == null) return false;
+ if (asm == null) return null;
var metadata = asm.Metadata;
if (metadata.IsAssembly) {
var name = metadata.GetAssemblyDefinition();
@@ -147,8 +148,7 @@ namespace ICSharpCode.ILSpy
} else {
WriteCommentLine(output, metadata.GetString(metadata.GetModuleDefinition().Name));
}
-
- return true;
+ return null;
}
public virtual void WriteCommentLine(ITextOutput output, string comment)
diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs
index 238c74307..fe7c45374 100644
--- a/ILSpy/MainWindow.xaml.cs
+++ b/ILSpy/MainWindow.xaml.cs
@@ -914,29 +914,80 @@ namespace ICSharpCode.ILSpy
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
var selectedNodes = SelectedNodes.ToList();
- if (selectedNodes.Count > 1) {
- var assemblyNodes = selectedNodes
- .OfType()
- .Where(n => n.Language is CSharpLanguage)
- .ToList();
-
- if (assemblyNodes.Count == selectedNodes.Count) {
- var initialPath = Path.GetDirectoryName(assemblyNodes[0].LoadedAssembly.FileName);
- var selectedPath = SolutionWriter.SelectSolutionFile(initialPath);
-
- if (!string.IsNullOrEmpty(selectedPath)) {
- SolutionWriter.CreateSolution(TextView, selectedPath, assemblyNodes);
- }
+ if (selectedNodes.Count == 1) {
+ // if there's only one treenode selected
+ // we will invoke the custom Save logic
+ if (selectedNodes[0].Save(TextView))
return;
+ } else if (selectedNodes.All(n => n is AssemblyTreeNode)) {
+ var initialPath = Path.GetDirectoryName(((AssemblyTreeNode)selectedNodes[0]).LoadedAssembly.FileName);
+ var selectedPath = SelectSolutionFile(initialPath);
+
+ if (!string.IsNullOrEmpty(selectedPath)) {
+ var assemblies = selectedNodes.OfType()
+ .Select(n => n.LoadedAssembly)
+ .Where(a => a != null).ToArray();
+ SolutionWriter.CreateSolution(TextView, selectedPath, CurrentLanguage, assemblies);
}
+ return;
}
- if (selectedNodes.Count != 1 || !selectedNodes[0].Save(TextView)) {
- var options = new DecompilationOptions() { FullDecompilation = true };
- TextView.SaveToDisk(CurrentLanguage, selectedNodes, options);
+ // Fallback: if nobody was able to handle the request, use default behavior.
+ // try to save all nodes to disk.
+ var options = new DecompilationOptions() { FullDecompilation = true };
+ TextView.SaveToDisk(CurrentLanguage, selectedNodes, options);
+ }
+
+ ///
+ /// Shows a File Selection dialog where the user can select the target file for the solution.
+ ///
+ /// The initial path to show in the dialog. If not specified, the 'Documents' directory
+ /// will be used.
+ ///
+ /// The full path of the selected target file, or null if the user canceled.
+ string SelectSolutionFile(string path)
+ {
+ const string SolutionExtension = ".sln";
+ const string DefaultSolutionName = "Solution";
+
+ if (string.IsNullOrWhiteSpace(path)) {
+ path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
}
+
+ SaveFileDialog dlg = new SaveFileDialog();
+ dlg.InitialDirectory = path;
+ dlg.FileName = Path.Combine(path, DefaultSolutionName + SolutionExtension);
+ dlg.Filter = "Visual Studio Solution file|*" + SolutionExtension;
+
+ bool targetInvalid;
+ do {
+ if (dlg.ShowDialog() != true) {
+ return null;
+ }
+
+ string selectedPath = Path.GetDirectoryName(dlg.FileName);
+ try {
+ targetInvalid = Directory.EnumerateFileSystemEntries(selectedPath).Any();
+ } catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is System.Security.SecurityException) {
+ MessageBox.Show(
+ "The directory cannot be accessed. Please ensure it exists and you have sufficient rights to access it.",
+ "Solution directory not accessible",
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ targetInvalid = true;
+ continue;
+ }
+
+ if (targetInvalid) {
+ MessageBox.Show(
+ "The directory is not empty. Please select an empty directory.",
+ "Solution directory not empty",
+ MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ } while (targetInvalid);
+
+ return dlg.FileName;
}
-
+
public void RefreshDecompiledView()
{
try {
diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs
index 45fd3ce67..d892e22ec 100644
--- a/ILSpy/SolutionWriter.cs
+++ b/ILSpy/SolutionWriter.cs
@@ -22,15 +22,12 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Security;
using System.Threading;
using System.Threading.Tasks;
-using System.Windows;
using ICSharpCode.Decompiler;
-using ICSharpCode.Decompiler.CSharp;
+using ICSharpCode.Decompiler.Solution;
+using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpy.TextView;
-using ICSharpCode.ILSpy.TreeNodes;
-using Microsoft.Win32;
namespace ICSharpCode.ILSpy
{
@@ -38,73 +35,23 @@ namespace ICSharpCode.ILSpy
/// An utility class that creates a Visual Studio solution containing projects for the
/// decompiled assemblies.
///
- internal static class SolutionWriter
+ internal class SolutionWriter
{
- private const string SolutionExtension = ".sln";
- private const string DefaultSolutionName = "Solution";
-
- ///
- /// Shows a File Selection dialog where the user can select the target file for the solution.
- ///
- /// The initial path to show in the dialog. If not specified, the 'Documents' directory
- /// will be used.
- ///
- /// The full path of the selected target file, or null if the user canceled.
- public static string SelectSolutionFile(string path)
- {
- if (string.IsNullOrWhiteSpace(path)) {
- path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
- }
-
- SaveFileDialog dlg = new SaveFileDialog();
- dlg.InitialDirectory = path;
- dlg.FileName = Path.Combine(path, DefaultSolutionName + SolutionExtension);
- dlg.Filter = "Visual Studio Solution file|*" + SolutionExtension;
-
- bool targetInvalid;
- do {
- if (dlg.ShowDialog() != true) {
- return null;
- }
-
- string selectedPath = Path.GetDirectoryName(dlg.FileName);
- try {
- targetInvalid = Directory.EnumerateFileSystemEntries(selectedPath).Any();
- } catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is SecurityException) {
- MessageBox.Show(
- "The directory cannot be accessed. Please ensure it exists and you have sufficient rights to access it.",
- "Solution directory not accessible",
- MessageBoxButton.OK, MessageBoxImage.Error);
- targetInvalid = true;
- continue;
- }
-
- if (targetInvalid) {
- MessageBox.Show(
- "The directory is not empty. Please select an empty directory.",
- "Solution directory not empty",
- MessageBoxButton.OK, MessageBoxImage.Warning);
- }
- } while (targetInvalid);
-
- return dlg.FileName;
- }
-
///
/// Creates a Visual Studio solution that contains projects with decompiled code
- /// of the specified . The solution file will be saved
+ /// of the specified . The solution file will be saved
/// to the . The directory of this file must either
/// be empty or not exist.
///
/// A reference to the instance.
/// The target file path of the solution file.
- /// The assembly nodes to decompile.
+ /// The assembly nodes to decompile.
///
/// Thrown when is null,
/// an empty or a whitespace string.
/// Thrown when > or
- /// is null.
- public static void CreateSolution(DecompilerTextView textView, string solutionFilePath, IEnumerable assemblyNodes)
+ /// is null.
+ public static void CreateSolution(DecompilerTextView textView, string solutionFilePath, Language language, IEnumerable assemblies)
{
if (textView == null) {
throw new ArgumentNullException(nameof(textView));
@@ -114,29 +61,37 @@ namespace ICSharpCode.ILSpy
throw new ArgumentException("The solution file path cannot be null or empty.", nameof(solutionFilePath));
}
- if (assemblyNodes == null) {
- throw new ArgumentNullException(nameof(assemblyNodes));
+ if (assemblies == null) {
+ throw new ArgumentNullException(nameof(assemblies));
}
+ var writer = new SolutionWriter(solutionFilePath);
+
textView
- .RunWithCancellation(ct => CreateSolution(solutionFilePath, assemblyNodes, ct))
+ .RunWithCancellation(ct => writer.CreateSolution(assemblies, language, ct))
.Then(output => textView.ShowText(output))
.HandleExceptions();
}
- private static async Task CreateSolution(
- string solutionFilePath,
- IEnumerable assemblyNodes,
- CancellationToken ct)
+ readonly string solutionFilePath;
+ readonly string solutionDirectory;
+ readonly ConcurrentBag projects;
+ readonly ConcurrentBag statusOutput;
+
+ SolutionWriter(string solutionFilePath)
{
- var solutionDirectory = Path.GetDirectoryName(solutionFilePath);
- var statusOutput = new ConcurrentBag();
- var projects = new ConcurrentBag();
+ this.solutionFilePath = solutionFilePath;
+ solutionDirectory = Path.GetDirectoryName(solutionFilePath);
+ statusOutput = new ConcurrentBag();
+ projects = new ConcurrentBag();
+ }
+ async Task CreateSolution(IEnumerable assemblies, Language language, CancellationToken ct)
+ {
var result = new AvalonEditTextOutput();
var duplicates = new HashSet();
- if (assemblyNodes.Any(n => !duplicates.Add(n.LoadedAssembly.ShortName))) {
+ if (assemblies.Any(asm => !duplicates.Add(asm.ShortName))) {
result.WriteLine("Duplicate assembly names selected, cannot generate a solution.");
return result;
}
@@ -144,7 +99,7 @@ namespace ICSharpCode.ILSpy
Stopwatch stopwatch = Stopwatch.StartNew();
try {
- await Task.Run(() => Parallel.ForEach(assemblyNodes, n => WriteProject(n, solutionDirectory, statusOutput, projects, ct)))
+ await Task.Run(() => Parallel.ForEach(assemblies, n => WriteProject(n, language, solutionDirectory, ct)))
.ConfigureAwait(false);
await Task.Run(() => SolutionCreator.WriteSolutionFile(solutionFilePath, projects))
@@ -162,7 +117,7 @@ namespace ICSharpCode.ILSpy
result.WriteLine(e.Message);
return true;
});
-
+
return result;
}
@@ -172,13 +127,13 @@ namespace ICSharpCode.ILSpy
if (statusOutput.Count == 0) {
result.WriteLine("Successfully decompiled the following assemblies into Visual Studio projects:");
- foreach (var item in assemblyNodes.Select(n => n.Text.ToString())) {
+ foreach (var item in assemblies.Select(n => n.Text.ToString())) {
result.WriteLine(item);
}
result.WriteLine();
- if (assemblyNodes.Count() == projects.Count) {
+ if (assemblies.Count() == projects.Count) {
result.WriteLine("Created the Visual Studio Solution file.");
}
@@ -191,17 +146,10 @@ namespace ICSharpCode.ILSpy
return result;
}
- private static void WriteProject(
- AssemblyTreeNode assemblyNode,
- string targetDirectory,
- ConcurrentBag statusOutput,
- ConcurrentBag targetContainer,
- CancellationToken ct)
+ void WriteProject(LoadedAssembly loadedAssembly, Language language, string targetDirectory, CancellationToken ct)
{
- var loadedAssembly = assemblyNode.LoadedAssembly;
-
targetDirectory = Path.Combine(targetDirectory, loadedAssembly.ShortName);
- string projectFileName = Path.Combine(targetDirectory, loadedAssembly.ShortName + assemblyNode.Language.ProjectFileExtension);
+ string projectFileName = Path.Combine(targetDirectory, loadedAssembly.ShortName + language.ProjectFileExtension);
if (!Directory.Exists(targetDirectory)) {
try {
@@ -214,21 +162,19 @@ namespace ICSharpCode.ILSpy
try {
using (var projectFileWriter = new StreamWriter(projectFileName)) {
- var projectFileOutput = new PlainTextOutput(projectFileWriter);
- var options = new DecompilationOptions() {
- FullDecompilation = true,
- CancellationToken = ct,
- SaveAsProjectDirectory = targetDirectory };
-
- if (assemblyNode.Decompile(assemblyNode.Language, projectFileOutput, options) is ProjectId projectInfo) {
- targetContainer.Add(new ProjectItem(projectFileName, projectInfo.PlatformName, projectInfo.Guid));
+ var projectFileOutput = new PlainTextOutput(projectFileWriter);
+ var options = new DecompilationOptions() {
+ FullDecompilation = true,
+ CancellationToken = ct,
+ SaveAsProjectDirectory = targetDirectory
+ };
+
+ var projectInfo = language.DecompileAssembly(loadedAssembly, projectFileOutput, options);
+ if (projectInfo != null) {
+ projects.Add(new ProjectItem(projectFileName, projectInfo.PlatformName, projectInfo.Guid));
}
}
- }
- catch (OperationCanceledException) {
- throw;
- }
- catch (Exception e) {
+ } catch (Exception e) when (!(e is OperationCanceledException)) {
statusOutput.Add($"Failed to decompile the assembly '{loadedAssembly.FileName}':{Environment.NewLine}{e}");
}
}
diff --git a/ILSpy/TreeNodes/AssemblyListTreeNode.cs b/ILSpy/TreeNodes/AssemblyListTreeNode.cs
index 3b8e5a519..e53a3a883 100644
--- a/ILSpy/TreeNodes/AssemblyListTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyListTreeNode.cs
@@ -141,7 +141,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
public Action Select = delegate { };
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, "List: " + assemblyList.ListName);
output.WriteLine();
@@ -150,8 +150,6 @@ namespace ICSharpCode.ILSpy.TreeNodes
output.WriteLine();
asm.Decompile(language, output, options);
}
-
- return true;
}
#region Find*Node
diff --git a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
index 192820246..8aa8f8c0d 100644
--- a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
@@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
var loaded = parentAssembly.LoadedAssembly.LoadedAssemblyReferencesInfo.TryGetInfo(r.FullName, out var info);
if (r.IsWindowsRuntime) {
@@ -98,8 +98,6 @@ namespace ICSharpCode.ILSpy.TreeNodes
output.Unindent();
output.WriteLine();
}
-
- return true;
}
}
}
diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs
index 39669c15f..60f34e6b2 100644
--- a/ILSpy/TreeNodes/AssemblyTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs
@@ -240,7 +240,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Recurse;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
void HandleException(Exception ex, string message)
{
@@ -259,21 +259,21 @@ namespace ICSharpCode.ILSpy.TreeNodes
switch (ex.InnerException) {
case BadImageFormatException badImage:
HandleException(badImage, "This file does not contain a managed assembly.");
- return null;
+ return;
case FileNotFoundException fileNotFound:
HandleException(fileNotFound, "The file was not found.");
- return null;
+ return;
case DirectoryNotFoundException dirNotFound:
HandleException(dirNotFound, "The directory was not found.");
- return null;
+ return;
case PEFileNotSupportedException notSupported:
HandleException(notSupported, notSupported.Message);
- return null;
+ return;
default:
throw;
}
}
- return language.DecompileAssembly(LoadedAssembly, output, options);
+ language.DecompileAssembly(LoadedAssembly, output, options);
}
public override bool Save(DecompilerTextView textView)
diff --git a/ILSpy/TreeNodes/BaseTypesEntryNode.cs b/ILSpy/TreeNodes/BaseTypesEntryNode.cs
index 41edd0f8c..3c96c40dd 100644
--- a/ILSpy/TreeNodes/BaseTypesEntryNode.cs
+++ b/ILSpy/TreeNodes/BaseTypesEntryNode.cs
@@ -97,10 +97,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
return false;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, language.TypeToString(type, includeNamespace: true));
- return true;
}
IEntity IMemberTreeNode.Member {
diff --git a/ILSpy/TreeNodes/BaseTypesTreeNode.cs b/ILSpy/TreeNodes/BaseTypesTreeNode.cs
index 58565a938..8cbdb2304 100644
--- a/ILSpy/TreeNodes/BaseTypesTreeNode.cs
+++ b/ILSpy/TreeNodes/BaseTypesTreeNode.cs
@@ -69,14 +69,12 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnsureLazyChildren));
foreach (ILSpyTreeNode child in this.Children) {
child.Decompile(language, output, options);
}
-
- return true;
}
}
}
\ No newline at end of file
diff --git a/ILSpy/TreeNodes/DerivedTypesEntryNode.cs b/ILSpy/TreeNodes/DerivedTypesEntryNode.cs
index f3d9fe29f..91da3e033 100644
--- a/ILSpy/TreeNodes/DerivedTypesEntryNode.cs
+++ b/ILSpy/TreeNodes/DerivedTypesEntryNode.cs
@@ -90,10 +90,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
e.Handled = BaseTypesEntryNode.ActivateItem(this, type);
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, language.TypeToString(type, includeNamespace: true));
- return true;
}
IEntity IMemberTreeNode.Member => type;
diff --git a/ILSpy/TreeNodes/DerivedTypesTreeNode.cs b/ILSpy/TreeNodes/DerivedTypesTreeNode.cs
index 8518e08e3..3b6e50a30 100644
--- a/ILSpy/TreeNodes/DerivedTypesTreeNode.cs
+++ b/ILSpy/TreeNodes/DerivedTypesTreeNode.cs
@@ -92,10 +92,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
return typeRef.GetFullTypeName(referenceMetadata) == typeDef.GetFullTypeName(definitionMetadata);
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
threading.Decompile(language, output, options, EnsureLazyChildren);
- return true;
}
}
}
\ No newline at end of file
diff --git a/ILSpy/TreeNodes/EventTreeNode.cs b/ILSpy/TreeNodes/EventTreeNode.cs
index c1797f794..1aadff896 100644
--- a/ILSpy/TreeNodes/EventTreeNode.cs
+++ b/ILSpy/TreeNodes/EventTreeNode.cs
@@ -67,10 +67,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileEvent(EventDefinition, output, options);
- return true;
}
public override bool IsPublicAPI {
diff --git a/ILSpy/TreeNodes/FieldTreeNode.cs b/ILSpy/TreeNodes/FieldTreeNode.cs
index 5d3323beb..5d4d2f7b0 100644
--- a/ILSpy/TreeNodes/FieldTreeNode.cs
+++ b/ILSpy/TreeNodes/FieldTreeNode.cs
@@ -68,10 +68,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileField(FieldDefinition, output, options);
- return true;
}
public override bool IsPublicAPI {
diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs
index 515188374..11fe3b278 100644
--- a/ILSpy/TreeNodes/ILSpyTreeNode.cs
+++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs
@@ -57,7 +57,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public abstract object Decompile(Language language, ITextOutput output, DecompilationOptions options);
+ public abstract void Decompile(Language language, ITextOutput output, DecompilationOptions options);
///
/// Used to implement special view logic for some items.
diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs
index 89c12ec10..9e4d95e45 100644
--- a/ILSpy/TreeNodes/MethodTreeNode.cs
+++ b/ILSpy/TreeNodes/MethodTreeNode.cs
@@ -83,10 +83,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileMethod(MethodDefinition, output, options);
- return true;
}
public override FilterResult Filter(FilterSettings settings)
diff --git a/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs b/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs
index a363c46db..4d51f46af 100644
--- a/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs
+++ b/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs
@@ -72,11 +72,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, moduleName);
language.WriteCommentLine(output, containsMetadata ? "contains metadata" : "contains no metadata");
- return true;
}
}
}
diff --git a/ILSpy/TreeNodes/NamespaceTreeNode.cs b/ILSpy/TreeNodes/NamespaceTreeNode.cs
index 3b9ff4845..d054603dc 100644
--- a/ILSpy/TreeNodes/NamespaceTreeNode.cs
+++ b/ILSpy/TreeNodes/NamespaceTreeNode.cs
@@ -56,10 +56,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Recurse;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileNamespace(name, this.Children.OfType().Select(t => t.TypeDefinition), output, options);
- return true;
}
}
}
diff --git a/ILSpy/TreeNodes/PropertyTreeNode.cs b/ILSpy/TreeNodes/PropertyTreeNode.cs
index 3267a1e1a..14769ea66 100644
--- a/ILSpy/TreeNodes/PropertyTreeNode.cs
+++ b/ILSpy/TreeNodes/PropertyTreeNode.cs
@@ -74,10 +74,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileProperty(PropertyDefinition, output, options);
- return true;
}
public override bool IsPublicAPI {
diff --git a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
index 9121e213f..fde4630f1 100644
--- a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
+++ b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
@@ -62,7 +62,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
this.Children.Add(new ModuleReferenceTreeNode(parentAssembly, r, metadata));
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, $"Detected Target-Framework-Id: {parentAssembly.LoadedAssembly.GetTargetFrameworkIdAsync().Result}");
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnsureLazyChildren));
@@ -86,7 +86,6 @@ namespace ICSharpCode.ILSpy.TreeNodes
output.Unindent();
output.WriteLine();
}
- return true;
}
}
}
diff --git a/ILSpy/TreeNodes/ResourceListTreeNode.cs b/ILSpy/TreeNodes/ResourceListTreeNode.cs
index 79cc1df50..c67c9d4b1 100644
--- a/ILSpy/TreeNodes/ResourceListTreeNode.cs
+++ b/ILSpy/TreeNodes/ResourceListTreeNode.cs
@@ -64,15 +64,13 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Recurse;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnsureLazyChildren));
foreach (ILSpyTreeNode child in this.Children) {
child.Decompile(language, output, options);
output.WriteLine();
}
-
- return true;
}
}
}
diff --git a/ILSpy/TreeNodes/ResourceNodes/ImageListResourceEntryNode.cs b/ILSpy/TreeNodes/ResourceNodes/ImageListResourceEntryNode.cs
index 3805db739..fb8b2199f 100644
--- a/ILSpy/TreeNodes/ResourceNodes/ImageListResourceEntryNode.cs
+++ b/ILSpy/TreeNodes/ResourceNodes/ImageListResourceEntryNode.cs
@@ -75,10 +75,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
EnsureLazyChildren();
- return true;
}
}
}
diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs
index cf5a3d5ac..ec74ad193 100644
--- a/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs
+++ b/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs
@@ -73,10 +73,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
return result;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, string.Format("{0} = {1}", key, data));
- return true;
}
public override bool Save(DecompilerTextView textView)
diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs
index c9adaa49c..3433f70d7 100644
--- a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs
+++ b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs
@@ -67,7 +67,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, string.Format("{0} ({1}, {2})", r.Name, r.ResourceType, r.Attributes));
@@ -76,8 +76,6 @@ namespace ICSharpCode.ILSpy.TreeNodes
smartOutput.AddButton(Images.Save, Resources.Save, delegate { Save(MainWindow.Instance.TextView); });
output.WriteLine();
}
-
- return true;
}
public override bool View(DecompilerTextView textView)
diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs
index a44edeae4..c1d908481 100644
--- a/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs
+++ b/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs
@@ -141,7 +141,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return true;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
EnsureLazyChildren();
base.Decompile(language, output, options);
@@ -168,8 +168,6 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
output.WriteLine();
}
-
- return true;
}
internal class SerializedObjectRepresentation
diff --git a/ILSpy/TreeNodes/ThreadingSupport.cs b/ILSpy/TreeNodes/ThreadingSupport.cs
index 8df078cad..e95787efc 100644
--- a/ILSpy/TreeNodes/ThreadingSupport.cs
+++ b/ILSpy/TreeNodes/ThreadingSupport.cs
@@ -128,9 +128,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Match;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
- return false;
}
}
@@ -152,9 +151,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Match;
}
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
- return false;
}
}
diff --git a/ILSpy/TreeNodes/TypeTreeNode.cs b/ILSpy/TreeNodes/TypeTreeNode.cs
index 4464a3521..3ef91c411 100644
--- a/ILSpy/TreeNodes/TypeTreeNode.cs
+++ b/ILSpy/TreeNodes/TypeTreeNode.cs
@@ -103,10 +103,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
public override bool CanExpandRecursively => true;
- public override object Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.DecompileType(TypeDefinition, output, options);
- return true;
}
public override object Icon => GetIcon(TypeDefinition);
From 24e492bfe306bdbd3718f76b5254d684ee2fe566 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer
Date: Wed, 24 Jul 2019 01:10:19 +0200
Subject: [PATCH 07/10] Adjust SaveCommandCanExecute to match
SaveCommandExecuted.
---
ILSpy/MainWindow.xaml | 2 +-
ILSpy/MainWindow.xaml.cs | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml
index 166c8ac80..ed8cc1b1b 100644
--- a/ILSpy/MainWindow.xaml
+++ b/ILSpy/MainWindow.xaml
@@ -28,7 +28,7 @@
Executed="RefreshCommandExecuted" />
n is AssemblyTreeNode);
+ e.CanExecute = selectedNodes.Count == 1 || (selectedNodes.Count > 1 && selectedNodes.All(n => n is AssemblyTreeNode));
}
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
@@ -919,7 +919,7 @@ namespace ICSharpCode.ILSpy
// we will invoke the custom Save logic
if (selectedNodes[0].Save(TextView))
return;
- } else if (selectedNodes.All(n => n is AssemblyTreeNode)) {
+ } else if (selectedNodes.Count > 1 && selectedNodes.All(n => n is AssemblyTreeNode)) {
var initialPath = Path.GetDirectoryName(((AssemblyTreeNode)selectedNodes[0]).LoadedAssembly.FileName);
var selectedPath = SelectSolutionFile(initialPath);
From 9dd22011d65fcdd91cb4f7a9b062d0fca6034722 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer
Date: Wed, 24 Jul 2019 08:05:37 +0200
Subject: [PATCH 08/10] Remove ILSpyTreeNode.Save accepting a file name, as it
is currently not necessary. Remove focused element check in
SaveCommandCanExecute, because saving the text view content independently
from the tree view selection is currently not supported.
---
ILSpy/Controls/ExtensionMethods.cs | 34 -----------------
ILSpy/MainWindow.xaml.cs | 7 +---
ILSpy/Properties/Resources.Designer.cs | 18 +++++++++
ILSpy/Properties/Resources.resx | 6 +++
ILSpy/TreeNodes/AssemblyTreeNode.cs | 52 ++++++++++----------------
ILSpy/TreeNodes/ILSpyTreeNode.cs | 9 -----
6 files changed, 44 insertions(+), 82 deletions(-)
diff --git a/ILSpy/Controls/ExtensionMethods.cs b/ILSpy/Controls/ExtensionMethods.cs
index 1c5e9f794..095ddc41e 100644
--- a/ILSpy/Controls/ExtensionMethods.cs
+++ b/ILSpy/Controls/ExtensionMethods.cs
@@ -19,7 +19,6 @@
using System;
using System.Windows;
using System.Windows.Markup;
-using System.Windows.Media;
namespace ICSharpCode.ILSpy.Controls
{
@@ -28,39 +27,6 @@ namespace ICSharpCode.ILSpy.Controls
///
public static class ExtensionMethods
{
- ///
- /// Checks if the current is contained in the visual tree of the
- /// object.
- ///
- /// The object to check, may be null.
- /// The object whose visual tree will be inspected.
- ///
- /// true if this object is contained in the visual tree of the ;
- /// otherwise, false.
- ///
- /// Thrown when is null.
- public static bool IsInVisualTreeOf(this DependencyObject thisObject, DependencyObject dependencyObject)
- {
- if (dependencyObject == null) {
- throw new ArgumentNullException(nameof(dependencyObject));
- }
-
- if (thisObject is null) {
- return false;
- }
-
- var parent = VisualTreeHelper.GetParent(thisObject);
- while (parent != null) {
- if (parent == dependencyObject) {
- return true;
- }
-
- parent = VisualTreeHelper.GetParent(parent);
- }
-
- return false;
- }
-
///
/// Sets the value of a dependency property on using a markup extension.
///
diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs
index 5bfb8366b..1fabedcd1 100644
--- a/ILSpy/MainWindow.xaml.cs
+++ b/ILSpy/MainWindow.xaml.cs
@@ -902,11 +902,6 @@ namespace ICSharpCode.ILSpy
void SaveCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.Handled = true;
- var focusedElement = FocusManager.GetFocusedElement(this) as DependencyObject;
- if (focusedElement.IsInVisualTreeOf(TextView)) {
- e.CanExecute = true;
- return;
- }
var selectedNodes = SelectedNodes.ToList();
e.CanExecute = selectedNodes.Count == 1 || (selectedNodes.Count > 1 && selectedNodes.All(n => n is AssemblyTreeNode));
}
@@ -926,7 +921,7 @@ namespace ICSharpCode.ILSpy
if (!string.IsNullOrEmpty(selectedPath)) {
var assemblies = selectedNodes.OfType()
.Select(n => n.LoadedAssembly)
- .Where(a => a != null).ToArray();
+ .Where(a => !a.HasLoadError).ToArray();
SolutionWriter.CreateSolution(TextView, selectedPath, CurrentLanguage, assemblies);
}
return;
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index 5c86e06e2..859948997 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -303,6 +303,24 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to The directory is not empty. File will be overwritten.\r\nAre you sure you want to continue?.
+ ///
+ public static string AssemblySaveCodeDirectoryNotEmpty {
+ get {
+ return ResourceManager.GetString("AssemblySaveCodeDirectoryNotEmpty", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Project Directory not empty.
+ ///
+ public static string AssemblySaveCodeDirectoryNotEmptyTitle {
+ get {
+ return ResourceManager.GetString("AssemblySaveCodeDirectoryNotEmptyTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Automatically check for updates every week.
///
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index 0128389d9..899febe76 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -729,4 +729,10 @@
Entity could not be resolved. Cannot analyze entities from missing assembly references. Add the missing reference and try again.
+
+ The directory is not empty. File will be overwritten.\r\nAre you sure you want to continue?
+
+
+ Project Directory not empty
+
\ No newline at end of file
diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs
index 60f34e6b2..13e2c1df2 100644
--- a/ILSpy/TreeNodes/AssemblyTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs
@@ -279,44 +279,30 @@ namespace ICSharpCode.ILSpy.TreeNodes
public override bool Save(DecompilerTextView textView)
{
Language language = this.Language;
- if (string.IsNullOrEmpty(language.ProjectFileExtension)) {
+ if (string.IsNullOrEmpty(language.ProjectFileExtension))
return false;
- }
-
SaveFileDialog dlg = new SaveFileDialog();
dlg.FileName = DecompilerTextView.CleanUpName(LoadedAssembly.ShortName) + language.ProjectFileExtension;
- dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension;
- if (dlg.ShowDialog() != true) {
- return true;
- }
-
- var targetDirectory = Path.GetDirectoryName(dlg.FileName);
- var existingFiles = Directory.GetFileSystemEntries(targetDirectory);
-
- if (existingFiles.Any(e => !string.Equals(e, dlg.FileName, StringComparison.OrdinalIgnoreCase))) {
- var result = MessageBox.Show(
- "The directory is not empty. File will be overwritten." + Environment.NewLine +
- "Are you sure you want to continue?",
- "Project Directory not empty",
- MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
- if (result == MessageBoxResult.No) {
- return true; // don't save, but mark the Save operation as handled
+ dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*";
+ if (dlg.ShowDialog() == true) {
+ DecompilationOptions options = new DecompilationOptions();
+ options.FullDecompilation = true;
+ if (dlg.FilterIndex == 1) {
+ options.SaveAsProjectDirectory = Path.GetDirectoryName(dlg.FileName);
+ foreach (string entry in Directory.GetFileSystemEntries(options.SaveAsProjectDirectory)) {
+ if (!string.Equals(entry, dlg.FileName, StringComparison.OrdinalIgnoreCase)) {
+ var result = MessageBox.Show(
+ Resources.AssemblySaveCodeDirectoryNotEmpty,
+ Resources.AssemblySaveCodeDirectoryNotEmptyTitle,
+ MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
+ if (result == MessageBoxResult.No)
+ return true; // don't save, but mark the Save operation as handled
+ break;
+ }
+ }
}
+ textView.SaveToDisk(language, new[] { this }, options, dlg.FileName);
}
-
- Save(textView, dlg.FileName);
- return true;
- }
-
- public override bool Save(DecompilerTextView textView, string fileName)
- {
- var targetDirectory = Path.GetDirectoryName(fileName);
- DecompilationOptions options = new DecompilationOptions {
- FullDecompilation = true,
- SaveAsProjectDirectory = targetDirectory
- };
-
- textView.SaveToDisk(Language, new[] { this }, options, fileName);
return true;
}
diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs
index 11fe3b278..fa1e663e4 100644
--- a/ILSpy/TreeNodes/ILSpyTreeNode.cs
+++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs
@@ -79,15 +79,6 @@ namespace ICSharpCode.ILSpy.TreeNodes
return false;
}
- ///
- /// Saves the content this node represents to the specified .
- /// The file will be silently overwritten.
- ///
- /// A reference to a instance.
- /// The target full path to save the content to.
- /// true on success; otherwise, false.
- public virtual bool Save(TextView.DecompilerTextView textView, string fileName) => Save(textView);
-
protected override void OnChildrenChanged(NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null) {
From 5fdeb223dde11dee57fac29e31dca37352af573d Mon Sep 17 00:00:00 2001
From: Siegfried Pammer
Date: Wed, 24 Jul 2019 08:59:33 +0200
Subject: [PATCH 09/10] "Save Code" should only work with ILSpyTreeNodes.
---
ILSpy/Commands/SaveCodeContextMenuEntry.cs | 105 +++++++++++++++++++--
ILSpy/MainWindow.xaml.cs | 77 +--------------
2 files changed, 101 insertions(+), 81 deletions(-)
diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs
index 85e5e301f..8ee945f0d 100644
--- a/ILSpy/Commands/SaveCodeContextMenuEntry.cs
+++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs
@@ -16,25 +16,118 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Windows;
using System.Windows.Input;
using ICSharpCode.ILSpy.Properties;
+using ICSharpCode.ILSpy.TreeNodes;
+using ICSharpCode.TreeView;
+using Microsoft.Win32;
namespace ICSharpCode.ILSpy.TextView
{
[ExportContextMenuEntry(Header = nameof(Resources._SaveCode), Category = nameof(Resources.Save), Icon = "Images/SaveFile.png")]
sealed class SaveCodeContextMenuEntry : IContextMenuEntry
{
- private readonly ICommand saveCommand;
+ public void Execute(TextViewContext context)
+ {
+ Execute(context.SelectedTreeNodes);
+ }
+
+ public bool IsEnabled(TextViewContext context) => true;
+
+ public bool IsVisible(TextViewContext context)
+ {
+ return CanExecute(context.SelectedTreeNodes);
+ }
- public SaveCodeContextMenuEntry()
+ public static bool CanExecute(IReadOnlyList selectedNodes)
{
- saveCommand = ApplicationCommands.Save;
+ if (selectedNodes == null || selectedNodes.Any(n => !(n is ILSpyTreeNode)))
+ return false;
+ return selectedNodes.Count == 1
+ || (selectedNodes.Count > 1 && (selectedNodes.All(n => n is AssemblyTreeNode) || selectedNodes.All(n => n is IMemberTreeNode)));
}
- public void Execute(TextViewContext context) => saveCommand.Execute(parameter: null);
+ public static void Execute(IReadOnlyList selectedNodes)
+ {
+ var currentLanguage = MainWindow.Instance.CurrentLanguage;
+ var textView = MainWindow.Instance.TextView;
+ if (selectedNodes.Count == 1 && selectedNodes[0] is ILSpyTreeNode singleSelection) {
+ // if there's only one treenode selected
+ // we will invoke the custom Save logic
+ if (singleSelection.Save(textView))
+ return;
+ } else if (selectedNodes.Count > 1 && selectedNodes.All(n => n is AssemblyTreeNode)) {
+ var initialPath = Path.GetDirectoryName(((AssemblyTreeNode)selectedNodes[0]).LoadedAssembly.FileName);
+ var selectedPath = SelectSolutionFile(initialPath);
- public bool IsEnabled(TextViewContext context) => true;
+ if (!string.IsNullOrEmpty(selectedPath)) {
+ var assemblies = selectedNodes.OfType()
+ .Select(n => n.LoadedAssembly)
+ .Where(a => !a.HasLoadError).ToArray();
+ SolutionWriter.CreateSolution(textView, selectedPath, currentLanguage, assemblies);
+ }
+ return;
+ }
- public bool IsVisible(TextViewContext context) => context.TreeView != null && saveCommand.CanExecute(parameter: null);
+ // Fallback: if nobody was able to handle the request, use default behavior.
+ // try to save all nodes to disk.
+ var options = new DecompilationOptions() { FullDecompilation = true };
+ textView.SaveToDisk(currentLanguage, selectedNodes.OfType(), options);
+ }
+
+ ///
+ /// Shows a File Selection dialog where the user can select the target file for the solution.
+ ///
+ /// The initial path to show in the dialog. If not specified, the 'Documents' directory
+ /// will be used.
+ ///
+ /// The full path of the selected target file, or null if the user canceled.
+ static string SelectSolutionFile(string path)
+ {
+ const string SolutionExtension = ".sln";
+ const string DefaultSolutionName = "Solution";
+
+ if (string.IsNullOrWhiteSpace(path)) {
+ path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ }
+
+ SaveFileDialog dlg = new SaveFileDialog();
+ dlg.InitialDirectory = path;
+ dlg.FileName = Path.Combine(path, DefaultSolutionName + SolutionExtension);
+ dlg.Filter = "Visual Studio Solution file|*" + SolutionExtension;
+
+ bool targetInvalid;
+ do {
+ if (dlg.ShowDialog() != true) {
+ return null;
+ }
+
+ string selectedPath = Path.GetDirectoryName(dlg.FileName);
+ try {
+ targetInvalid = Directory.EnumerateFileSystemEntries(selectedPath).Any();
+ } catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is System.Security.SecurityException) {
+ MessageBox.Show(
+ "The directory cannot be accessed. Please ensure it exists and you have sufficient rights to access it.",
+ "Solution directory not accessible",
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ targetInvalid = true;
+ continue;
+ }
+
+ if (targetInvalid) {
+ MessageBox.Show(
+ "The directory is not empty. Please select an empty directory.",
+ "Solution directory not empty",
+ MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ } while (targetInvalid);
+
+ return dlg.FileName;
+ }
}
}
diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs
index 1fabedcd1..5732b5673 100644
--- a/ILSpy/MainWindow.xaml.cs
+++ b/ILSpy/MainWindow.xaml.cs
@@ -902,85 +902,12 @@ namespace ICSharpCode.ILSpy
void SaveCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.Handled = true;
- var selectedNodes = SelectedNodes.ToList();
- e.CanExecute = selectedNodes.Count == 1 || (selectedNodes.Count > 1 && selectedNodes.All(n => n is AssemblyTreeNode));
+ e.CanExecute = SaveCodeContextMenuEntry.CanExecute(SelectedNodes.ToList());
}
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
- var selectedNodes = SelectedNodes.ToList();
- if (selectedNodes.Count == 1) {
- // if there's only one treenode selected
- // we will invoke the custom Save logic
- if (selectedNodes[0].Save(TextView))
- return;
- } else if (selectedNodes.Count > 1 && selectedNodes.All(n => n is AssemblyTreeNode)) {
- var initialPath = Path.GetDirectoryName(((AssemblyTreeNode)selectedNodes[0]).LoadedAssembly.FileName);
- var selectedPath = SelectSolutionFile(initialPath);
-
- if (!string.IsNullOrEmpty(selectedPath)) {
- var assemblies = selectedNodes.OfType()
- .Select(n => n.LoadedAssembly)
- .Where(a => !a.HasLoadError).ToArray();
- SolutionWriter.CreateSolution(TextView, selectedPath, CurrentLanguage, assemblies);
- }
- return;
- }
-
- // Fallback: if nobody was able to handle the request, use default behavior.
- // try to save all nodes to disk.
- var options = new DecompilationOptions() { FullDecompilation = true };
- TextView.SaveToDisk(CurrentLanguage, selectedNodes, options);
- }
-
- ///
- /// Shows a File Selection dialog where the user can select the target file for the solution.
- ///
- /// The initial path to show in the dialog. If not specified, the 'Documents' directory
- /// will be used.
- ///
- /// The full path of the selected target file, or null if the user canceled.
- string SelectSolutionFile(string path)
- {
- const string SolutionExtension = ".sln";
- const string DefaultSolutionName = "Solution";
-
- if (string.IsNullOrWhiteSpace(path)) {
- path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
- }
-
- SaveFileDialog dlg = new SaveFileDialog();
- dlg.InitialDirectory = path;
- dlg.FileName = Path.Combine(path, DefaultSolutionName + SolutionExtension);
- dlg.Filter = "Visual Studio Solution file|*" + SolutionExtension;
-
- bool targetInvalid;
- do {
- if (dlg.ShowDialog() != true) {
- return null;
- }
-
- string selectedPath = Path.GetDirectoryName(dlg.FileName);
- try {
- targetInvalid = Directory.EnumerateFileSystemEntries(selectedPath).Any();
- } catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is System.Security.SecurityException) {
- MessageBox.Show(
- "The directory cannot be accessed. Please ensure it exists and you have sufficient rights to access it.",
- "Solution directory not accessible",
- MessageBoxButton.OK, MessageBoxImage.Error);
- targetInvalid = true;
- continue;
- }
-
- if (targetInvalid) {
- MessageBox.Show(
- "The directory is not empty. Please select an empty directory.",
- "Solution directory not empty",
- MessageBoxButton.OK, MessageBoxImage.Warning);
- }
- } while (targetInvalid);
-
- return dlg.FileName;
+ SaveCodeContextMenuEntry.Execute(SelectedNodes.ToList());
}
public void RefreshDecompiledView()
From 470cd1ec23802759410d9b7af0f06da6b3635e3a Mon Sep 17 00:00:00 2001
From: Siegfried Pammer
Date: Fri, 26 Jul 2019 17:48:04 +0200
Subject: [PATCH 10/10] Fix SolutionCreator.GetRelativePath so that it properly
handles directories with '.' in the name.
---
.../Solution/SolutionCreator.cs | 32 +++++++------------
1 file changed, 12 insertions(+), 20 deletions(-)
diff --git a/ICSharpCode.Decompiler/Solution/SolutionCreator.cs b/ICSharpCode.Decompiler/Solution/SolutionCreator.cs
index fc63beca5..824f03a23 100644
--- a/ICSharpCode.Decompiler/Solution/SolutionCreator.cs
+++ b/ICSharpCode.Decompiler/Solution/SolutionCreator.cs
@@ -55,16 +55,16 @@ namespace ICSharpCode.Decompiler.Solution
}
using (var writer = new StreamWriter(targetFile)) {
- WriteSolutionFile(writer, projects, Path.GetDirectoryName(targetFile));
+ WriteSolutionFile(writer, projects, targetFile);
}
FixProjectReferences(projects);
}
- private static void WriteSolutionFile(TextWriter writer, IEnumerable projects, string solutionPath)
+ private static void WriteSolutionFile(TextWriter writer, IEnumerable projects, string solutionFilePath)
{
WriteHeader(writer);
- WriteProjects(writer, projects, solutionPath);
+ WriteProjects(writer, projects, solutionFilePath);
writer.WriteLine("Global");
@@ -86,12 +86,12 @@ namespace ICSharpCode.Decompiler.Solution
writer.WriteLine("MinimumVisualStudioVersion = 10.0.40219.1");
}
- private static void WriteProjects(TextWriter writer, IEnumerable projects, string solutionPath)
+ private static void WriteProjects(TextWriter writer, IEnumerable projects, string solutionFilePath)
{
var solutionGuid = Guid.NewGuid().ToString("B").ToUpperInvariant();
foreach (var project in projects) {
- var projectRelativePath = GetRelativePath(solutionPath, project.FilePath);
+ var projectRelativePath = GetRelativePath(solutionFilePath, project.FilePath);
var projectGuid = project.Guid.ToString("B").ToUpperInvariant();
writer.WriteLine($"Project(\"{solutionGuid}\") = \"{project.ProjectName}\", \"{projectRelativePath}\", \"{projectGuid}\"");
@@ -148,7 +148,6 @@ namespace ICSharpCode.Decompiler.Solution
var projectsMap = projects.ToDictionary(p => p.ProjectName, p => p);
foreach (var project in projects) {
- var projectDirectory = Path.GetDirectoryName(project.FilePath);
XDocument projectDoc = XDocument.Load(project.FilePath);
var referencesItemGroups = projectDoc.Root
@@ -156,14 +155,14 @@ namespace ICSharpCode.Decompiler.Solution
.Where(e => e.Elements(ProjectFileNamespace + "Reference").Any());
foreach (var itemGroup in referencesItemGroups) {
- FixProjectReferences(projectDirectory, itemGroup, projectsMap);
+ FixProjectReferences(project.FilePath, itemGroup, projectsMap);
}
projectDoc.Save(project.FilePath);
}
}
- private static void FixProjectReferences(string projectDirectory, XElement itemGroup, IDictionary projects)
+ private static void FixProjectReferences(string projectFilePath, XElement itemGroup, IDictionary projects)
{
foreach (var item in itemGroup.Elements(ProjectFileNamespace + "Reference").ToList()) {
var assemblyName = item.Attribute("Include")?.Value;
@@ -173,20 +172,20 @@ namespace ICSharpCode.Decompiler.Solution
var projectReference = new XElement(ProjectFileNamespace + "ProjectReference",
new XElement(ProjectFileNamespace + "Project", referencedProject.Guid.ToString("B").ToUpperInvariant()),
new XElement(ProjectFileNamespace + "Name", referencedProject.ProjectName));
- projectReference.SetAttributeValue("Include", GetRelativePath(projectDirectory, referencedProject.FilePath));
+ projectReference.SetAttributeValue("Include", GetRelativePath(projectFilePath, referencedProject.FilePath));
itemGroup.Add(projectReference);
}
}
}
- private static string GetRelativePath(string fromPath, string toPath)
+ private static string GetRelativePath(string fromFilePath, string toFilePath)
{
- Uri fromUri = new Uri(AppendDirectorySeparatorChar(fromPath));
- Uri toUri = new Uri(AppendDirectorySeparatorChar(toPath));
+ Uri fromUri = new Uri(fromFilePath);
+ Uri toUri = new Uri(toFilePath);
if (fromUri.Scheme != toUri.Scheme) {
- return toPath;
+ return toFilePath;
}
Uri relativeUri = fromUri.MakeRelativeUri(toUri);
@@ -198,12 +197,5 @@ namespace ICSharpCode.Decompiler.Solution
return relativePath;
}
-
- private static string AppendDirectorySeparatorChar(string path)
- {
- return Path.HasExtension(path) || path.EndsWith(Path.DirectorySeparatorChar.ToString())
- ? path
- : path + Path.DirectorySeparatorChar;
- }
}
}