mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-03-21 21:47:32 +02:00
refactor to use generics for file nodes
use less generic names
This commit is contained in:
parent
2ca2acaca5
commit
682be18507
@ -165,7 +165,7 @@ func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode)
|
|||||||
|
|
||||||
// if there is any file that hasn't been fully added we'll fully add everything,
|
// if there is any file that hasn't been fully added we'll fully add everything,
|
||||||
// otherwise we'll remove everything
|
// otherwise we'll remove everything
|
||||||
adding := node.AnyFile(func(file *models.CommitFile) bool {
|
adding := node.SomeFile(func(file *models.CommitFile) bool {
|
||||||
return self.git.Patch.PatchManager.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
|
return self.git.Patch.PatchManager.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -203,8 +203,7 @@ func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitFilesController) toggleAllForPatch(_ *filetree.CommitFileNode) error {
|
func (self *CommitFilesController) toggleAllForPatch(_ *filetree.CommitFileNode) error {
|
||||||
// not a fan of type assertions but this will be fixed very soon thanks to generics
|
root := self.context().CommitFileTreeViewModel.GetRoot()
|
||||||
root := self.context().CommitFileTreeViewModel.Tree().(*filetree.CommitFileNode)
|
|
||||||
return self.toggleForPatch(root)
|
return self.toggleForPatch(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
|||||||
self.mutexes.RefreshingFilesMutex.Lock()
|
self.mutexes.RefreshingFilesMutex.Lock()
|
||||||
defer self.mutexes.RefreshingFilesMutex.Unlock()
|
defer self.mutexes.RefreshingFilesMutex.Unlock()
|
||||||
|
|
||||||
if node.IsLeaf() {
|
if node.IsFile() {
|
||||||
file := node.File
|
file := node.File
|
||||||
|
|
||||||
if file.HasInlineMergeConflicts {
|
if file.HasInlineMergeConflicts {
|
||||||
|
@ -7,10 +7,10 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BuildTreeFromFiles(files []*models.File) *FileNode {
|
func BuildTreeFromFiles(files []*models.File) *Node[models.File] {
|
||||||
root := &FileNode{}
|
root := &Node[models.File]{}
|
||||||
|
|
||||||
var curr *FileNode
|
var curr *Node[models.File]
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
splitPath := split(file.Name)
|
splitPath := split(file.Name)
|
||||||
curr = root
|
curr = root
|
||||||
@ -30,7 +30,7 @@ func BuildTreeFromFiles(files []*models.File) *FileNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newChild := &FileNode{
|
newChild := &Node[models.File]{
|
||||||
Path: path,
|
Path: path,
|
||||||
File: setFile,
|
File: setFile,
|
||||||
}
|
}
|
||||||
@ -46,17 +46,17 @@ func BuildTreeFromFiles(files []*models.File) *FileNode {
|
|||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildFlatTreeFromCommitFiles(files []*models.CommitFile) *CommitFileNode {
|
func BuildFlatTreeFromCommitFiles(files []*models.CommitFile) *Node[models.CommitFile] {
|
||||||
rootAux := BuildTreeFromCommitFiles(files)
|
rootAux := BuildTreeFromCommitFiles(files)
|
||||||
sortedFiles := rootAux.GetLeaves()
|
sortedFiles := rootAux.GetLeaves()
|
||||||
|
|
||||||
return &CommitFileNode{Children: sortedFiles}
|
return &Node[models.CommitFile]{Children: sortedFiles}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildTreeFromCommitFiles(files []*models.CommitFile) *CommitFileNode {
|
func BuildTreeFromCommitFiles(files []*models.CommitFile) *Node[models.CommitFile] {
|
||||||
root := &CommitFileNode{}
|
root := &Node[models.CommitFile]{}
|
||||||
|
|
||||||
var curr *CommitFileNode
|
var curr *Node[models.CommitFile]
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
splitPath := split(file.Name)
|
splitPath := split(file.Name)
|
||||||
curr = root
|
curr = root
|
||||||
@ -77,7 +77,7 @@ func BuildTreeFromCommitFiles(files []*models.CommitFile) *CommitFileNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newChild := &CommitFileNode{
|
newChild := &Node[models.CommitFile]{
|
||||||
Path: path,
|
Path: path,
|
||||||
File: setFile,
|
File: setFile,
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ func BuildTreeFromCommitFiles(files []*models.CommitFile) *CommitFileNode {
|
|||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildFlatTreeFromFiles(files []*models.File) *FileNode {
|
func BuildFlatTreeFromFiles(files []*models.File) *Node[models.File] {
|
||||||
rootAux := BuildTreeFromFiles(files)
|
rootAux := BuildTreeFromFiles(files)
|
||||||
sortedFiles := rootAux.GetLeaves()
|
sortedFiles := rootAux.GetLeaves()
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ func BuildFlatTreeFromFiles(files []*models.File) *FileNode {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
return &FileNode{Children: sortedFiles}
|
return &Node[models.File]{Children: sortedFiles}
|
||||||
}
|
}
|
||||||
|
|
||||||
func split(str string) []string {
|
func split(str string) []string {
|
||||||
|
@ -11,14 +11,14 @@ func TestBuildTreeFromFiles(t *testing.T) {
|
|||||||
scenarios := []struct {
|
scenarios := []struct {
|
||||||
name string
|
name string
|
||||||
files []*models.File
|
files []*models.File
|
||||||
expected *FileNode
|
expected *Node[models.File]
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no files",
|
name: "no files",
|
||||||
files: []*models.File{},
|
files: []*models.File{},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{},
|
Children: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -31,12 +31,12 @@ func TestBuildTreeFromFiles(t *testing.T) {
|
|||||||
Name: "dir1/b",
|
Name: "dir1/b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
Path: "dir1",
|
Path: "dir1",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "dir1/a"},
|
File: &models.File{Name: "dir1/a"},
|
||||||
Path: "dir1/a",
|
Path: "dir1/a",
|
||||||
@ -60,12 +60,12 @@ func TestBuildTreeFromFiles(t *testing.T) {
|
|||||||
Name: "dir2/dir4/b",
|
Name: "dir2/dir4/b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
Path: "dir1/dir3",
|
Path: "dir1/dir3",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "dir1/dir3/a"},
|
File: &models.File{Name: "dir1/dir3/a"},
|
||||||
Path: "dir1/dir3/a",
|
Path: "dir1/dir3/a",
|
||||||
@ -75,7 +75,7 @@ func TestBuildTreeFromFiles(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Path: "dir2/dir4",
|
Path: "dir2/dir4",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "dir2/dir4/b"},
|
File: &models.File{Name: "dir2/dir4/b"},
|
||||||
Path: "dir2/dir4/b",
|
Path: "dir2/dir4/b",
|
||||||
@ -96,9 +96,9 @@ func TestBuildTreeFromFiles(t *testing.T) {
|
|||||||
Name: "a",
|
Name: "a",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "a"},
|
File: &models.File{Name: "a"},
|
||||||
Path: "a",
|
Path: "a",
|
||||||
@ -124,11 +124,11 @@ func TestBuildTreeFromFiles(t *testing.T) {
|
|||||||
Name: "a",
|
Name: "a",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
// it is a little strange that we're not bubbling up our merge conflict
|
// it is a little strange that we're not bubbling up our merge conflict
|
||||||
// here but we are technically still in in tree mode and that's the rule
|
// here but we are technically still in in tree mode and that's the rule
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "a"},
|
File: &models.File{Name: "a"},
|
||||||
Path: "a",
|
Path: "a",
|
||||||
@ -159,14 +159,14 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
|
|||||||
scenarios := []struct {
|
scenarios := []struct {
|
||||||
name string
|
name string
|
||||||
files []*models.File
|
files []*models.File
|
||||||
expected *FileNode
|
expected *Node[models.File]
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no files",
|
name: "no files",
|
||||||
files: []*models.File{},
|
files: []*models.File{},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{},
|
Children: []*Node[models.File]{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -179,9 +179,9 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
|
|||||||
Name: "dir1/b",
|
Name: "dir1/b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "dir1/a"},
|
File: &models.File{Name: "dir1/a"},
|
||||||
Path: "dir1/a",
|
Path: "dir1/a",
|
||||||
@ -205,9 +205,9 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
|
|||||||
Name: "dir2/b",
|
Name: "dir2/b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "dir1/a"},
|
File: &models.File{Name: "dir1/a"},
|
||||||
Path: "dir1/a",
|
Path: "dir1/a",
|
||||||
@ -231,9 +231,9 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
|
|||||||
Name: "a",
|
Name: "a",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "a"},
|
File: &models.File{Name: "a"},
|
||||||
Path: "a",
|
Path: "a",
|
||||||
@ -273,9 +273,9 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
|
|||||||
Tracked: true,
|
Tracked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "c1", HasMergeConflicts: true},
|
File: &models.File{Name: "c1", HasMergeConflicts: true},
|
||||||
Path: "c1",
|
Path: "c1",
|
||||||
@ -318,14 +318,14 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
|
|||||||
scenarios := []struct {
|
scenarios := []struct {
|
||||||
name string
|
name string
|
||||||
files []*models.CommitFile
|
files []*models.CommitFile
|
||||||
expected *CommitFileNode
|
expected *Node[models.CommitFile]
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no files",
|
name: "no files",
|
||||||
files: []*models.CommitFile{},
|
files: []*models.CommitFile{},
|
||||||
expected: &CommitFileNode{
|
expected: &Node[models.CommitFile]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*CommitFileNode{},
|
Children: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -338,12 +338,12 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
|
|||||||
Name: "dir1/b",
|
Name: "dir1/b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &CommitFileNode{
|
expected: &Node[models.CommitFile]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*CommitFileNode{
|
Children: []*Node[models.CommitFile]{
|
||||||
{
|
{
|
||||||
Path: "dir1",
|
Path: "dir1",
|
||||||
Children: []*CommitFileNode{
|
Children: []*Node[models.CommitFile]{
|
||||||
{
|
{
|
||||||
File: &models.CommitFile{Name: "dir1/a"},
|
File: &models.CommitFile{Name: "dir1/a"},
|
||||||
Path: "dir1/a",
|
Path: "dir1/a",
|
||||||
@ -367,12 +367,12 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
|
|||||||
Name: "dir2/dir4/b",
|
Name: "dir2/dir4/b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &CommitFileNode{
|
expected: &Node[models.CommitFile]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*CommitFileNode{
|
Children: []*Node[models.CommitFile]{
|
||||||
{
|
{
|
||||||
Path: "dir1/dir3",
|
Path: "dir1/dir3",
|
||||||
Children: []*CommitFileNode{
|
Children: []*Node[models.CommitFile]{
|
||||||
{
|
{
|
||||||
File: &models.CommitFile{Name: "dir1/dir3/a"},
|
File: &models.CommitFile{Name: "dir1/dir3/a"},
|
||||||
Path: "dir1/dir3/a",
|
Path: "dir1/dir3/a",
|
||||||
@ -382,7 +382,7 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Path: "dir2/dir4",
|
Path: "dir2/dir4",
|
||||||
Children: []*CommitFileNode{
|
Children: []*Node[models.CommitFile]{
|
||||||
{
|
{
|
||||||
File: &models.CommitFile{Name: "dir2/dir4/b"},
|
File: &models.CommitFile{Name: "dir2/dir4/b"},
|
||||||
Path: "dir2/dir4/b",
|
Path: "dir2/dir4/b",
|
||||||
@ -403,9 +403,9 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
|
|||||||
Name: "a",
|
Name: "a",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &CommitFileNode{
|
expected: &Node[models.CommitFile]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*CommitFileNode{
|
Children: []*Node[models.CommitFile]{
|
||||||
{
|
{
|
||||||
File: &models.CommitFile{Name: "a"},
|
File: &models.CommitFile{Name: "a"},
|
||||||
Path: "a",
|
Path: "a",
|
||||||
@ -432,14 +432,14 @@ func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
|
|||||||
scenarios := []struct {
|
scenarios := []struct {
|
||||||
name string
|
name string
|
||||||
files []*models.CommitFile
|
files []*models.CommitFile
|
||||||
expected *CommitFileNode
|
expected *Node[models.CommitFile]
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no files",
|
name: "no files",
|
||||||
files: []*models.CommitFile{},
|
files: []*models.CommitFile{},
|
||||||
expected: &CommitFileNode{
|
expected: &Node[models.CommitFile]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*CommitFileNode{},
|
Children: []*Node[models.CommitFile]{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -452,9 +452,9 @@ func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
|
|||||||
Name: "dir1/b",
|
Name: "dir1/b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &CommitFileNode{
|
expected: &Node[models.CommitFile]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*CommitFileNode{
|
Children: []*Node[models.CommitFile]{
|
||||||
{
|
{
|
||||||
File: &models.CommitFile{Name: "dir1/a"},
|
File: &models.CommitFile{Name: "dir1/a"},
|
||||||
Path: "dir1/a",
|
Path: "dir1/a",
|
||||||
@ -478,9 +478,9 @@ func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
|
|||||||
Name: "dir2/b",
|
Name: "dir2/b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &CommitFileNode{
|
expected: &Node[models.CommitFile]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*CommitFileNode{
|
Children: []*Node[models.CommitFile]{
|
||||||
{
|
{
|
||||||
File: &models.CommitFile{Name: "dir1/a"},
|
File: &models.CommitFile{Name: "dir1/a"},
|
||||||
Path: "dir1/a",
|
Path: "dir1/a",
|
||||||
@ -504,9 +504,9 @@ func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
|
|||||||
Name: "a",
|
Name: "a",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &CommitFileNode{
|
expected: &Node[models.CommitFile]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*CommitFileNode{
|
Children: []*Node[models.CommitFile]{
|
||||||
{
|
{
|
||||||
File: &models.CommitFile{Name: "a"},
|
File: &models.CommitFile{Name: "a"},
|
||||||
Path: "a",
|
Path: "a",
|
||||||
|
@ -1,166 +1,21 @@
|
|||||||
package filetree
|
package filetree
|
||||||
|
|
||||||
import (
|
import "github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/generics/slices"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
// CommitFileNode wraps a node and provides some commit-file-specific methods for it.
|
||||||
type CommitFileNode struct {
|
type CommitFileNode struct {
|
||||||
Children []*CommitFileNode
|
*Node[models.CommitFile]
|
||||||
File *models.CommitFile
|
|
||||||
Path string // e.g. '/path/to/mydir'
|
|
||||||
CompressionLevel int // equal to the number of forward slashes you'll see in the path when it's rendered in tree mode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func NewCommitFileNode(node *Node[models.CommitFile]) *CommitFileNode {
|
||||||
_ INode = &CommitFileNode{}
|
|
||||||
_ types.ListItem = &CommitFileNode{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *CommitFileNode) ID() string {
|
|
||||||
return s.GetPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) Description() string {
|
|
||||||
return s.GetPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods satisfying INode interface
|
|
||||||
|
|
||||||
func (s *CommitFileNode) IsNil() bool {
|
|
||||||
return s == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) IsLeaf() bool {
|
|
||||||
return s.File != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) GetPath() string {
|
|
||||||
return s.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) GetChildren() []INode {
|
|
||||||
return slices.Map(s.Children, func(child *CommitFileNode) INode {
|
|
||||||
return child
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) SetChildren(children []INode) {
|
|
||||||
castChildren := slices.Map(children, func(child INode) *CommitFileNode {
|
|
||||||
return child.(*CommitFileNode)
|
|
||||||
})
|
|
||||||
|
|
||||||
s.Children = castChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) GetCompressionLevel() int {
|
|
||||||
return s.CompressionLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) SetCompressionLevel(level int) {
|
|
||||||
s.CompressionLevel = level
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods utilising generic functions for INodes
|
|
||||||
|
|
||||||
func (s *CommitFileNode) Sort() {
|
|
||||||
sortNode(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) ForEachFile(cb func(*models.CommitFile) error) error {
|
|
||||||
return forEachLeaf(s, func(n INode) error {
|
|
||||||
castNode := n.(*CommitFileNode)
|
|
||||||
return cb(castNode.File)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) Any(test func(node *CommitFileNode) bool) bool {
|
|
||||||
return any(s, func(n INode) bool {
|
|
||||||
castNode := n.(*CommitFileNode)
|
|
||||||
return test(castNode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) Every(test func(node *CommitFileNode) bool) bool {
|
|
||||||
return every(s, func(n INode) bool {
|
|
||||||
castNode := n.(*CommitFileNode)
|
|
||||||
return test(castNode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) EveryFile(test func(file *models.CommitFile) bool) bool {
|
|
||||||
return every(s, func(n INode) bool {
|
|
||||||
castNode := n.(*CommitFileNode)
|
|
||||||
|
|
||||||
return castNode.File == nil || test(castNode.File)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *CommitFileNode) Flatten(collapsedPaths *CollapsedPaths) []*CommitFileNode {
|
|
||||||
results := flatten(n, collapsedPaths)
|
|
||||||
|
|
||||||
return slices.Map(results, func(result INode) *CommitFileNode {
|
|
||||||
return result.(*CommitFileNode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node *CommitFileNode) GetNodeAtIndex(index int, collapsedPaths *CollapsedPaths) *CommitFileNode {
|
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := getNodeAtIndex(node, index, collapsedPaths)
|
return &CommitFileNode{Node: node}
|
||||||
if result == nil {
|
|
||||||
// not sure how this can be nil: we probably are missing a mutex somewhere
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.(*CommitFileNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *CommitFileNode) GetIndexForPath(path string, collapsedPaths *CollapsedPaths) (int, bool) {
|
// returns the underlying node, without any commit-file-specific methods attached
|
||||||
return getIndexForPath(node, path, collapsedPaths)
|
func (self *CommitFileNode) Raw() *Node[models.CommitFile] {
|
||||||
}
|
return self.Node
|
||||||
|
|
||||||
func (node *CommitFileNode) Size(collapsedPaths *CollapsedPaths) int {
|
|
||||||
if node == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return size(node, collapsedPaths)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) Compress() {
|
|
||||||
// with these functions I try to only have type conversion code on the actual struct,
|
|
||||||
// but comparing interface values to nil is fraught with danger so I'm duplicating
|
|
||||||
// that code here.
|
|
||||||
if s == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
compressAux(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) GetLeaves() []*CommitFileNode {
|
|
||||||
leaves := getLeaves(s)
|
|
||||||
|
|
||||||
return slices.Map(leaves, func(leaf INode) *CommitFileNode {
|
|
||||||
return leaf.(*CommitFileNode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// extra methods
|
|
||||||
|
|
||||||
func (s *CommitFileNode) AnyFile(test func(file *models.CommitFile) bool) bool {
|
|
||||||
return s.Any(func(node *CommitFileNode) bool {
|
|
||||||
return node.IsLeaf() && test(node.File)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommitFileNode) NameAtDepth(depth int) string {
|
|
||||||
splitName := split(s.Path)
|
|
||||||
name := join(splitName[depth:])
|
|
||||||
|
|
||||||
return name
|
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
package filetree
|
package filetree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/jesseduffield/generics/slices"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ICommitFileTree interface {
|
type ICommitFileTree interface {
|
||||||
ITree
|
ITree[models.CommitFile]
|
||||||
|
|
||||||
Get(index int) *CommitFileNode
|
Get(index int) *CommitFileNode
|
||||||
GetFile(path string) *models.CommitFile
|
GetFile(path string) *models.CommitFile
|
||||||
GetAllItems() []*CommitFileNode
|
GetAllItems() []*CommitFileNode
|
||||||
GetAllFiles() []*models.CommitFile
|
GetAllFiles() []*models.CommitFile
|
||||||
|
GetRoot() *CommitFileNode
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommitFileTree struct {
|
type CommitFileTree struct {
|
||||||
getFiles func() []*models.CommitFile
|
getFiles func() []*models.CommitFile
|
||||||
tree *CommitFileNode
|
tree *Node[models.CommitFile]
|
||||||
showTree bool
|
showTree bool
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
collapsedPaths *CollapsedPaths
|
collapsedPaths *CollapsedPaths
|
||||||
@ -44,7 +46,7 @@ func (self *CommitFileTree) ToggleShowTree() {
|
|||||||
|
|
||||||
func (self *CommitFileTree) Get(index int) *CommitFileNode {
|
func (self *CommitFileTree) Get(index int) *CommitFileNode {
|
||||||
// need to traverse the three depth first until we get to the index.
|
// need to traverse the three depth first until we get to the index.
|
||||||
return self.tree.GetNodeAtIndex(index+1, self.collapsedPaths) // ignoring root
|
return NewCommitFileNode(self.tree.GetNodeAtIndex(index+1, self.collapsedPaths)) // ignoring root
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitFileTree) GetIndexForPath(path string) (int, bool) {
|
func (self *CommitFileTree) GetIndexForPath(path string) (int, bool) {
|
||||||
@ -57,7 +59,10 @@ func (self *CommitFileTree) GetAllItems() []*CommitFileNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.tree.Flatten(self.collapsedPaths)[1:] // ignoring root
|
// ignoring root
|
||||||
|
return slices.Map(self.tree.Flatten(self.collapsedPaths)[1:], func(node *Node[models.CommitFile]) *CommitFileNode {
|
||||||
|
return NewCommitFileNode(node)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitFileTree) Len() int {
|
func (self *CommitFileTree) Len() int {
|
||||||
@ -84,8 +89,8 @@ func (self *CommitFileTree) ToggleCollapsed(path string) {
|
|||||||
self.collapsedPaths.ToggleCollapsed(path)
|
self.collapsedPaths.ToggleCollapsed(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitFileTree) Tree() INode {
|
func (self *CommitFileTree) GetRoot() *CommitFileNode {
|
||||||
return self.tree
|
return NewCommitFileNode(self.tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitFileTree) CollapsedPaths() *CollapsedPaths {
|
func (self *CommitFileTree) CollapsedPaths() *CollapsedPaths {
|
||||||
|
@ -1,199 +1,47 @@
|
|||||||
package filetree
|
package filetree
|
||||||
|
|
||||||
import (
|
import "github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/generics/slices"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
// FileNode wraps a node and provides some file-specific methods for it.
|
||||||
type FileNode struct {
|
type FileNode struct {
|
||||||
Children []*FileNode
|
*Node[models.File]
|
||||||
File *models.File
|
|
||||||
Path string // e.g. '/path/to/mydir'
|
|
||||||
CompressionLevel int // equal to the number of forward slashes you'll see in the path when it's rendered in tree mode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var _ models.IFile = &FileNode{}
|
||||||
_ INode = &FileNode{}
|
|
||||||
_ types.ListItem = &FileNode{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *FileNode) ID() string {
|
func NewFileNode(node *Node[models.File]) *FileNode {
|
||||||
return s.GetPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) Description() string {
|
|
||||||
return s.GetPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods satisfying INode interface
|
|
||||||
|
|
||||||
// interfaces values whose concrete value is nil are not themselves nil
|
|
||||||
// hence the existence of this method
|
|
||||||
func (s *FileNode) IsNil() bool {
|
|
||||||
return s == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) IsLeaf() bool {
|
|
||||||
return s.File != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) GetPath() string {
|
|
||||||
return s.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) GetPreviousPath() string {
|
|
||||||
if s.File != nil {
|
|
||||||
return s.File.GetPreviousPath()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) GetChildren() []INode {
|
|
||||||
return slices.Map(s.Children, func(child *FileNode) INode {
|
|
||||||
return child
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) SetChildren(children []INode) {
|
|
||||||
castChildren := slices.Map(children, func(child INode) *FileNode {
|
|
||||||
return child.(*FileNode)
|
|
||||||
})
|
|
||||||
|
|
||||||
s.Children = castChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) GetCompressionLevel() int {
|
|
||||||
return s.CompressionLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) SetCompressionLevel(level int) {
|
|
||||||
s.CompressionLevel = level
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods utilising generic functions for INodes
|
|
||||||
|
|
||||||
func (s *FileNode) Sort() {
|
|
||||||
sortNode(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) ForEachFile(cb func(*models.File) error) error {
|
|
||||||
return forEachLeaf(s, func(n INode) error {
|
|
||||||
castNode := n.(*FileNode)
|
|
||||||
return cb(castNode.File)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) Any(test func(node *FileNode) bool) bool {
|
|
||||||
return any(s, func(n INode) bool {
|
|
||||||
castNode := n.(*FileNode)
|
|
||||||
return test(castNode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *FileNode) Flatten(collapsedPaths *CollapsedPaths) []*FileNode {
|
|
||||||
results := flatten(n, collapsedPaths)
|
|
||||||
return slices.Map(results, func(result INode) *FileNode {
|
|
||||||
return result.(*FileNode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node *FileNode) GetNodeAtIndex(index int, collapsedPaths *CollapsedPaths) *FileNode {
|
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := getNodeAtIndex(node, index, collapsedPaths)
|
return &FileNode{Node: node}
|
||||||
if result == nil {
|
}
|
||||||
// not sure how this can be nil: we probably are missing a mutex somewhere
|
|
||||||
return nil
|
// returns the underlying node, without any file-specific methods attached
|
||||||
|
func (self *FileNode) Raw() *Node[models.File] {
|
||||||
|
return self.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FileNode) GetHasUnstagedChanges() bool {
|
||||||
|
return self.SomeFile(func(file *models.File) bool { return file.HasUnstagedChanges })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FileNode) GetHasStagedChanges() bool {
|
||||||
|
return self.SomeFile(func(file *models.File) bool { return file.HasStagedChanges })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FileNode) GetHasInlineMergeConflicts() bool {
|
||||||
|
return self.SomeFile(func(file *models.File) bool { return file.HasInlineMergeConflicts })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FileNode) GetIsTracked() bool {
|
||||||
|
return self.SomeFile(func(file *models.File) bool { return file.Tracked })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FileNode) GetPreviousPath() string {
|
||||||
|
if self.File == nil {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.(*FileNode)
|
return self.File.PreviousName
|
||||||
}
|
|
||||||
|
|
||||||
func (node *FileNode) GetIndexForPath(path string, collapsedPaths *CollapsedPaths) (int, bool) {
|
|
||||||
return getIndexForPath(node, path, collapsedPaths)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node *FileNode) Size(collapsedPaths *CollapsedPaths) int {
|
|
||||||
if node == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return size(node, collapsedPaths)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) Compress() {
|
|
||||||
// with these functions I try to only have type conversion code on the actual struct,
|
|
||||||
// but comparing interface values to nil is fraught with danger so I'm duplicating
|
|
||||||
// that code here.
|
|
||||||
if s == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
compressAux(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node *FileNode) GetFilePathsMatching(test func(*models.File) bool) []string {
|
|
||||||
return getPathsMatching(node, func(n INode) bool {
|
|
||||||
castNode := n.(*FileNode)
|
|
||||||
if castNode.File == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return test(castNode.File)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) GetLeaves() []*FileNode {
|
|
||||||
leaves := getLeaves(s)
|
|
||||||
|
|
||||||
return slices.Map(leaves, func(leaf INode) *FileNode {
|
|
||||||
return leaf.(*FileNode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// extra methods
|
|
||||||
|
|
||||||
func (s *FileNode) GetHasUnstagedChanges() bool {
|
|
||||||
return s.AnyFile(func(file *models.File) bool { return file.HasUnstagedChanges })
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) GetHasStagedChanges() bool {
|
|
||||||
return s.AnyFile(func(file *models.File) bool { return file.HasStagedChanges })
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) GetHasInlineMergeConflicts() bool {
|
|
||||||
return s.AnyFile(func(file *models.File) bool { return file.HasInlineMergeConflicts })
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) GetIsTracked() bool {
|
|
||||||
return s.AnyFile(func(file *models.File) bool { return file.Tracked })
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) AnyFile(test func(file *models.File) bool) bool {
|
|
||||||
return s.Any(func(node *FileNode) bool {
|
|
||||||
return node.IsLeaf() && test(node.File)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileNode) NameAtDepth(depth int) string {
|
|
||||||
splitName := split(s.Path)
|
|
||||||
name := join(splitName[depth:])
|
|
||||||
|
|
||||||
if s.File != nil && s.File.IsRename() {
|
|
||||||
splitPrevName := split(s.File.PreviousName)
|
|
||||||
|
|
||||||
prevName := s.File.PreviousName
|
|
||||||
// if the file has just been renamed inside the same directory, we can shave off
|
|
||||||
// the prefix for the previous path too. Otherwise we'll keep it unchanged
|
|
||||||
sameParentDir := len(splitName) == len(splitPrevName) && join(splitName[0:depth]) == join(splitPrevName[0:depth])
|
|
||||||
if sameParentDir {
|
|
||||||
prevName = join(splitPrevName[depth:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return prevName + " → " + name
|
|
||||||
}
|
|
||||||
|
|
||||||
return name
|
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
func TestCompress(t *testing.T) {
|
func TestCompress(t *testing.T) {
|
||||||
scenarios := []struct {
|
scenarios := []struct {
|
||||||
name string
|
name string
|
||||||
root *FileNode
|
root *Node[models.File]
|
||||||
expected *FileNode
|
expected *Node[models.File]
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "nil node",
|
name: "nil node",
|
||||||
@ -20,27 +20,27 @@ func TestCompress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "leaf node",
|
name: "leaf node",
|
||||||
root: &FileNode{
|
root: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"},
|
{File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"},
|
{File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "big example",
|
name: "big example",
|
||||||
root: &FileNode{
|
root: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
Path: "dir1",
|
Path: "dir1",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
|
File: &models.File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
|
||||||
Path: "dir1/file2",
|
Path: "dir1/file2",
|
||||||
@ -49,7 +49,7 @@ func TestCompress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Path: "dir2",
|
Path: "dir2",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "file3", ShortStatus: " M", HasStagedChanges: true},
|
File: &models.File{Name: "file3", ShortStatus: " M", HasStagedChanges: true},
|
||||||
Path: "dir2/file3",
|
Path: "dir2/file3",
|
||||||
@ -62,10 +62,10 @@ func TestCompress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Path: "dir3",
|
Path: "dir3",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
Path: "dir3/dir3-1",
|
Path: "dir3/dir3-1",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
|
File: &models.File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
|
||||||
Path: "dir3/dir3-1/file5",
|
Path: "dir3/dir3-1/file5",
|
||||||
@ -80,12 +80,12 @@ func TestCompress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &FileNode{
|
expected: &Node[models.File]{
|
||||||
Path: "",
|
Path: "",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
Path: "dir1",
|
Path: "dir1",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
|
File: &models.File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
|
||||||
Path: "dir1/file2",
|
Path: "dir1/file2",
|
||||||
@ -94,7 +94,7 @@ func TestCompress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Path: "dir2",
|
Path: "dir2",
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "file3", ShortStatus: " M", HasStagedChanges: true},
|
File: &models.File{Name: "file3", ShortStatus: " M", HasStagedChanges: true},
|
||||||
Path: "dir2/file3",
|
Path: "dir2/file3",
|
||||||
@ -108,7 +108,7 @@ func TestCompress(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Path: "dir3/dir3-1",
|
Path: "dir3/dir3-1",
|
||||||
CompressionLevel: 1,
|
CompressionLevel: 1,
|
||||||
Children: []*FileNode{
|
Children: []*Node[models.File]{
|
||||||
{
|
{
|
||||||
File: &models.File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
|
File: &models.File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
|
||||||
Path: "dir3/dir3-1/file5",
|
Path: "dir3/dir3-1/file5",
|
||||||
|
@ -18,7 +18,7 @@ const (
|
|||||||
DisplayConflicted
|
DisplayConflicted
|
||||||
)
|
)
|
||||||
|
|
||||||
type ITree interface {
|
type ITree[T any] interface {
|
||||||
InTreeMode() bool
|
InTreeMode() bool
|
||||||
ExpandToPath(path string)
|
ExpandToPath(path string)
|
||||||
ToggleShowTree()
|
ToggleShowTree()
|
||||||
@ -27,12 +27,11 @@ type ITree interface {
|
|||||||
SetTree()
|
SetTree()
|
||||||
IsCollapsed(path string) bool
|
IsCollapsed(path string) bool
|
||||||
ToggleCollapsed(path string)
|
ToggleCollapsed(path string)
|
||||||
Tree() INode
|
|
||||||
CollapsedPaths() *CollapsedPaths
|
CollapsedPaths() *CollapsedPaths
|
||||||
}
|
}
|
||||||
|
|
||||||
type IFileTree interface {
|
type IFileTree interface {
|
||||||
ITree
|
ITree[models.File]
|
||||||
|
|
||||||
FilterFiles(test func(*models.File) bool) []*models.File
|
FilterFiles(test func(*models.File) bool) []*models.File
|
||||||
SetFilter(filter FileTreeDisplayFilter)
|
SetFilter(filter FileTreeDisplayFilter)
|
||||||
@ -46,13 +45,15 @@ type IFileTree interface {
|
|||||||
|
|
||||||
type FileTree struct {
|
type FileTree struct {
|
||||||
getFiles func() []*models.File
|
getFiles func() []*models.File
|
||||||
tree *FileNode
|
tree *Node[models.File]
|
||||||
showTree bool
|
showTree bool
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
filter FileTreeDisplayFilter
|
filter FileTreeDisplayFilter
|
||||||
collapsedPaths *CollapsedPaths
|
collapsedPaths *CollapsedPaths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ IFileTree = &FileTree{}
|
||||||
|
|
||||||
func NewFileTree(getFiles func() []*models.File, log *logrus.Entry, showTree bool) *FileTree {
|
func NewFileTree(getFiles func() []*models.File, log *logrus.Entry, showTree bool) *FileTree {
|
||||||
return &FileTree{
|
return &FileTree{
|
||||||
getFiles: getFiles,
|
getFiles: getFiles,
|
||||||
@ -102,7 +103,7 @@ func (self *FileTree) ToggleShowTree() {
|
|||||||
|
|
||||||
func (self *FileTree) Get(index int) *FileNode {
|
func (self *FileTree) Get(index int) *FileNode {
|
||||||
// need to traverse the three depth first until we get to the index.
|
// need to traverse the three depth first until we get to the index.
|
||||||
return self.tree.GetNodeAtIndex(index+1, self.collapsedPaths) // ignoring root
|
return NewFileNode(self.tree.GetNodeAtIndex(index+1, self.collapsedPaths)) // ignoring root
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileTree) GetFile(path string) *models.File {
|
func (self *FileTree) GetFile(path string) *models.File {
|
||||||
@ -128,7 +129,10 @@ func (self *FileTree) GetAllItems() []*FileNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.tree.Flatten(self.collapsedPaths)[1:] // ignoring root
|
// ignoring root
|
||||||
|
return slices.Map(self.tree.Flatten(self.collapsedPaths)[1:], func(node *Node[models.File]) *FileNode {
|
||||||
|
return NewFileNode(node)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileTree) Len() int {
|
func (self *FileTree) Len() int {
|
||||||
@ -156,12 +160,12 @@ func (self *FileTree) ToggleCollapsed(path string) {
|
|||||||
self.collapsedPaths.ToggleCollapsed(path)
|
self.collapsedPaths.ToggleCollapsed(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileTree) Tree() INode {
|
func (self *FileTree) Tree() *FileNode {
|
||||||
return self.tree
|
return NewFileNode(self.tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileTree) GetRoot() *FileNode {
|
func (self *FileTree) GetRoot() *FileNode {
|
||||||
return self.tree
|
return NewFileNode(self.tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileTree) CollapsedPaths() *CollapsedPaths {
|
func (self *FileTree) CollapsedPaths() *CollapsedPaths {
|
||||||
|
@ -1,206 +0,0 @@
|
|||||||
package filetree
|
|
||||||
|
|
||||||
import "github.com/jesseduffield/generics/slices"
|
|
||||||
|
|
||||||
type INode interface {
|
|
||||||
IsNil() bool
|
|
||||||
IsLeaf() bool
|
|
||||||
GetPath() string
|
|
||||||
GetChildren() []INode
|
|
||||||
SetChildren([]INode)
|
|
||||||
GetCompressionLevel() int
|
|
||||||
SetCompressionLevel(int)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortNode(node INode) {
|
|
||||||
sortChildren(node)
|
|
||||||
|
|
||||||
for _, child := range node.GetChildren() {
|
|
||||||
sortNode(child)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortChildren(node INode) {
|
|
||||||
if node.IsLeaf() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sortedChildren := slices.Clone(node.GetChildren())
|
|
||||||
|
|
||||||
slices.SortFunc(sortedChildren, func(a, b INode) bool {
|
|
||||||
if !a.IsLeaf() && b.IsLeaf() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if a.IsLeaf() && !b.IsLeaf() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.GetPath() < b.GetPath()
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: think about making this in-place
|
|
||||||
node.SetChildren(sortedChildren)
|
|
||||||
}
|
|
||||||
|
|
||||||
func forEachLeaf(node INode, cb func(INode) error) error {
|
|
||||||
if node.IsLeaf() {
|
|
||||||
if err := cb(node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, child := range node.GetChildren() {
|
|
||||||
if err := forEachLeaf(child, cb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func any(node INode, test func(INode) bool) bool {
|
|
||||||
if test(node) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, child := range node.GetChildren() {
|
|
||||||
if any(child, test) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func every(node INode, test func(INode) bool) bool {
|
|
||||||
if !test(node) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, child := range node.GetChildren() {
|
|
||||||
if !every(child, test) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func flatten(node INode, collapsedPaths *CollapsedPaths) []INode {
|
|
||||||
result := []INode{}
|
|
||||||
result = append(result, node)
|
|
||||||
|
|
||||||
if !collapsedPaths.IsCollapsed(node.GetPath()) {
|
|
||||||
for _, child := range node.GetChildren() {
|
|
||||||
result = append(result, flatten(child, collapsedPaths)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNodeAtIndex(node INode, index int, collapsedPaths *CollapsedPaths) INode {
|
|
||||||
foundNode, _ := getNodeAtIndexAux(node, index, collapsedPaths)
|
|
||||||
|
|
||||||
return foundNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNodeAtIndexAux(node INode, index int, collapsedPaths *CollapsedPaths) (INode, int) {
|
|
||||||
offset := 1
|
|
||||||
|
|
||||||
if index == 0 {
|
|
||||||
return node, offset
|
|
||||||
}
|
|
||||||
|
|
||||||
if !collapsedPaths.IsCollapsed(node.GetPath()) {
|
|
||||||
for _, child := range node.GetChildren() {
|
|
||||||
foundNode, offsetChange := getNodeAtIndexAux(child, index-offset, collapsedPaths)
|
|
||||||
offset += offsetChange
|
|
||||||
if foundNode != nil {
|
|
||||||
return foundNode, offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, offset
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIndexForPath(node INode, path string, collapsedPaths *CollapsedPaths) (int, bool) {
|
|
||||||
offset := 0
|
|
||||||
|
|
||||||
if node.GetPath() == path {
|
|
||||||
return offset, true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !collapsedPaths.IsCollapsed(node.GetPath()) {
|
|
||||||
for _, child := range node.GetChildren() {
|
|
||||||
offsetChange, found := getIndexForPath(child, path, collapsedPaths)
|
|
||||||
offset += offsetChange + 1
|
|
||||||
if found {
|
|
||||||
return offset, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return offset, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func size(node INode, collapsedPaths *CollapsedPaths) int {
|
|
||||||
output := 1
|
|
||||||
|
|
||||||
if !collapsedPaths.IsCollapsed(node.GetPath()) {
|
|
||||||
for _, child := range node.GetChildren() {
|
|
||||||
output += size(child, collapsedPaths)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
func compressAux(node INode) INode {
|
|
||||||
if node.IsLeaf() {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
children := node.GetChildren()
|
|
||||||
for i := range children {
|
|
||||||
grandchildren := children[i].GetChildren()
|
|
||||||
for len(grandchildren) == 1 && !grandchildren[0].IsLeaf() {
|
|
||||||
grandchildren[0].SetCompressionLevel(children[i].GetCompressionLevel() + 1)
|
|
||||||
children[i] = grandchildren[0]
|
|
||||||
grandchildren = children[i].GetChildren()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range children {
|
|
||||||
children[i] = compressAux(children[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
node.SetChildren(children)
|
|
||||||
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPathsMatching(node INode, test func(INode) bool) []string {
|
|
||||||
paths := []string{}
|
|
||||||
|
|
||||||
if test(node) {
|
|
||||||
paths = append(paths, node.GetPath())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, child := range node.GetChildren() {
|
|
||||||
paths = append(paths, getPathsMatching(child, test)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLeaves(node INode) []INode {
|
|
||||||
if node.IsLeaf() {
|
|
||||||
return []INode{node}
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices.FlatMap(node.GetChildren(), func(child INode) []INode {
|
|
||||||
return getLeaves(child)
|
|
||||||
})
|
|
||||||
}
|
|
301
pkg/gui/filetree/node.go
Normal file
301
pkg/gui/filetree/node.go
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
package filetree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/generics/slices"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Represents a file or directory in a file tree.
|
||||||
|
type Node[T any] struct {
|
||||||
|
// File will be nil if the node is a directory.
|
||||||
|
File *T
|
||||||
|
|
||||||
|
// If the node is a directory, Children contains the contents of the directory,
|
||||||
|
// otherwise it's nil.
|
||||||
|
Children []*Node[T]
|
||||||
|
|
||||||
|
// path of the file/directory
|
||||||
|
Path string
|
||||||
|
|
||||||
|
// rather than render a tree as:
|
||||||
|
// a/
|
||||||
|
// b/
|
||||||
|
// file.blah
|
||||||
|
//
|
||||||
|
// we instead render it as:
|
||||||
|
// a/b/
|
||||||
|
// file.blah
|
||||||
|
// This saves vertical space. The CompressionLevel of a node is equal to the
|
||||||
|
// number of times a 'compression' like the above has happened, where two
|
||||||
|
// nodes are squished into one.
|
||||||
|
CompressionLevel int
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.ListItem = &Node[models.File]{}
|
||||||
|
|
||||||
|
func (self *Node[T]) IsFile() bool {
|
||||||
|
return self.File != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) GetPath() string {
|
||||||
|
return self.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) Sort() {
|
||||||
|
self.SortChildren()
|
||||||
|
|
||||||
|
for _, child := range self.Children {
|
||||||
|
child.Sort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) ForEachFile(cb func(*T) error) error {
|
||||||
|
if self.IsFile() {
|
||||||
|
if err := cb(self.File); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range self.Children {
|
||||||
|
if err := child.ForEachFile(cb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) SortChildren() {
|
||||||
|
if self.IsFile() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
children := slices.Clone(self.Children)
|
||||||
|
|
||||||
|
slices.SortFunc(children, func(a, b *Node[T]) bool {
|
||||||
|
if !a.IsFile() && b.IsFile() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if a.IsFile() && !b.IsFile() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.GetPath() < b.GetPath()
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: think about making this in-place
|
||||||
|
self.Children = children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) Some(test func(*Node[T]) bool) bool {
|
||||||
|
if test(self) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range self.Children {
|
||||||
|
if child.Some(test) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) SomeFile(test func(*T) bool) bool {
|
||||||
|
if self.IsFile() {
|
||||||
|
if test(self.File) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, child := range self.Children {
|
||||||
|
if child.SomeFile(test) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) Every(test func(*Node[T]) bool) bool {
|
||||||
|
if !test(self) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range self.Children {
|
||||||
|
if !child.Every(test) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) EveryFile(test func(*T) bool) bool {
|
||||||
|
if self.IsFile() {
|
||||||
|
if !test(self.File) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, child := range self.Children {
|
||||||
|
if !child.EveryFile(test) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) Flatten(collapsedPaths *CollapsedPaths) []*Node[T] {
|
||||||
|
result := []*Node[T]{self}
|
||||||
|
|
||||||
|
if len(self.Children) > 0 && !collapsedPaths.IsCollapsed(self.GetPath()) {
|
||||||
|
result = append(result, slices.FlatMap(self.Children, func(child *Node[T]) []*Node[T] {
|
||||||
|
return child.Flatten(collapsedPaths)
|
||||||
|
})...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) GetNodeAtIndex(index int, collapsedPaths *CollapsedPaths) *Node[T] {
|
||||||
|
if self == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
node, _ := self.getNodeAtIndexAux(index, collapsedPaths)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) getNodeAtIndexAux(index int, collapsedPaths *CollapsedPaths) (*Node[T], int) {
|
||||||
|
offset := 1
|
||||||
|
|
||||||
|
if index == 0 {
|
||||||
|
return self, offset
|
||||||
|
}
|
||||||
|
|
||||||
|
if !collapsedPaths.IsCollapsed(self.GetPath()) {
|
||||||
|
for _, child := range self.Children {
|
||||||
|
foundNode, offsetChange := child.getNodeAtIndexAux(index-offset, collapsedPaths)
|
||||||
|
offset += offsetChange
|
||||||
|
if foundNode != nil {
|
||||||
|
return foundNode, offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) GetIndexForPath(path string, collapsedPaths *CollapsedPaths) (int, bool) {
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
if self.GetPath() == path {
|
||||||
|
return offset, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !collapsedPaths.IsCollapsed(self.GetPath()) {
|
||||||
|
for _, child := range self.Children {
|
||||||
|
offsetChange, found := child.GetIndexForPath(path, collapsedPaths)
|
||||||
|
offset += offsetChange + 1
|
||||||
|
if found {
|
||||||
|
return offset, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) Size(collapsedPaths *CollapsedPaths) int {
|
||||||
|
if self == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
output := 1
|
||||||
|
|
||||||
|
if !collapsedPaths.IsCollapsed(self.GetPath()) {
|
||||||
|
for _, child := range self.Children {
|
||||||
|
output += child.Size(collapsedPaths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) Compress() {
|
||||||
|
if self == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.compressAux()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) compressAux() *Node[T] {
|
||||||
|
if self.IsFile() {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
children := self.Children
|
||||||
|
for i := range children {
|
||||||
|
grandchildren := children[i].Children
|
||||||
|
for len(grandchildren) == 1 && !grandchildren[0].IsFile() {
|
||||||
|
grandchildren[0].CompressionLevel = children[i].CompressionLevel + 1
|
||||||
|
children[i] = grandchildren[0]
|
||||||
|
grandchildren = children[i].Children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range children {
|
||||||
|
children[i] = children[i].compressAux()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.Children = children
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) GetPathsMatching(test func(*Node[T]) bool) []string {
|
||||||
|
paths := []string{}
|
||||||
|
|
||||||
|
if test(self) {
|
||||||
|
paths = append(paths, self.GetPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range self.Children {
|
||||||
|
paths = append(paths, child.GetPathsMatching(test)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) GetFilePathsMatching(test func(*T) bool) []string {
|
||||||
|
matchingFileNodes := slices.Filter(self.GetLeaves(), func(node *Node[T]) bool {
|
||||||
|
return test(node.File)
|
||||||
|
})
|
||||||
|
|
||||||
|
return slices.Map(matchingFileNodes, func(node *Node[T]) string {
|
||||||
|
return node.GetPath()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) GetLeaves() []*Node[T] {
|
||||||
|
if self.IsFile() {
|
||||||
|
return []*Node[T]{self}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.FlatMap(self.Children, func(child *Node[T]) []*Node[T] {
|
||||||
|
return child.GetLeaves()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) ID() string {
|
||||||
|
return self.GetPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node[T]) Description() string {
|
||||||
|
return self.GetPath()
|
||||||
|
}
|
@ -30,9 +30,10 @@ func RenderFileTree(
|
|||||||
diffName string,
|
diffName string,
|
||||||
submoduleConfigs []*models.SubmoduleConfig,
|
submoduleConfigs []*models.SubmoduleConfig,
|
||||||
) []string {
|
) []string {
|
||||||
return renderAux(tree.Tree(), tree.CollapsedPaths(), "", -1, func(n filetree.INode, depth int) string {
|
return renderAux(tree.GetRoot().Raw(), tree.CollapsedPaths(), "", -1, func(node *filetree.Node[models.File], depth int) string {
|
||||||
castN := n.(*filetree.FileNode)
|
fileNode := filetree.NewFileNode(node)
|
||||||
return getFileLine(castN.GetHasUnstagedChanges(), castN.GetHasStagedChanges(), castN.NameAtDepth(depth), diffName, submoduleConfigs, castN.File)
|
|
||||||
|
return getFileLine(fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), fileNameAtDepth(node, depth), diffName, submoduleConfigs, node.File)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,19 +42,17 @@ func RenderCommitFileTree(
|
|||||||
diffName string,
|
diffName string,
|
||||||
patchManager *patch.PatchManager,
|
patchManager *patch.PatchManager,
|
||||||
) []string {
|
) []string {
|
||||||
return renderAux(tree.Tree(), tree.CollapsedPaths(), "", -1, func(n filetree.INode, depth int) string {
|
return renderAux(tree.GetRoot().Raw(), tree.CollapsedPaths(), "", -1, func(node *filetree.Node[models.CommitFile], depth int) string {
|
||||||
castN := n.(*filetree.CommitFileNode)
|
|
||||||
|
|
||||||
// This is a little convoluted because we're dealing with either a leaf or a non-leaf.
|
// This is a little convoluted because we're dealing with either a leaf or a non-leaf.
|
||||||
// But this code actually applies to both. If it's a leaf, the status will just
|
// But this code actually applies to both. If it's a leaf, the status will just
|
||||||
// be whatever status it is, but if it's a non-leaf it will determine its status
|
// be whatever status it is, but if it's a non-leaf it will determine its status
|
||||||
// based on the leaves of that subtree
|
// based on the leaves of that subtree
|
||||||
var status patch.PatchStatus
|
var status patch.PatchStatus
|
||||||
if castN.EveryFile(func(file *models.CommitFile) bool {
|
if node.EveryFile(func(file *models.CommitFile) bool {
|
||||||
return patchManager.GetFileStatus(file.Name, tree.GetRef().RefName()) == patch.WHOLE
|
return patchManager.GetFileStatus(file.Name, tree.GetRef().RefName()) == patch.WHOLE
|
||||||
}) {
|
}) {
|
||||||
status = patch.WHOLE
|
status = patch.WHOLE
|
||||||
} else if castN.EveryFile(func(file *models.CommitFile) bool {
|
} else if node.EveryFile(func(file *models.CommitFile) bool {
|
||||||
return patchManager.GetFileStatus(file.Name, tree.GetRef().RefName()) == patch.UNSELECTED
|
return patchManager.GetFileStatus(file.Name, tree.GetRef().RefName()) == patch.UNSELECTED
|
||||||
}) {
|
}) {
|
||||||
status = patch.UNSELECTED
|
status = patch.UNSELECTED
|
||||||
@ -61,37 +60,37 @@ func RenderCommitFileTree(
|
|||||||
status = patch.PART
|
status = patch.PART
|
||||||
}
|
}
|
||||||
|
|
||||||
return getCommitFileLine(castN.NameAtDepth(depth), diffName, castN.File, status)
|
return getCommitFileLine(commitFileNameAtDepth(node, depth), diffName, node.File, status)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderAux(
|
func renderAux[T any](
|
||||||
s filetree.INode,
|
node *filetree.Node[T],
|
||||||
collapsedPaths *filetree.CollapsedPaths,
|
collapsedPaths *filetree.CollapsedPaths,
|
||||||
prefix string,
|
prefix string,
|
||||||
depth int,
|
depth int,
|
||||||
renderLine func(filetree.INode, int) string,
|
renderLine func(*filetree.Node[T], int) string,
|
||||||
) []string {
|
) []string {
|
||||||
if s == nil || s.IsNil() {
|
if node == nil {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
isRoot := depth == -1
|
isRoot := depth == -1
|
||||||
|
|
||||||
if s.IsLeaf() {
|
if node.IsFile() {
|
||||||
if isRoot {
|
if isRoot {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
return []string{prefix + renderLine(s, depth)}
|
return []string{prefix + renderLine(node, depth)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if collapsedPaths.IsCollapsed(s.GetPath()) {
|
if collapsedPaths.IsCollapsed(node.GetPath()) {
|
||||||
return []string{prefix + COLLAPSED_ARROW + " " + renderLine(s, depth)}
|
return []string{prefix + COLLAPSED_ARROW + " " + renderLine(node, depth)}
|
||||||
}
|
}
|
||||||
|
|
||||||
arr := []string{}
|
arr := []string{}
|
||||||
if !isRoot {
|
if !isRoot {
|
||||||
arr = append(arr, prefix+EXPANDED_ARROW+" "+renderLine(s, depth))
|
arr = append(arr, prefix+EXPANDED_ARROW+" "+renderLine(node, depth))
|
||||||
}
|
}
|
||||||
|
|
||||||
newPrefix := prefix
|
newPrefix := prefix
|
||||||
@ -101,8 +100,8 @@ func renderAux(
|
|||||||
newPrefix = strings.TrimSuffix(prefix, INNER_ITEM) + NESTED
|
newPrefix = strings.TrimSuffix(prefix, INNER_ITEM) + NESTED
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, child := range s.GetChildren() {
|
for i, child := range node.Children {
|
||||||
isLast := i == len(s.GetChildren())-1
|
isLast := i == len(node.Children)-1
|
||||||
|
|
||||||
var childPrefix string
|
var childPrefix string
|
||||||
if isRoot {
|
if isRoot {
|
||||||
@ -113,7 +112,7 @@ func renderAux(
|
|||||||
childPrefix = newPrefix + INNER_ITEM
|
childPrefix = newPrefix + INNER_ITEM
|
||||||
}
|
}
|
||||||
|
|
||||||
arr = append(arr, renderAux(child, collapsedPaths, childPrefix, depth+1+s.GetCompressionLevel(), renderLine)...)
|
arr = append(arr, renderAux(child, collapsedPaths, childPrefix, depth+1+node.CompressionLevel, renderLine)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr
|
return arr
|
||||||
@ -220,3 +219,39 @@ func getColorForChangeStatus(changeStatus string) style.TextStyle {
|
|||||||
return theme.DefaultTextColor
|
return theme.DefaultTextColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fileNameAtDepth(node *filetree.Node[models.File], depth int) string {
|
||||||
|
splitName := split(node.Path)
|
||||||
|
name := join(splitName[depth:])
|
||||||
|
|
||||||
|
if node.File != nil && node.File.IsRename() {
|
||||||
|
splitPrevName := split(node.File.PreviousName)
|
||||||
|
|
||||||
|
prevName := node.File.PreviousName
|
||||||
|
// if the file has just been renamed inside the same directory, we can shave off
|
||||||
|
// the prefix for the previous path too. Otherwise we'll keep it unchanged
|
||||||
|
sameParentDir := len(splitName) == len(splitPrevName) && join(splitName[0:depth]) == join(splitPrevName[0:depth])
|
||||||
|
if sameParentDir {
|
||||||
|
prevName = join(splitPrevName[depth:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return prevName + " → " + name
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func commitFileNameAtDepth(node *filetree.Node[models.CommitFile], depth int) string {
|
||||||
|
splitName := split(node.Path)
|
||||||
|
name := join(splitName[depth:])
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func split(str string) []string {
|
||||||
|
return strings.Split(str, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func join(strs []string) string {
|
||||||
|
return strings.Join(strs, "/")
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user