Browse Source

DisplaySettings live-wires the decompiler editor

DecompilerTextView subscribes to SettingsService.DisplaySettings.PropertyChanged
and mirrors the new value into the AvaloniaEdit TextEditor immediately — no
re-decompile, no Apply. Properties wired:

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
1bd3670bed
  1. 34
      ILSpy.Tests/Options/OptionsTabTests.cs
  2. 3
      ILSpy/Languages/LanguageService.cs
  3. 66
      ILSpy/TextView/DecompilerTextView.axaml.cs
  4. 9
      ILSpy/TreeNodes/ILSpyTreeNode.cs

34
ILSpy.Tests/Options/OptionsTabTests.cs

@ -30,6 +30,8 @@ using ILSpy.AppEnv; @@ -30,6 +30,8 @@ using ILSpy.AppEnv;
using ILSpy.Commands;
using ILSpy.Docking;
using ILSpy.Options;
using ILSpy.TextView;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
@ -199,4 +201,36 @@ public class OptionsTabTests @@ -199,4 +201,36 @@ public class OptionsTabTests
// Reset must restore the panel's settings to `new DecompilerSettings()` defaults.
refreshedItem.IsEnabled.Should().Be(defaultValue);
}
[AvaloniaTest]
public async Task Display_Setting_Change_Live_Updates_Decompiler_Editor()
{
// DisplaySettings → DecompilerTextView wire-up: mutating SelectedFontSize on the live
// settings instance flows immediately into Editor.FontSize without any Apply or
// re-decompile step. Covers the broader live-bind contract for font / size / line
// numbers / word wrap / highlight current line / indentation.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var settings = AppComposition.Current.GetExport<SettingsService>();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
// Materialise a DecompilerTextView by selecting a node.
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
vm.AssemblyTreeModel.SelectNode(typeNode);
var view = await window.WaitForComponent<DecompilerTextView>();
var editor = await view.WaitForComponent<AvaloniaEdit.TextEditor>();
var originalSize = settings.DisplaySettings.SelectedFontSize;
var newSize = originalSize + 5;
// Act — change the live setting; subscriber should write through to Editor.FontSize.
settings.DisplaySettings.SelectedFontSize = newSize;
editor.FontSize.Should().Be(newSize);
// Clean-up: restore so later tests see the original.
settings.DisplaySettings.SelectedFontSize = originalSize;
}
}

3
ILSpy/Languages/LanguageService.cs

@ -72,6 +72,9 @@ namespace ILSpy.Languages @@ -72,6 +72,9 @@ namespace ILSpy.Languages
public Language GetLanguage(string? name)
=> Languages.FirstOrDefault(l => l.Name == name) ?? Languages.First();
// The MVVM-toolkit source generator declares the partial method with nullable
// reference types (the backing field is a nullable T). Match the signature exactly
// to avoid a CS8611 nullability mismatch.
partial void OnCurrentLanguageChanged(Language? oldValue, Language newValue)
{
if (newValue == null)

66
ILSpy/TextView/DecompilerTextView.axaml.cs

@ -44,6 +44,7 @@ using ICSharpCode.ILSpyX; @@ -44,6 +44,7 @@ using ICSharpCode.ILSpyX;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.Options;
namespace ILSpy.TextView
{
@ -152,6 +153,71 @@ namespace ILSpy.TextView @@ -152,6 +153,71 @@ namespace ILSpy.TextView
richPopup.Closed += OnRichPopupClosed;
// Popup.Open uses PlacementTarget to find its TopLevel, so the popup itself doesn't
// need to be in any visual tree of ours.
WireUpDisplaySettings();
}
/// <summary>
/// Mirrors the live <see cref="DisplaySettings"/> into the editor on construction and on
/// every <c>PropertyChanged</c> after that. Lets the Options Display panel produce
/// immediate visual feedback without a re-decompile.
/// </summary>
void WireUpDisplaySettings()
{
var settings = TryGetDisplaySettings();
if (settings == null)
return;
ApplyAllDisplaySettings(settings);
settings.PropertyChanged += (_, e) => ApplyDisplaySetting(settings, e.PropertyName);
}
static DisplaySettings? TryGetDisplaySettings()
{
try
{ return AppComposition.Current.GetExport<SettingsService>().DisplaySettings; }
catch { return null; }
}
void ApplyAllDisplaySettings(DisplaySettings s)
{
ApplyDisplaySetting(s, nameof(DisplaySettings.SelectedFont));
ApplyDisplaySetting(s, nameof(DisplaySettings.SelectedFontSize));
ApplyDisplaySetting(s, nameof(DisplaySettings.ShowLineNumbers));
ApplyDisplaySetting(s, nameof(DisplaySettings.EnableWordWrap));
ApplyDisplaySetting(s, nameof(DisplaySettings.HighlightCurrentLine));
ApplyDisplaySetting(s, nameof(DisplaySettings.IndentationSize));
ApplyDisplaySetting(s, nameof(DisplaySettings.IndentationUseTabs));
}
void ApplyDisplaySetting(DisplaySettings s, string? propertyName)
{
switch (propertyName)
{
case nameof(DisplaySettings.SelectedFont):
if (!string.IsNullOrEmpty(s.SelectedFont))
Editor.FontFamily = new FontFamily(s.SelectedFont);
break;
case nameof(DisplaySettings.SelectedFontSize):
if (s.SelectedFontSize > 0)
Editor.FontSize = s.SelectedFontSize;
break;
case nameof(DisplaySettings.ShowLineNumbers):
Editor.ShowLineNumbers = s.ShowLineNumbers;
break;
case nameof(DisplaySettings.EnableWordWrap):
Editor.WordWrap = s.EnableWordWrap;
break;
case nameof(DisplaySettings.HighlightCurrentLine):
Editor.Options.HighlightCurrentLine = s.HighlightCurrentLine;
break;
case nameof(DisplaySettings.IndentationSize):
if (s.IndentationSize > 0)
Editor.Options.IndentationSize = s.IndentationSize;
break;
case nameof(DisplaySettings.IndentationUseTabs):
Editor.Options.ConvertTabsToSpaces = !s.IndentationUseTabs;
break;
}
}
static IReadOnlyList<IContextMenuEntryExport> TryGetContextMenuEntries()

9
ILSpy/TreeNodes/ILSpyTreeNode.cs

@ -76,6 +76,15 @@ namespace ILSpy.TreeNodes @@ -76,6 +76,15 @@ namespace ILSpy.TreeNodes
public Language Language => LanguageService.CurrentLanguage;
/// <summary>
/// Long-form label used in navigation surfaces — back/forward history dropdowns, future
/// breadcrumb rendering, etc. Defaults to <see cref="SharpTreeNode.Text"/>; override on
/// nodes whose <see cref="SharpTreeNode.Text"/> is too generic to be useful out of
/// context (e.g. "Base Types" → "Base Types (System.Exception)") so the user can tell
/// dropdown entries apart at a glance.
/// </summary>
public override object? NavigationText => Text;
/// <summary>
/// True for nodes that represent assemblies the user did not open explicitly — the
/// decompiler resolved them as part of another assembly's references and added them to

Loading…
Cancel
Save