Browse Source

Add Left/Right keyboard expand-collapse and parent/child nav

Standard tree keyboard navigation in the assembly list: Left collapses an expanded node or moves to the parent when already collapsed; Right expands a closed node or steps into its first child. Operates on the primary selection via the hierarchical model (FindNode/Expand/Collapse) and SelectNode; the parent step is gated on the parent being a visible row so it never selects the hidden root.
pull/3755/head
Siegfried Pammer 1 month ago
parent
commit
d7bce602d1
  1. 38
      ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs
  2. 28
      ILSpy/AssemblyTree/AssemblyListPane.axaml.cs

38
ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs

@ -856,6 +856,44 @@ public class AssemblyTreeTests
description: "a second Delete must unload another assembly; the selection didn't desync"); description: "a second Delete must unload another assembly; the selection didn't desync");
} }
[AvaloniaTest]
public async Task Left_And_Right_Keys_Collapse_Expand_And_Navigate_The_Tree()
{
// Standard tree keyboard nav: Right expands a collapsed node then steps into its first
// child; Left collapses an expanded node, or moves to the parent when already collapsed.
var (window, vm) = await TestHarness.BootAsync(3);
var model = vm.AssemblyTreeModel;
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
grid.Focus();
Dispatcher.UIThread.RunJobs();
var assembly = model.FindNode<AssemblyTreeNode>("System.Linq");
assembly.EnsureLazyChildren();
model.SelectNode(assembly);
await Waiters.WaitForAsync(() => ReferenceEquals(model.SelectedItem, assembly));
assembly.IsExpanded.Should().BeFalse("precondition: the assembly starts collapsed");
// Right expands.
window.KeyPress(Key.Right, RawInputModifiers.None, PhysicalKey.ArrowRight, null);
await Waiters.WaitForAsync(() => assembly.IsExpanded, description: "Right expands the collapsed node");
// Right again steps into the first child.
window.KeyPress(Key.Right, RawInputModifiers.None, PhysicalKey.ArrowRight, null);
await Waiters.WaitForAsync(
() => model.SelectedItem is { } s && !ReferenceEquals(s, assembly) && ReferenceEquals(s.Parent, assembly),
description: "Right on an expanded node selects its first child");
// Left on the (collapsed/leaf) child moves selection back to the parent.
window.KeyPress(Key.Left, RawInputModifiers.None, PhysicalKey.ArrowLeft, null);
await Waiters.WaitForAsync(() => ReferenceEquals(model.SelectedItem, assembly),
description: "Left on a collapsed child selects the parent");
// Left on the expanded parent collapses it.
window.KeyPress(Key.Left, RawInputModifiers.None, PhysicalKey.ArrowLeft, null);
await Waiters.WaitForAsync(() => !assembly.IsExpanded, description: "Left collapses the expanded node");
}
[AvaloniaTest] [AvaloniaTest]
public async Task Clear_Assembly_List_Command_Empties_The_Active_List() public async Task Clear_Assembly_List_Command_Empties_The_Active_List()
{ {

28
ILSpy/AssemblyTree/AssemblyListPane.axaml.cs

@ -310,6 +310,34 @@ namespace ILSpy.AssemblyTree
Dispatcher.UIThread.Post(() => ReselectAfterDelete(reselectIndex), DispatcherPriority.Background); Dispatcher.UIThread.Post(() => ReselectAfterDelete(reselectIndex), DispatcherPriority.Background);
return; return;
} }
if (e.Key is Key.Left or Key.Right && e.KeyModifiers == KeyModifiers.None
&& model.SelectedItem is { } current
&& TreeGrid.HierarchicalModel is IHierarchicalModel hmNav
&& hmNav.FindNode(current) is { } currentNode)
{
if (e.Key == Key.Left)
{
// Collapse if open; otherwise step out to the parent (unless it's the hidden root).
if (currentNode.IsExpanded)
hmNav.Collapse(currentNode);
else if (current.Parent is { } parent && hmNav.FindNode(parent) is not null)
model.SelectNode(parent);
else
return;
}
else
{
// Expand if closed and has children; otherwise step into the first child.
if (!currentNode.IsExpanded && !currentNode.IsLeaf)
hmNav.Expand(currentNode);
else if (currentNode.IsExpanded && currentNode.Children.Count > 0)
model.SelectNode(currentNode.Children[0].Item as SharpTreeNode);
else
return;
}
e.Handled = true;
return;
}
if (e.Key == Key.R && e.KeyModifiers == KeyModifiers.Control) if (e.Key == Key.R && e.KeyModifiers == KeyModifiers.Control)
{ {
var members = model.SelectedItems.OfType<IMemberTreeNode>() var members = model.SelectedItems.OfType<IMemberTreeNode>()

Loading…
Cancel
Save