Skip to content

Commit 38dccc8

Browse files
Implement 3330: Generate diagram from UI without advanced options
1 parent 36abb39 commit 38dccc8

File tree

5 files changed

+151
-2
lines changed

5 files changed

+151
-2
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright (c) 2024 Christoph Wille for the SharpDevelop Team
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
// software and associated documentation files (the "Software"), to deal in the Software
5+
// without restriction, including without limitation the rights to use, copy, modify, merge,
6+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+
// to whom the Software is furnished to do so, subject to the following conditions:
8+
//
9+
// The above copyright notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+
// DEALINGS IN THE SOFTWARE.
18+
19+
using System;
20+
using System.Composition;
21+
using System.Diagnostics;
22+
using System.IO;
23+
using System.Linq;
24+
using System.Threading.Tasks;
25+
using System.Windows;
26+
27+
using ICSharpCode.Decompiler;
28+
using ICSharpCode.ILSpy.Docking;
29+
using ICSharpCode.ILSpy.Properties;
30+
using ICSharpCode.ILSpy.TreeNodes;
31+
using ICSharpCode.ILSpyX.MermaidDiagrammer;
32+
33+
using Microsoft.Win32;
34+
35+
namespace ICSharpCode.ILSpy.TextView
36+
{
37+
[ExportContextMenuEntry(Header = nameof(Resources._CreateDiagram), Category = nameof(Resources.Save), Icon = "Images/Save")]
38+
[Shared]
39+
sealed class CreateDiagramContextMenuEntry(DockWorkspace dockWorkspace) : IContextMenuEntry
40+
{
41+
public void Execute(TextViewContext context)
42+
{
43+
var assembly = (context.SelectedTreeNodes?.FirstOrDefault() as AssemblyTreeNode)?.LoadedAssembly;
44+
if (assembly == null)
45+
return;
46+
47+
var selectedPath = SelectDestinationFolder();
48+
if (string.IsNullOrEmpty(selectedPath))
49+
return;
50+
51+
dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => {
52+
AvalonEditTextOutput output = new AvalonEditTextOutput();
53+
Stopwatch stopwatch = Stopwatch.StartNew();
54+
try
55+
{
56+
var command = new GenerateHtmlDiagrammer {
57+
Assembly = assembly.FileName,
58+
OutputFolder = selectedPath
59+
};
60+
61+
command.Run();
62+
}
63+
catch (OperationCanceledException)
64+
{
65+
output.WriteLine();
66+
output.WriteLine(Resources.GenerationWasCancelled);
67+
throw;
68+
}
69+
stopwatch.Stop();
70+
output.WriteLine(Resources.GenerationCompleteInSeconds, stopwatch.Elapsed.TotalSeconds.ToString("F1"));
71+
output.WriteLine();
72+
73+
var diagramHtml = Path.Combine(selectedPath, "index.html");
74+
output.AddButton(null, Resources.OpenExplorer, delegate { Process.Start("explorer", "/select,\"" + diagramHtml + "\""); });
75+
output.WriteLine();
76+
return output;
77+
}, ct), Properties.Resources.CreatingDiagram).Then(dockWorkspace.ShowText).HandleExceptions();
78+
79+
return;
80+
}
81+
82+
public bool IsEnabled(TextViewContext context) => true;
83+
84+
public bool IsVisible(TextViewContext context)
85+
{
86+
return context.SelectedTreeNodes?.Length == 1
87+
&& context.SelectedTreeNodes?.FirstOrDefault() is AssemblyTreeNode tn
88+
&& tn.LoadedAssembly.IsLoadedAsValidAssembly;
89+
}
90+
91+
static string SelectDestinationFolder()
92+
{
93+
OpenFolderDialog dialog = new();
94+
dialog.Multiselect = false;
95+
dialog.Title = "Select target folder";
96+
97+
if (dialog.ShowDialog() != true)
98+
{
99+
return null;
100+
}
101+
102+
string selectedPath = Path.GetDirectoryName(dialog.FolderName);
103+
bool directoryNotEmpty;
104+
try
105+
{
106+
directoryNotEmpty = Directory.EnumerateFileSystemEntries(selectedPath).Any();
107+
}
108+
catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is System.Security.SecurityException)
109+
{
110+
MessageBox.Show(
111+
"The directory cannot be accessed. Please ensure it exists and you have sufficient rights to access it.",
112+
"Target directory not accessible",
113+
MessageBoxButton.OK, MessageBoxImage.Error);
114+
return null;
115+
}
116+
117+
return dialog.FolderName;
118+
}
119+
}
120+
}

ILSpy/Docking/DockWorkspace.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,11 @@ public Task<T> RunWithCancellation<T>(Func<CancellationToken, Task<T>> taskCreat
226226
return ActiveTabPage.ShowTextViewAsync(textView => textView.RunWithCancellation(taskCreation));
227227
}
228228

229+
public Task<T> RunWithCancellation<T>(Func<CancellationToken, Task<T>> taskCreation, string progressTitle)
230+
{
231+
return ActiveTabPage.ShowTextViewAsync(textView => textView.RunWithCancellation(taskCreation, progressTitle));
232+
}
233+
229234
internal void ShowNodes(AvalonEditTextOutput output, TreeNodes.ILSpyTreeNode[] nodes, IHighlightingDefinition highlighting)
230235
{
231236
ActiveTabPage.ShowTextView(textView => textView.ShowNodes(output, nodes, highlighting));

ILSpy/Properties/Resources.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ILSpy/Properties/Resources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ Are you sure you want to continue?</value>
210210
<data name="Create" xml:space="preserve">
211211
<value>Create</value>
212212
</data>
213+
<data name="CreatingDiagram" xml:space="preserve">
214+
<value>Creating diagram...</value>
215+
</data>
213216
<data name="CultureLabel" xml:space="preserve">
214217
<value>Culture</value>
215218
</data>
@@ -1036,6 +1039,9 @@ Do you want to continue?</value>
10361039
<data name="_CollapseTreeNodes" xml:space="preserve">
10371040
<value>_Collapse all tree nodes</value>
10381041
</data>
1042+
<data name="_CreateDiagram" xml:space="preserve">
1043+
<value>Create _Diagram...</value>
1044+
</data>
10391045
<data name="_File" xml:space="preserve">
10401046
<value>_File</value>
10411047
</data>

ILSpy/TextView/DecompilerTextView.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -595,14 +595,14 @@ public void Report(DecompilationProgress value)
595595
/// the task.
596596
/// If another task is started before the previous task finishes running, the previous task is cancelled.
597597
/// </summary>
598-
public Task<T> RunWithCancellation<T>(Func<CancellationToken, Task<T>> taskCreation)
598+
public Task<T> RunWithCancellation<T>(Func<CancellationToken, Task<T>> taskCreation, string? progressTitle = null)
599599
{
600600
if (waitAdorner.Visibility != Visibility.Visible)
601601
{
602602
waitAdorner.Visibility = Visibility.Visible;
603603
// Work around a WPF bug by setting IsIndeterminate only while the progress bar is visible.
604604
// https://github.com/icsharpcode/ILSpy/issues/593
605-
progressTitle.Text = Properties.Resources.Decompiling;
605+
this.progressTitle.Text = progressTitle == null ? Properties.Resources.Decompiling : progressTitle;
606606
progressBar.IsIndeterminate = true;
607607
progressText.Text = null;
608608
progressText.Visibility = Visibility.Collapsed;

0 commit comments

Comments
 (0)