diff --git a/ILSpy/Commands/PdbGenerator.cs b/ILSpy/Commands/PdbGenerator.cs index 3bdec887b..2a75b5967 100644 --- a/ILSpy/Commands/PdbGenerator.cs +++ b/ILSpy/Commands/PdbGenerator.cs @@ -99,8 +99,15 @@ namespace ILSpy.Commands if (string.IsNullOrEmpty(folder)) 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. - await dockWorkspace.RunInNewTabAsync(Resources.GeneratingPortablePDB, token => Task.Run(() => { + await dockWorkspace.RunInNewTabAsync(title, token => Task.Run(() => { var output = new AvaloniaEditTextOutput { Title = "Generate Portable PDB" }; var totalWatch = Stopwatch.StartNew(); foreach (var (assembly, file) in supported) @@ -129,6 +136,7 @@ namespace ILSpy.Commands { output.Write(string.Format(Resources.GenerationFailedForAssembly, assembly.FileName, ex.Message)); output.WriteLine(); + output.WriteExceptionDetails(ex); } } totalWatch.Stop(); diff --git a/ILSpy/SmartTextOutputExtensions.cs b/ILSpy/SmartTextOutputExtensions.cs index 8bce49d7e..50ef8f96e 100644 --- a/ILSpy/SmartTextOutputExtensions.cs +++ b/ILSpy/SmartTextOutputExtensions.cs @@ -25,6 +25,7 @@ using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; +using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Properties; using ILSpy.TextView; @@ -34,6 +35,34 @@ namespace ILSpy { public static class SmartTextOutputExtensions { + /// + /// 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. + /// + 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(); + } + /// /// Appends the standard "Open Explorer" result button that opens in /// the OS file manager, followed by a blank line. Shared tail of the export / save / PDB outputs. diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index 666d9fb2b..d617cfb93 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -468,9 +468,7 @@ namespace ILSpy.TreeNodes { language.WriteCommentLine(output, message); output.WriteLine(); - output.MarkFoldStart("Exception details", true); - output.Write(ex.ToString()); - output.MarkFoldEnd(); + output.WriteExceptionDetails(ex); } try