diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index 576286aac..3db61bb38 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -78,6 +78,14 @@ namespace ICSharpCode.ILSpy /// public bool IsDebug { get; set; } + /// + /// When true, the pipeline records its steps so a step view can display them. It travels with + /// the request rather than being read from the view: a step index only means anything against + /// a run recorded the same way, so the full run and the step-limited replay of one of its + /// indices have to agree on it, and the decompile that reads it happens on a background task. + /// + public bool RecordSteps { get; set; } + /// /// Optional sink for whole-project decompilation progress. Wired onto /// diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index ca88b3367..3e32ee93f 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -84,6 +84,15 @@ namespace ICSharpCode.ILSpy.Docking /// behaviour (e.g. ShowOptionsCommand). public IDocumentDock? Documents => factory.Documents; + /// + /// Whether decompiles started in this workspace record their transform steps. Set on the UI + /// thread by whoever displays them and copied into each run's , + /// so a background decompile never samples live view state - and every tab, including ones + /// opened later, records the same way. A step index only means anything against a run + /// recorded like the one the index was taken from. + /// + public bool RecordSteps { get; set; } + public IRelayCommand NavigateBackCommand { get; } public IRelayCommand NavigateForwardCommand { get; } public IRelayCommand NavigateToHistoryCommand { get; } diff --git a/ILSpy/Languages/CSharpLanguage.DebugSteps.cs b/ILSpy/Languages/CSharpLanguage.DebugSteps.cs index 5e729e4cb..b09f9bd61 100644 --- a/ILSpy/Languages/CSharpLanguage.DebugSteps.cs +++ b/ILSpy/Languages/CSharpLanguage.DebugSteps.cs @@ -77,15 +77,6 @@ namespace ICSharpCode.ILSpy.Languages /// static DebugStepsPaneModel? DebugStepsPane => AppComposition.TryGetExport(); - /// - /// Points the whole pipeline at the shared stepper, but only while the Debug Steps pane is - /// there to display what it records - see . - /// - static partial void ConfigureStepRecording(CSharpDecompiler decompiler) - { - decompiler.RecordSteps = DebugStepsPane?.IsRecording ?? false; - } - /// /// Drops the recorded steps of the last run. The stepper pins every ILAst its steps captured, so /// this is what actually releases that memory once nothing is displaying it. diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index fa4654ec8..a7f800a42 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -327,10 +327,10 @@ namespace ICSharpCode.ILSpy.Languages }; decompiler.Stepper.StepLimit = options.StepLimit; decompiler.Stepper.IsDebug = options.IsDebug; - // The Debug Steps pane walks the whole pipeline, so the IL transforms have to record into - // the same stepper as the C# AST transforms. Implemented only under DEBUG, where the pane - // exists; a Release decompiler records no steps at all. - ConfigureStepRecording(decompiler); + // The Debug Steps pane walks the whole pipeline, so the IL transforms record into the same + // stepper as the C# AST transforms. Which runs record is decided by whoever started the + // decompile, not by the pane's state at the moment this runs on a background task. + decompiler.RecordSteps = options.RecordSteps; if (options.EscapeInvalidIdentifiers) decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); return decompiler; @@ -776,10 +776,6 @@ namespace ICSharpCode.ILSpy.Languages } } - // Implemented only under DEBUG (CSharpLanguage.DebugSteps.cs): enables IL-transform step - // recording while the Debug Steps pane is open. A no-op partial in Release. - static partial void ConfigureStepRecording(CSharpDecompiler decompiler); - // Implemented only under DEBUG (CSharpLanguage.DebugSteps.cs): writes the ILAst the pipeline was // halted in, when a Debug Steps replay stopped it before there was any C# to show. A no-op // partial in Release, where nothing sets a step limit. diff --git a/ILSpy/TextView/DecompilerTabPageModel.cs b/ILSpy/TextView/DecompilerTabPageModel.cs index 565b9b0b4..269f5e5c2 100644 --- a/ILSpy/TextView/DecompilerTabPageModel.cs +++ b/ILSpy/TextView/DecompilerTabPageModel.cs @@ -395,6 +395,7 @@ namespace ICSharpCode.ILSpy.TextView int? pendingHighlightStep; bool pendingIsDebug; + /// /// Output-length safety limits (characters): a decompile that produces more than the active /// limit is stopped and replaced with a "too much code" message rather than hanging/OOMing the @@ -559,6 +560,10 @@ namespace ICSharpCode.ILSpy.TextView var stepLimit = pendingStepLimit; var highlightStep = pendingHighlightStep; var isDebug = pendingIsDebug; + // Unlike the per-run overrides above, this is not reset: it describes the tab, and + // every run has to record the same way or a step index picked from one tree would + // select a different step on replay. + var recordSteps = AppEnv.AppComposition.TryGetExport()?.RecordSteps ?? false; pendingStepLimit = int.MaxValue; pendingHighlightStep = null; pendingIsDebug = false; @@ -580,6 +585,7 @@ namespace ICSharpCode.ILSpy.TextView StepLimit = stepLimit, HighlightStep = highlightStep, IsDebug = isDebug, + RecordSteps = recordSteps, }; try { diff --git a/ILSpy/ViewModels/DebugStepsPaneModel.cs b/ILSpy/ViewModels/DebugStepsPaneModel.cs index 661abc6e8..ee0fdef70 100644 --- a/ILSpy/ViewModels/DebugStepsPaneModel.cs +++ b/ILSpy/ViewModels/DebugStepsPaneModel.cs @@ -415,6 +415,10 @@ namespace ICSharpCode.ILSpy.ViewModels if (IsRecording == enabled) return; IsRecording = enabled; + // The workspace carries the flag into every run it starts, including the ones the user + // triggers by selecting another node, so closing the pane has to clear it there too. + if (AppComposition.TryGetExport() is { } workspace) + workspace.RecordSteps = enabled; if (enabled) { // C# is the only language that records anything, so re-running any other one would @@ -434,7 +438,10 @@ namespace ICSharpCode.ILSpy.ViewModels lastSelectedStep = stepLimit; // Composition unavailable in design-time previews; the gesture is a no-op there. var dock = AppComposition.TryGetExport(); - dock?.ActiveDecompilerTab?.RestartDecompileWithStepLimit(stepLimit, isDebug, highlightStep); + if (dock == null) + return; + dock.RecordSteps = IsRecording; + dock.ActiveDecompilerTab?.RestartDecompileWithStepLimit(stepLimit, isDebug, highlightStep); } } }