Browse Source

Refresh decompiler output in place on display-setting changes

Toggling a display setting that affects decompiler output (folding, member or
using expansion, debug info, IL detail, indentation) routed through
ForceRefreshActiveTab, whose ShowSelectedNode re-projects the tree selection
and so activates the preview tab -- yanking the user off whatever tab they were
on (e.g. the live Options page they were editing).

Add RefreshDecompilerOutputInPlace, which re-decompiles the cached decompiler
content directly without re-projecting or activating anything, and route the
Redecompile display-setting reaction to it. F5 reload and dependency-load keep
using ForceRefreshActiveTab, where re-projecting the selection is intended.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3755/head
Siegfried Pammer 4 weeks ago
parent
commit
34038c9af4
  1. 33
      ILSpy.Tests/Options/OptionsTabTests.cs
  2. 13
      ILSpy/AssemblyTree/AssemblyTreeModel.cs
  3. 10
      ILSpy/Docking/DockWorkspace.cs

33
ILSpy.Tests/Options/OptionsTabTests.cs

@ -20,6 +20,7 @@ using System.Linq; @@ -20,6 +20,7 @@ using System.Linq;
using System.Threading.Tasks;
using Avalonia.Headless.NUnit;
using Avalonia.Threading;
using AwesomeAssertions;
@ -299,4 +300,36 @@ public class OptionsTabTests @@ -299,4 +300,36 @@ public class OptionsTabTests
// Clean-up: restore so later tests see the original.
settings.DisplaySettings.SelectedFontSize = originalSize;
}
[AvaloniaTest]
public async Task Toggling_A_Display_Setting_Does_Not_Switch_Away_From_The_Focused_Options_Tab()
{
// Scenario: the user is on the Options tab and toggles a setting that forces a re-decompile
// (e.g. DecodeCustomAttributeBlobs). The decompiler output must refresh in place; it must NOT
// pull focus back to MainTab and yank the user off the Options page they are editing.
var (_, vm) = await TestHarness.BootAsync(3);
// Show some decompiled content first, so there is a decompiler tab to refresh.
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
vm.AssemblyTreeModel.SelectNode(typeNode);
await vm.DockWorkspace.WaitForDecompiledTextAsync();
// Open Options and confirm it is the active document.
AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._Options)).Execute(null);
var documents = vm.DockWorkspace.Documents!;
var optionsTab = documents.VisibleDockables!.OfType<ContentTabPage>()
.Single(t => t.Content is OptionsPageModel);
documents.ActiveDockable.Should().BeSameAs(optionsTab, "baseline: Options is the active document");
// Toggle a re-decompile display setting.
var display = AppComposition.Current.GetExport<SettingsService>().DisplaySettings;
display.DecodeCustomAttributeBlobs = !display.DecodeCustomAttributeBlobs;
for (int i = 0; i < 12; i++)
Dispatcher.UIThread.RunJobs();
documents.ActiveDockable.Should().BeSameAs(optionsTab,
"an output display setting must re-decompile in place, not switch the user off the focused tab");
}
}

13
ILSpy/AssemblyTree/AssemblyTreeModel.cs

@ -288,8 +288,9 @@ namespace ILSpy.AssemblyTree @@ -288,8 +288,9 @@ namespace ILSpy.AssemblyTree
case Options.DisplaySettingReaction.Redecompile:
// Baked into the decompiler/disassembler output (folding, member/using
// expansion, debug info, IL detail, indentation), so it only shows after a
// re-decompile of the active tab.
RefreshDecompiledView();
// re-decompile. Refresh in place: changing an option must not switch the
// user's current tab.
RefreshDecompiledViewInPlace();
break;
// EditorLive / None: the text view applies editor settings to AvaloniaEdit itself,
// and the rest have no model-side reaction.
@ -905,6 +906,14 @@ namespace ILSpy.AssemblyTree @@ -905,6 +906,14 @@ namespace ILSpy.AssemblyTree
public void RefreshDecompiledView()
=> AppEnv.AppComposition.TryGetExport<Docking.DockWorkspace>()?.ForceRefreshActiveTab();
/// <summary>
/// Re-decompiles the decompiler tab's content in place for an output-affecting display setting,
/// without activating or navigating to it. Changing an option must not switch the user's current
/// tab, so this avoids the selection re-projection that <see cref="RefreshDecompiledView"/> does.
/// </summary>
public void RefreshDecompiledViewInPlace()
=> AppEnv.AppComposition.TryGetExport<Docking.DockWorkspace>()?.RefreshDecompilerOutputInPlace();
/// <summary>
/// Resolves every assembly reference of each supplied assembly node through that
/// assembly's own resolver -- which auto-loads the targets into the live list -- then

10
ILSpy/Docking/DockWorkspace.cs

@ -719,6 +719,16 @@ namespace ILSpy.Docking @@ -719,6 +719,16 @@ namespace ILSpy.Docking
ActiveDecompilerTab?.Redecompile();
}
/// <summary>
/// Re-decompile the decompiler tab's content in place so an output-affecting display setting
/// takes effect, WITHOUT activating or navigating to it. Unlike <see cref="ForceRefreshActiveTab"/>
/// (which re-projects the tree selection via <c>ShowSelectedNode</c> and so activates the preview
/// tab), changing an option must not steal the user's current tab. Refreshes the cached
/// decompiler content directly, so it is up to date even when another tab is showing.
/// </summary>
public void RefreshDecompilerOutputInPlace()
=> decompilerContent?.Redecompile();
void ShowSelectedNode()
{
using var _ = ILSpy.AppEnv.AppLog.Phase("DockWorkspace.ShowSelectedNode");

Loading…
Cancel
Save