Files
lazygit/pkg/gui/filetree/commit_file_tree_view_model.go

268 lines
7.1 KiB
Go

package filetree
import (
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type ICommitFileTreeViewModel interface {
ICommitFileTree
types.IListCursor
GetRef() models.Ref
SetRef(models.Ref)
GetRefRange() *types.RefRange // can be nil, in which case GetRef should be used
SetRefRange(*types.RefRange) // should be set to nil when selection is not a range
GetCanRebase() bool
SetCanRebase(bool)
}
type CommitFileTreeViewModel struct {
types.IListCursor
ICommitFileTree
// this is e.g. the commit for which we're viewing the files, if there is no
// range selection, or if the range selection can't be used for some reason
ref models.Ref
// this is a commit range for which we're viewing the files. Can be nil, in
// which case ref is used.
refRange *types.RefRange
// we set this to true when you're viewing the files within the checked-out branch's commits.
// If you're viewing the files of some random other branch we can't do any rebase stuff.
canRebase bool
searchHistory *utils.HistoryBuffer[string]
}
var _ ICommitFileTreeViewModel = &CommitFileTreeViewModel{}
func NewCommitFileTreeViewModel(getFiles func() []*models.CommitFile, common *common.Common, showTree bool) *CommitFileTreeViewModel {
fileTree := NewCommitFileTree(getFiles, common, showTree)
listCursor := traits.NewListCursor(fileTree.Len)
return &CommitFileTreeViewModel{
ICommitFileTree: fileTree,
IListCursor: listCursor,
ref: nil,
refRange: nil,
canRebase: false,
searchHistory: utils.NewHistoryBuffer[string](1000),
}
}
func (self *CommitFileTreeViewModel) GetRef() models.Ref {
return self.ref
}
func (self *CommitFileTreeViewModel) SetRef(ref models.Ref) {
self.ref = ref
}
func (self *CommitFileTreeViewModel) GetRefRange() *types.RefRange {
return self.refRange
}
func (self *CommitFileTreeViewModel) SetRefRange(refsForRange *types.RefRange) {
self.refRange = refsForRange
}
func (self *CommitFileTreeViewModel) GetCanRebase() bool {
return self.canRebase
}
func (self *CommitFileTreeViewModel) SetCanRebase(canRebase bool) {
self.canRebase = canRebase
}
func (self *CommitFileTreeViewModel) GetSelected() *CommitFileNode {
if self.Len() == 0 {
return nil
}
return self.Get(self.GetSelectedLineIdx())
}
func (self *CommitFileTreeViewModel) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.ID()
}
func (self *CommitFileTreeViewModel) GetSelectedItems() ([]*CommitFileNode, int, int) {
if self.Len() == 0 {
return nil, 0, 0
}
startIdx, endIdx := self.GetSelectionRange()
nodes := []*CommitFileNode{}
for i := startIdx; i <= endIdx; i++ {
nodes = append(nodes, self.Get(i))
}
return nodes, startIdx, endIdx
}
func (self *CommitFileTreeViewModel) GetSelectedItemIds() ([]string, int, int) {
selectedItems, startIdx, endIdx := self.GetSelectedItems()
ids := lo.Map(selectedItems, func(item *CommitFileNode, _ int) string {
return item.ID()
})
return ids, startIdx, endIdx
}
func (self *CommitFileTreeViewModel) GetSelectedFile() *models.CommitFile {
node := self.GetSelected()
if node == nil {
return nil
}
return node.File
}
func (self *CommitFileTreeViewModel) GetSelectedPath() string {
node := self.GetSelected()
if node == nil {
return ""
}
return node.GetPath()
}
// SetTree rebuilds the tree and clamps the selection so it stays in range. The
// embedded tree's SetTree only rebuilds the node list and doesn't touch the
// cursor, so after a shrinking rebuild (e.g. moving a patch out into the index)
// the selection index could be left past the end of the tree; GetSelectedItems
// would then return a nil node and crash callers such as canEditFiles when the
// options map is rendered during layout.
//
// Unlike FileTreeViewModel.SetTree we don't re-find the selected node by path
// afterwards: that walk lands on the containing directory when a file is removed
// from a dir that then collapses, whereas keeping the (clamped) index lands on
// the sibling file, which is what we want here.
func (self *CommitFileTreeViewModel) SetTree() {
self.ICommitFileTree.SetTree()
self.ClampSelection()
}
// duplicated from file_tree_view_model.go. Generics will help here
func (self *CommitFileTreeViewModel) ToggleShowTree() {
selectedNode := self.GetSelected()
self.ICommitFileTree.ToggleShowTree()
if selectedNode == nil {
return
}
path := selectedNode.path
if self.InTreeMode() {
self.ExpandToPath(path)
} else if len(selectedNode.Children) > 0 {
path = selectedNode.GetLeaves()[0].path
}
index, found := self.GetIndexForPath(path)
if found {
self.SetSelection(index)
}
}
func (self *CommitFileTreeViewModel) CollapseAll() {
selectedNode := self.GetSelected()
self.ICommitFileTree.CollapseAll()
if selectedNode == nil {
return
}
topLevelPath := strings.Split(selectedNode.path, "/")[0]
index, found := self.GetIndexForPath(topLevelPath)
if found {
self.SetSelectedLineIdx(index)
}
}
func (self *CommitFileTreeViewModel) ExpandAll() {
selectedNode := self.GetSelected()
self.ICommitFileTree.ExpandAll()
if selectedNode == nil {
return
}
index, found := self.GetIndexForPath(selectedNode.path)
if found {
self.SetSelectedLineIdx(index)
}
}
// Try to select the given path if present. If it doesn't exist, or one of the parent directories is
// collapsed, do nothing.
// Note that filepath is an actual file path, not an internal tree path as with e.g.
// ToggleCollapsed. It must be a relative path (relative to the repo root), and it must contain
// forward slashes rather than backslashes even on Windows.
func (self *CommitFileTreeViewModel) SelectPath(filepath string, showRootItem bool) {
index, found := self.GetIndexForPath(InternalTreePathForFilePath(filepath, showRootItem))
if found {
self.SetSelection(index)
}
}
// IFilterableContext methods
func (self *CommitFileTreeViewModel) SetFilter(filter string, useFuzzySearch bool) {
self.ICommitFileTree.SetTextFilter(filter, useFuzzySearch)
}
func (self *CommitFileTreeViewModel) GetFilter() string {
return self.ICommitFileTree.GetTextFilter()
}
func (self *CommitFileTreeViewModel) ClearFilter() {
selectedNode := self.GetSelected()
var selectedPath string
if selectedNode != nil {
selectedPath = selectedNode.GetInternalPath()
}
self.ICommitFileTree.SetTextFilter("", false)
if selectedPath != "" {
self.ExpandToPath(selectedPath)
if idx, found := self.GetIndexForPath(selectedPath); found {
self.SetSelection(idx)
return
}
}
self.ClampSelection()
}
func (self *CommitFileTreeViewModel) ReApplyFilter(useFuzzySearch bool) {
self.ICommitFileTree.SetTextFilter(self.ICommitFileTree.GetTextFilter(), useFuzzySearch)
}
func (self *CommitFileTreeViewModel) IsFiltering() bool {
return self.ICommitFileTree.GetTextFilter() != ""
}
// used for type switch
func (self *CommitFileTreeViewModel) IsFilterableContext() {}
func (self *CommitFileTreeViewModel) GetSearchHistory() *utils.HistoryBuffer[string] {
return self.searchHistory
}