mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-06-20 01:19:23 +02:00
Add GetVisualDepth method to FileTree/CommitFileTree
This commit is contained in:
committed by
Stefan Haller
parent
fe5df2334b
commit
61b72cef38
@@ -151,6 +151,10 @@ func (self *CommitFileTree) GetFile(path string) *models.CommitFile {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) GetVisualDepth(index int) int {
|
||||
return self.tree.GetVisualDepthAtIndex(index+1, self.collapsedPaths) // +1 to skip root
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) InTreeMode() bool {
|
||||
return self.showTree
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ type ITree[T any] interface {
|
||||
CollapsedPaths() *CollapsedPaths
|
||||
CollapseAll()
|
||||
ExpandAll()
|
||||
GetVisualDepth(index int) int
|
||||
}
|
||||
|
||||
type IFileTree interface {
|
||||
@@ -221,6 +222,10 @@ func (self *FileTree) CollapsedPaths() *CollapsedPaths {
|
||||
return self.collapsedPaths
|
||||
}
|
||||
|
||||
func (self *FileTree) GetVisualDepth(index int) int {
|
||||
return self.tree.GetVisualDepthAtIndex(index+1, self.collapsedPaths) // +1 to skip root
|
||||
}
|
||||
|
||||
func (self *FileTree) GetStatusFilter() FileTreeDisplayFilter {
|
||||
return self.filter
|
||||
}
|
||||
|
||||
@@ -202,29 +202,43 @@ func (self *Node[T]) GetNodeAtIndex(index int, collapsedPaths *CollapsedPaths) *
|
||||
return nil
|
||||
}
|
||||
|
||||
node, _ := self.getNodeAtIndexAux(index, collapsedPaths)
|
||||
node, _, _ := self.getNodeAtIndexAux(index, collapsedPaths, -1)
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *Node[T]) getNodeAtIndexAux(index int, collapsedPaths *CollapsedPaths) (*Node[T], int) {
|
||||
// GetVisualDepthAtIndex returns the visual depth (indentation level) of the
|
||||
// node at the given flat index. Visual depth differs from tree depth because
|
||||
// compressed nodes (e.g. "a/b/") count as a single visual level.
|
||||
// Returns -1 if the index is out of range.
|
||||
func (self *Node[T]) GetVisualDepthAtIndex(index int, collapsedPaths *CollapsedPaths) int {
|
||||
if self == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
_, _, depth := self.getNodeAtIndexAux(index, collapsedPaths, -1)
|
||||
|
||||
return depth
|
||||
}
|
||||
|
||||
func (self *Node[T]) getNodeAtIndexAux(index int, collapsedPaths *CollapsedPaths, visualDepth int) (*Node[T], int, int) {
|
||||
offset := 1
|
||||
|
||||
if index == 0 {
|
||||
return self, offset
|
||||
return self, offset, visualDepth
|
||||
}
|
||||
|
||||
if !collapsedPaths.IsCollapsed(self.path) {
|
||||
for _, child := range self.Children {
|
||||
foundNode, offsetChange := child.getNodeAtIndexAux(index-offset, collapsedPaths)
|
||||
foundNode, offsetChange, depth := child.getNodeAtIndexAux(index-offset, collapsedPaths, visualDepth+1)
|
||||
offset += offsetChange
|
||||
if foundNode != nil {
|
||||
return foundNode, offset
|
||||
return foundNode, offset, depth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, offset
|
||||
return nil, offset, -1
|
||||
}
|
||||
|
||||
func (self *Node[T]) GetIndexForPath(path string, collapsedPaths *CollapsedPaths) (int, bool) {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package filetree
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetVisualDepthAtIndex(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
files []*models.File
|
||||
showRootItem bool
|
||||
collapsedPaths []string
|
||||
expectedDepths []int // one per visible node, skipping root
|
||||
}{
|
||||
{
|
||||
name: "flat files with root item",
|
||||
files: []*models.File{
|
||||
{Path: "a"},
|
||||
{Path: "b"},
|
||||
},
|
||||
showRootItem: true,
|
||||
// Displayed as:
|
||||
// index 0: ▼ / (depth 0, the "." root dir)
|
||||
// index 1: a (depth 1)
|
||||
// index 2: b (depth 1)
|
||||
expectedDepths: []int{0, 1, 1},
|
||||
},
|
||||
{
|
||||
name: "flat files without root item",
|
||||
files: []*models.File{
|
||||
{Path: "a"},
|
||||
{Path: "b"},
|
||||
},
|
||||
showRootItem: false,
|
||||
// Displayed as:
|
||||
// index 0: a (depth 0)
|
||||
// index 1: b (depth 0)
|
||||
expectedDepths: []int{0, 0},
|
||||
},
|
||||
{
|
||||
name: "nested directories with root item",
|
||||
files: []*models.File{
|
||||
{Path: "dir/a"},
|
||||
{Path: "dir/b"},
|
||||
{Path: "c"},
|
||||
},
|
||||
showRootItem: true,
|
||||
// Displayed as:
|
||||
// index 0: ▼ / (depth 0)
|
||||
// index 1: ▼ dir (depth 1)
|
||||
// index 2: a (depth 2)
|
||||
// index 3: b (depth 2)
|
||||
// index 4: c (depth 1)
|
||||
expectedDepths: []int{0, 1, 2, 2, 1},
|
||||
},
|
||||
{
|
||||
name: "compressed paths with root item",
|
||||
files: []*models.File{
|
||||
{Path: "dir1/dir3/a"},
|
||||
{Path: "dir2/dir4/b"},
|
||||
},
|
||||
showRootItem: true,
|
||||
// Tree compresses dir1/dir3 and dir2/dir4 into single nodes.
|
||||
// Displayed as:
|
||||
// index 0: ▼ / (depth 0)
|
||||
// index 1: ▼ dir1/dir3 (depth 1, compressed)
|
||||
// index 2: a (depth 2)
|
||||
// index 3: ▼ dir2/dir4 (depth 1, compressed)
|
||||
// index 4: b (depth 2)
|
||||
expectedDepths: []int{0, 1, 2, 1, 2},
|
||||
},
|
||||
{
|
||||
name: "compressed paths without root item",
|
||||
files: []*models.File{
|
||||
{Path: "dir1/dir3/a"},
|
||||
{Path: "dir2/dir4/b"},
|
||||
},
|
||||
showRootItem: false,
|
||||
// Displayed as:
|
||||
// index 0: ▼ dir1/dir3 (depth 0, compressed)
|
||||
// index 1: a (depth 1)
|
||||
// index 2: ▼ dir2/dir4 (depth 0, compressed)
|
||||
// index 3: b (depth 1)
|
||||
expectedDepths: []int{0, 1, 0, 1},
|
||||
},
|
||||
{
|
||||
name: "collapsed directory hides children",
|
||||
files: []*models.File{
|
||||
{Path: "dir/a"},
|
||||
{Path: "dir/b"},
|
||||
{Path: "c"},
|
||||
},
|
||||
showRootItem: true,
|
||||
collapsedPaths: []string{"./dir"},
|
||||
// Displayed as:
|
||||
// index 0: ▼ / (depth 0)
|
||||
// index 1: ▶ dir (depth 1, collapsed)
|
||||
// index 2: c (depth 1)
|
||||
expectedDepths: []int{0, 1, 1},
|
||||
},
|
||||
{
|
||||
name: "out of range returns -1",
|
||||
files: []*models.File{
|
||||
{Path: "a"},
|
||||
},
|
||||
showRootItem: false,
|
||||
expectedDepths: []int{0},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
tree := BuildTreeFromFiles(s.files, s.showRootItem)
|
||||
collapsedPaths := NewCollapsedPaths()
|
||||
for _, p := range s.collapsedPaths {
|
||||
collapsedPaths.Collapse(p)
|
||||
}
|
||||
|
||||
for i, expectedDepth := range s.expectedDepths {
|
||||
// +1 to skip the invisible root node, matching what FileTree.GetVisualDepth does
|
||||
actualDepth := tree.GetVisualDepthAtIndex(i+1, collapsedPaths)
|
||||
assert.Equal(t, expectedDepth, actualDepth,
|
||||
"index %d: expected depth %d, got %d", i, expectedDepth, actualDepth)
|
||||
}
|
||||
|
||||
// Verify out-of-range returns -1
|
||||
outOfRange := tree.GetVisualDepthAtIndex(len(s.expectedDepths)+1, collapsedPaths)
|
||||
assert.Equal(t, -1, outOfRange, "out of range index should return -1")
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user