2022-01-30 05:46:46 +02:00
|
|
|
package filetree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
type FileTreeDisplayFilter int
|
|
|
|
|
|
|
|
const (
|
|
|
|
DisplayAll FileTreeDisplayFilter = iota
|
|
|
|
DisplayStaged
|
|
|
|
DisplayUnstaged
|
|
|
|
// this shows files with merge conflicts
|
|
|
|
DisplayConflicted
|
|
|
|
)
|
|
|
|
|
2022-01-30 07:38:07 +02:00
|
|
|
type ITree interface {
|
2022-01-30 05:46:46 +02:00
|
|
|
InTreeMode() bool
|
|
|
|
ExpandToPath(path string)
|
|
|
|
ToggleShowTree()
|
|
|
|
GetIndexForPath(path string) (int, bool)
|
2022-03-19 00:31:52 +02:00
|
|
|
Len() int
|
2022-01-30 05:46:46 +02:00
|
|
|
SetTree()
|
|
|
|
IsCollapsed(path string) bool
|
|
|
|
ToggleCollapsed(path string)
|
|
|
|
Tree() INode
|
2022-03-19 03:26:30 +02:00
|
|
|
CollapsedPaths() *CollapsedPaths
|
2022-01-30 07:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type IFileTree interface {
|
|
|
|
ITree
|
|
|
|
|
|
|
|
FilterFiles(test func(*models.File) bool) []*models.File
|
|
|
|
SetFilter(filter FileTreeDisplayFilter)
|
2022-03-19 00:31:52 +02:00
|
|
|
Get(index int) *FileNode
|
2022-01-30 07:38:07 +02:00
|
|
|
GetFile(path string) *models.File
|
|
|
|
GetAllItems() []*FileNode
|
|
|
|
GetAllFiles() []*models.File
|
2022-01-30 05:46:46 +02:00
|
|
|
GetFilter() FileTreeDisplayFilter
|
|
|
|
}
|
|
|
|
|
|
|
|
type FileTree struct {
|
|
|
|
getFiles func() []*models.File
|
|
|
|
tree *FileNode
|
|
|
|
showTree bool
|
|
|
|
log *logrus.Entry
|
|
|
|
filter FileTreeDisplayFilter
|
2022-03-19 03:26:30 +02:00
|
|
|
collapsedPaths *CollapsedPaths
|
2022-01-30 05:46:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewFileTree(getFiles func() []*models.File, log *logrus.Entry, showTree bool) *FileTree {
|
|
|
|
return &FileTree{
|
|
|
|
getFiles: getFiles,
|
|
|
|
log: log,
|
|
|
|
showTree: showTree,
|
|
|
|
filter: DisplayAll,
|
2022-03-19 03:26:30 +02:00
|
|
|
collapsedPaths: NewCollapsedPaths(),
|
2022-01-30 05:46:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) InTreeMode() bool {
|
|
|
|
return self.showTree
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) ExpandToPath(path string) {
|
|
|
|
self.collapsedPaths.ExpandToPath(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) getFilesForDisplay() []*models.File {
|
|
|
|
switch self.filter {
|
|
|
|
case DisplayAll:
|
|
|
|
return self.getFiles()
|
|
|
|
case DisplayStaged:
|
|
|
|
return self.FilterFiles(func(file *models.File) bool { return file.HasStagedChanges })
|
|
|
|
case DisplayUnstaged:
|
|
|
|
return self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges })
|
|
|
|
case DisplayConflicted:
|
|
|
|
return self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts })
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("Unexpected files display filter: %d", self.filter))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) FilterFiles(test func(*models.File) bool) []*models.File {
|
|
|
|
result := make([]*models.File, 0)
|
|
|
|
for _, file := range self.getFiles() {
|
|
|
|
if test(file) {
|
|
|
|
result = append(result, file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) SetFilter(filter FileTreeDisplayFilter) {
|
|
|
|
self.filter = filter
|
|
|
|
self.SetTree()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) ToggleShowTree() {
|
|
|
|
self.showTree = !self.showTree
|
|
|
|
self.SetTree()
|
|
|
|
}
|
|
|
|
|
2022-03-19 00:31:52 +02:00
|
|
|
func (self *FileTree) Get(index int) *FileNode {
|
2022-01-30 05:46:46 +02:00
|
|
|
// need to traverse the three depth first until we get to the index.
|
|
|
|
return self.tree.GetNodeAtIndex(index+1, self.collapsedPaths) // ignoring root
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) GetFile(path string) *models.File {
|
|
|
|
for _, file := range self.getFiles() {
|
|
|
|
if file.Name == path {
|
|
|
|
return file
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) GetIndexForPath(path string) (int, bool) {
|
|
|
|
index, found := self.tree.GetIndexForPath(path, self.collapsedPaths)
|
|
|
|
return index - 1, found
|
|
|
|
}
|
|
|
|
|
|
|
|
// note: this gets all items when the filter is taken into consideration. There may
|
|
|
|
// be hidden files that aren't included here. Files off the screen however will
|
|
|
|
// be included
|
|
|
|
func (self *FileTree) GetAllItems() []*FileNode {
|
|
|
|
if self.tree == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.tree.Flatten(self.collapsedPaths)[1:] // ignoring root
|
|
|
|
}
|
|
|
|
|
2022-03-19 00:31:52 +02:00
|
|
|
func (self *FileTree) Len() int {
|
2022-01-30 05:46:46 +02:00
|
|
|
return self.tree.Size(self.collapsedPaths) - 1 // ignoring root
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) GetAllFiles() []*models.File {
|
|
|
|
return self.getFiles()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) SetTree() {
|
|
|
|
filesForDisplay := self.getFilesForDisplay()
|
|
|
|
if self.showTree {
|
|
|
|
self.tree = BuildTreeFromFiles(filesForDisplay)
|
|
|
|
} else {
|
|
|
|
self.tree = BuildFlatTreeFromFiles(filesForDisplay)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) IsCollapsed(path string) bool {
|
|
|
|
return self.collapsedPaths.IsCollapsed(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) ToggleCollapsed(path string) {
|
|
|
|
self.collapsedPaths.ToggleCollapsed(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) Tree() INode {
|
|
|
|
return self.tree
|
|
|
|
}
|
|
|
|
|
2022-03-19 03:26:30 +02:00
|
|
|
func (self *FileTree) CollapsedPaths() *CollapsedPaths {
|
2022-01-30 05:46:46 +02:00
|
|
|
return self.collapsedPaths
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *FileTree) GetFilter() FileTreeDisplayFilter {
|
|
|
|
return self.filter
|
|
|
|
}
|