Browse Source

Fold the full exception trace into failure reports

Generate-PDB and assembly-node decompile failures reported only the exception
Message, which is too thin to act on: the runtime-async PDB-generation crash
surfaces as a bare "Object reference not set to an instance of an object" with
no indication of where it came from. Append the whole exception (type, message,
stack trace, inner exceptions) inside a default-collapsed "Exception details"
fold so the report stays scannable while the trace is one click away.

The fold span is counted in WriteLine() calls rather than embedded newline
characters, so the trace is emitted one line per WriteLine() -- a single
Write() of the multi-line string would collapse to one counted line and be
dropped as a single-line fold.

Also fixes the PDB tab title showing a literal "{0}": the GeneratingPortablePDB
resource is a format string whose placeholder was never filled.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3755/head
Siegfried Pammer 4 weeks ago
parent
commit
f1b40a68b8
  1. 10
      ILSpy/Commands/PdbGenerator.cs
  2. 29
      ILSpy/SmartTextOutputExtensions.cs
  3. 4
      ILSpy/TreeNodes/AssemblyTreeNode.cs

10
ILSpy/Commands/PdbGenerator.cs

@ -99,8 +99,15 @@ namespace ILSpy.Commands
if (string.IsNullOrEmpty(folder)) if (string.IsNullOrEmpty(folder))
return; return;
// GeneratingPortablePDB is a "...for {0}..." format string; fill the placeholder with the
// single assembly's name, or a count when several were selected, so the tab title isn't a
// literal "{0}".
string title = supported.Count == 1
? string.Format(Resources.GeneratingPortablePDB, supported.Keys.First().ShortName)
: string.Format(Resources.GeneratingPortablePDB, supported.Count + " assemblies");
// Run in a dedicated frozen tab so browsing the tree while PDBs generate can't cancel it. // Run in a dedicated frozen tab so browsing the tree while PDBs generate can't cancel it.
await dockWorkspace.RunInNewTabAsync(Resources.GeneratingPortablePDB, token => Task.Run(() => { await dockWorkspace.RunInNewTabAsync(title, token => Task.Run(() => {
var output = new AvaloniaEditTextOutput { Title = "Generate Portable PDB" }; var output = new AvaloniaEditTextOutput { Title = "Generate Portable PDB" };
var totalWatch = Stopwatch.StartNew(); var totalWatch = Stopwatch.StartNew();
foreach (var (assembly, file) in supported) foreach (var (assembly, file) in supported)
@ -129,6 +136,7 @@ namespace ILSpy.Commands
{ {
output.Write(string.Format(Resources.GenerationFailedForAssembly, assembly.FileName, ex.Message)); output.Write(string.Format(Resources.GenerationFailedForAssembly, assembly.FileName, ex.Message));
output.WriteLine(); output.WriteLine();
output.WriteExceptionDetails(ex);
} }
} }
totalWatch.Stop(); totalWatch.Stop();

29
ILSpy/SmartTextOutputExtensions.cs

@ -25,6 +25,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ILSpy.TextView; using ILSpy.TextView;
@ -34,6 +35,34 @@ namespace ILSpy
{ {
public static class SmartTextOutputExtensions public static class SmartTextOutputExtensions
{ {
/// <summary>
/// Writes the full exception (type, message, stack trace, inner exceptions) inside a
/// default-collapsed "Exception details" fold. Callers write their own header line first; this
/// keeps a failure report scannable while leaving the trace one click away when a failure is not
/// self-explanatory.
/// </summary>
public static void WriteExceptionDetails(this ITextOutput output, Exception ex)
{
ArgumentNullException.ThrowIfNull(output);
ArgumentNullException.ThrowIfNull(ex);
// Blank line between the caller's header and the fold so the collapsed "Exception details"
// marker is visually separated from the message above it.
output.WriteLine();
output.MarkFoldStart("Exception details", true);
// The fold span is counted in WriteLine() calls, not in the '\n' characters embedded in a
// single Write(). Emit the trace one line per WriteLine() so the fold genuinely spans
// multiple lines; otherwise it collapses to a single line and is dropped as noise.
var lines = ex.ToString().Split('\n');
for (int i = 0; i < lines.Length; i++)
{
if (i > 0)
output.WriteLine();
output.Write(lines[i].TrimEnd('\r'));
}
output.MarkFoldEnd();
output.WriteLine();
}
/// <summary> /// <summary>
/// Appends the standard "Open Explorer" result button that opens <paramref name="folder"/> in /// Appends the standard "Open Explorer" result button that opens <paramref name="folder"/> in
/// the OS file manager, followed by a blank line. Shared tail of the export / save / PDB outputs. /// the OS file manager, followed by a blank line. Shared tail of the export / save / PDB outputs.

4
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -468,9 +468,7 @@ namespace ILSpy.TreeNodes
{ {
language.WriteCommentLine(output, message); language.WriteCommentLine(output, message);
output.WriteLine(); output.WriteLine();
output.MarkFoldStart("Exception details", true); output.WriteExceptionDetails(ex);
output.Write(ex.ToString());
output.MarkFoldEnd();
} }
try try

Loading…
Cancel
Save