mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-28 10:15:32 -05:00
268 lines
7.1 KiB
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
|
|
}
|