From 7d576c3d30db91a2c4d30e4c80e4fd65fa490612 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 20 Sep 2026 16:03:49 +0200 Subject: [PATCH] Expand to the new half of a rename after rebuilding the tree When the deletion of the selected file is staged and git then reports it as the old half of a rename whose new half sits in a collapsed directory, the selection is meant to move to the rename. With gui.showRootItemInFileTree turned on, the directory stays collapsed and the selection lands on it. With the option turned off, the directory expands, but if another file in it sorts before the rename, that file gets selected. The loop that expands the directory runs before the tree is rebuilt and works on the file list instead of on tree nodes. It compares the rename's previous path, a user-facing path, against the selected node's internal path, so the two never match while the root item is shown. The path it hands to ExpandToPath is user-facing as well, so the directory would stay collapsed either way. And because the loop runs before the old node list is captured, expanding a directory above the selection shifts that list, and the search for the new selection starts from whatever node moved into the selected line. Rebuild the tree first, capture the old node list before anything expands, and then look for the rename among the leaves of the new tree. ExpandToPath gets the leaf's own internal path, so the two kinds of paths never need converting. Co-Authored-By: Claude Fable 5.1 --- pkg/gui/filetree/file_tree_view_model.go | 18 +++++++++--------- pkg/gui/filetree/file_tree_view_model_test.go | 6 ------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/pkg/gui/filetree/file_tree_view_model.go b/pkg/gui/filetree/file_tree_view_model.go index 514fbca01..5ca010b8a 100644 --- a/pkg/gui/filetree/file_tree_view_model.go +++ b/pkg/gui/filetree/file_tree_view_model.go @@ -98,22 +98,22 @@ func (self *FileTreeViewModel) GetSelectedPath() string { } func (self *FileTreeViewModel) SetTree() { - newFiles := self.GetAllFiles() selectedNode := self.GetSelected() - - // for when you stage the old file of a rename and the new file is in a collapsed dir - for _, file := range newFiles { - if selectedNode != nil && selectedNode.path != "" && file.PreviousPath == selectedNode.path { - self.ExpandToPath(file.Path) - } - } - prevNodes := self.GetAllItems() prevSelectedLineIdx := self.GetSelectedLineIdx() self.IFileTree.SetTree() if selectedNode != nil { + // If the selected file has become the old half of a rename, e.g. because + // its deletion was staged, make sure the rename is visible so that the + // selection can move to it. + for _, node := range self.GetRoot().GetLeaves() { + if node.File.PreviousPath == selectedNode.GetPath() { + self.ExpandToPath(node.GetInternalPath()) + } + } + newNodes := self.GetAllItems() newIdx := self.findNewSelectedIdx(prevNodes[prevSelectedLineIdx:], newNodes) if newIdx != -1 && newIdx != prevSelectedLineIdx { diff --git a/pkg/gui/filetree/file_tree_view_model_test.go b/pkg/gui/filetree/file_tree_view_model_test.go index d05cf7e93..42844b482 100644 --- a/pkg/gui/filetree/file_tree_view_model_test.go +++ b/pkg/gui/filetree/file_tree_view_model_test.go @@ -88,18 +88,12 @@ func TestSetTreeFollowsRenameIntoCollapsedDir(t *testing.T) { { name: "with root item", showRootItem: true, - /* EXPECTED: expectedPath: "a/new.go", - ACTUAL: */ - expectedPath: "a", }, { name: "without root item", showRootItem: false, - /* EXPECTED: expectedPath: "a/new.go", - ACTUAL: */ - expectedPath: "a/b.go", }, }