diff --git a/ILSpy.Tests/Docking/DocumentTabContextMenuTests.cs b/ILSpy.Tests/Docking/DocumentTabContextMenuTests.cs index 567fe6707..944ce4e5e 100644 --- a/ILSpy.Tests/Docking/DocumentTabContextMenuTests.cs +++ b/ILSpy.Tests/Docking/DocumentTabContextMenuTests.cs @@ -79,6 +79,32 @@ public class DocumentTabContextMenuTests .Should().BeSameAs(a.CloseAllCommand); } + [AvaloniaTest] + public async Task Close_Entry_Is_Disabled_When_The_Tab_Cannot_Be_Closed() + { + // The last remaining document is made un-closeable (DockWorkspace.UpdateLastDocumentCanClose + // flips CanClose to false so the user can't empty the document area). The context-menu Close + // entry must honour that the same way Dock's own close button does, so it binds IsEnabled to + // the tab's CanClose. + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); + var (a, _, _) = OpenThreeTabs(vm.DockWorkspace); + + var menu = global::ILSpy.Themes.PreviewTabContextMenuBehavior.BuildDocumentContextMenu(a); + var close = menu.ItemsSource!.OfType() + .Single(m => Equals(m.Header, ICSharpCode.ILSpy.Properties.Resources.Close)); + + a.CanClose = true; + Dispatcher.UIThread.RunJobs(); + close.IsEnabled.Should().BeTrue("a closeable tab's Close entry is enabled"); + + a.CanClose = false; + Dispatcher.UIThread.RunJobs(); + close.IsEnabled.Should().BeFalse("an un-closeable tab's Close entry is disabled"); + } + [AvaloniaTest] public async Task Close_All_But_This_Leaves_Only_The_Target_Tab() { diff --git a/ILSpy/Themes/PreviewTabContextMenuBehavior.cs b/ILSpy/Themes/PreviewTabContextMenuBehavior.cs index 9f2b087f2..a88455bd0 100644 --- a/ILSpy/Themes/PreviewTabContextMenuBehavior.cs +++ b/ILSpy/Themes/PreviewTabContextMenuBehavior.cs @@ -20,6 +20,8 @@ using System.ComponentModel; using Avalonia; using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Input; using Dock.Avalonia.Controls; @@ -90,6 +92,12 @@ namespace ILSpy.Themes internal static ContextMenu BuildDocumentContextMenu(ContentTabPage tab) { var close = new MenuItem { Header = Resources.Close, Command = tab.CloseCommand }; + // The last remaining document is made un-closeable (DockWorkspace keeps CanClose false so + // the document area can't be emptied). Mirror that on the Close entry, the way Dock's own + // close button does -- CanClose is observable (DockableBase : ReactiveBase), so this stays + // in sync as tabs open and close. + close.Bind(InputElement.IsEnabledProperty, + new Binding(nameof(ContentTabPage.CanClose)) { Source = tab }); var closeAllButThis = new MenuItem { Header = Resources.CloseAllButThisTab, Command = tab.CloseAllButThisCommand }; var closeAll = new MenuItem { Header = Resources.CloseAllTabs, Command = tab.CloseAllCommand };