Skip to content

Commit caae42a

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

File tree

5 files changed

+155
-2
lines changed

5 files changed

+155
-2
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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() {
53+
EnableHyperlinks = true
54+
};
55+
Stopwatch stopwatch = Stopwatch.StartNew();
56+
try
57+
{
58+
var command = new GenerateHtmlDiagrammer {
59+
Assembly = assembly.FileName,
60+
OutputFolder = selectedPath
61+
};
62+
63+
command.Run();
64+
}
65+
catch (OperationCanceledException)
66+
{
67+
output.WriteLine();
68+
output.WriteLine(Resources.GenerationWasCancelled);
69+
throw;
70+
}
71+
stopwatch.Stop();
72+
output.WriteLine(Resources.GenerationCompleteInSeconds, stopwatch.Elapsed.TotalSeconds.ToString("F1"));
73+
output.WriteLine();
74+
output.WriteLine("Learn more: " + "https://github.com/icsharpcode/ILSpy/wiki/Diagramming#tips-for-using-the-html-diagrammer");
75+
output.WriteLine();
76+
77+
var diagramHtml = Path.Combine(selectedPath, "index.html");
78+
output.AddButton(null, Resources.OpenExplorer, delegate { Process.Start("explorer", "/select,\"" + diagramHtml + "\""); });
79+
output.WriteLine();
80+
return output;
81+
}, ct), Properties.Resources.CreatingDiagram).Then(dockWorkspace.ShowText).HandleExceptions();
82+
83+
return;
84+
}
85+
86+
public bool IsEnabled(TextViewContext context) => true;
87+
88+
public bool IsVisible(TextViewContext context)
89+
{
90+
return context.SelectedTreeNodes?.Length == 1
91+
&& context.SelectedTreeNodes?.FirstOrDefault() is AssemblyTreeNode tn
92+
&& tn.LoadedAssembly.IsLoadedAsValidAssembly;
93+
}
94+
95+
static string SelectDestinationFolder()
96+
{
97+
OpenFolderDialog dialog = new();
98+
dialog.Multiselect = false;
99+
dialog.Title = "Select target folder";
100+
101+
if (dialog.ShowDialog() != true)
102+
{
103+
return null;
104+
}
105+
106+
string selectedPath = Path.GetDirectoryName(dialog.FolderName);
107+
bool directoryNotEmpty;
108+
try
109+
{
110+
directoryNotEmpty = Directory.EnumerateFileSystemEntries(selectedPath).Any();
111+
}
112+
catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is System.Security.SecurityException)
113+
{
114+
MessageBox.Show(
115+
"The directory cannot be accessed. Please ensure it exists and you have sufficient rights to access it.",
116+
"Target directory not accessible",
117+
MessageBoxButton.OK, MessageBoxImage.Error);
118+
return null;
119+
}
120+
121+
return dialog.FolderName;
122+
}
123+
}
124+
}

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)