mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-03-23 21:51:07 +02:00
migrate files context to new structure
This commit is contained in:
parent
09dc160da9
commit
8ea7b7a62e
@ -56,7 +56,7 @@ var AllContextKeys = []types.ContextKey{
|
|||||||
|
|
||||||
type ContextTree struct {
|
type ContextTree struct {
|
||||||
Status types.Context
|
Status types.Context
|
||||||
Files types.IListContext
|
Files *WorkingTreeContext
|
||||||
Submodules types.IListContext
|
Submodules types.IListContext
|
||||||
Menu types.IListContext
|
Menu types.IListContext
|
||||||
Branches types.IListContext
|
Branches types.IListContext
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TagsContext struct {
|
type TagsContext struct {
|
||||||
*TagsList
|
*TagsViewModel
|
||||||
*BaseContext
|
*BaseContext
|
||||||
*ListContextTrait
|
*ListContextTrait
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ func NewTagsContext(
|
|||||||
self := &TagsContext{}
|
self := &TagsContext{}
|
||||||
takeFocus := func() error { return c.PushContext(self) }
|
takeFocus := func() error { return c.PushContext(self) }
|
||||||
|
|
||||||
list := NewTagsList(getModel)
|
list := NewTagsViewModel(getModel)
|
||||||
viewTrait := NewViewTrait(getView)
|
viewTrait := NewViewTrait(getView)
|
||||||
listContextTrait := &ListContextTrait{
|
listContextTrait := &ListContextTrait{
|
||||||
base: baseContext,
|
base: baseContext,
|
||||||
@ -56,21 +56,21 @@ func NewTagsContext(
|
|||||||
|
|
||||||
self.BaseContext = baseContext
|
self.BaseContext = baseContext
|
||||||
self.ListContextTrait = listContextTrait
|
self.ListContextTrait = listContextTrait
|
||||||
self.TagsList = list
|
self.TagsViewModel = list
|
||||||
|
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
type TagsList struct {
|
type TagsViewModel struct {
|
||||||
*ListTrait
|
*ListTrait
|
||||||
getModel func() []*models.Tag
|
getModel func() []*models.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *TagsList) GetItemsLength() int {
|
func (self *TagsViewModel) GetItemsLength() int {
|
||||||
return len(self.getModel())
|
return len(self.getModel())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *TagsList) GetSelectedTag() *models.Tag {
|
func (self *TagsViewModel) GetSelectedTag() *models.Tag {
|
||||||
if self.GetItemsLength() == 0 {
|
if self.GetItemsLength() == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -78,13 +78,13 @@ func (self *TagsList) GetSelectedTag() *models.Tag {
|
|||||||
return self.getModel()[self.GetSelectedLineIdx()]
|
return self.getModel()[self.GetSelectedLineIdx()]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *TagsList) GetSelectedItem() (types.ListItem, bool) {
|
func (self *TagsViewModel) GetSelectedItem() (types.ListItem, bool) {
|
||||||
tag := self.GetSelectedTag()
|
item := self.GetSelectedTag()
|
||||||
return tag, tag != nil
|
return item, item != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTagsList(getModel func() []*models.Tag) *TagsList {
|
func NewTagsViewModel(getModel func() []*models.Tag) *TagsViewModel {
|
||||||
self := &TagsList{
|
self := &TagsViewModel{
|
||||||
getModel: getModel,
|
getModel: getModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
101
pkg/gui/context/working_tree_context.go
Normal file
101
pkg/gui/context/working_tree_context.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WorkingTreeContext struct {
|
||||||
|
*WorkingTreeViewModal
|
||||||
|
*BaseContext
|
||||||
|
*ListContextTrait
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.IListContext = (*WorkingTreeContext)(nil)
|
||||||
|
|
||||||
|
func NewWorkingTreeContext(
|
||||||
|
getModel func() []*models.File,
|
||||||
|
getView func() *gocui.View,
|
||||||
|
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||||
|
|
||||||
|
onFocus func(...types.OnFocusOpts) error,
|
||||||
|
onRenderToMain func(...types.OnFocusOpts) error,
|
||||||
|
onFocusLost func() error,
|
||||||
|
|
||||||
|
c *types.ControllerCommon,
|
||||||
|
) *WorkingTreeContext {
|
||||||
|
baseContext := NewBaseContext(NewBaseContextOpts{
|
||||||
|
ViewName: "files",
|
||||||
|
WindowName: "files",
|
||||||
|
Key: FILES_CONTEXT_KEY,
|
||||||
|
Kind: types.SIDE_CONTEXT,
|
||||||
|
})
|
||||||
|
|
||||||
|
self := &WorkingTreeContext{}
|
||||||
|
takeFocus := func() error { return c.PushContext(self) }
|
||||||
|
|
||||||
|
list := NewWorkingTreeViewModal(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
|
||||||
|
viewTrait := NewViewTrait(getView)
|
||||||
|
listContextTrait := &ListContextTrait{
|
||||||
|
base: baseContext,
|
||||||
|
listTrait: list.ListTrait,
|
||||||
|
viewTrait: viewTrait,
|
||||||
|
|
||||||
|
GetDisplayStrings: getDisplayStrings,
|
||||||
|
OnFocus: onFocus,
|
||||||
|
OnRenderToMain: onRenderToMain,
|
||||||
|
OnFocusLost: onFocusLost,
|
||||||
|
takeFocus: takeFocus,
|
||||||
|
|
||||||
|
// TODO: handle this in a trait
|
||||||
|
RenderSelection: false,
|
||||||
|
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.BaseContext = baseContext
|
||||||
|
self.ListContextTrait = listContextTrait
|
||||||
|
self.WorkingTreeViewModal = list
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkingTreeViewModal struct {
|
||||||
|
*ListTrait
|
||||||
|
*filetree.FileTreeViewModel
|
||||||
|
getModel func() []*models.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorkingTreeViewModal) GetItemsLength() int {
|
||||||
|
return self.FileTreeViewModel.GetItemsLength()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorkingTreeViewModal) GetSelectedFileNode() *filetree.FileNode {
|
||||||
|
if self.GetItemsLength() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.FileTreeViewModel.GetItemAtIndex(self.selectedIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorkingTreeViewModal) GetSelectedItem() (types.ListItem, bool) {
|
||||||
|
item := self.GetSelectedFileNode()
|
||||||
|
return item, item != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorkingTreeViewModal(getModel func() []*models.File, log *logrus.Entry, showTree bool) *WorkingTreeViewModal {
|
||||||
|
self := &WorkingTreeViewModal{
|
||||||
|
getModel: getModel,
|
||||||
|
FileTreeViewModel: filetree.NewFileTreeViewModel(getModel, log, showTree),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ListTrait = &ListTrait{
|
||||||
|
selectedIdx: 0,
|
||||||
|
HasLength: self,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
@ -22,13 +22,13 @@ type FilesController struct {
|
|||||||
// struct embedding, but Go does not allow hiding public fields in an embedded struct
|
// struct embedding, but Go does not allow hiding public fields in an embedded struct
|
||||||
// to the client
|
// to the client
|
||||||
c *types.ControllerCommon
|
c *types.ControllerCommon
|
||||||
getContext func() types.IListContext
|
getContext func() *context.WorkingTreeContext
|
||||||
|
getFiles func() []*models.File
|
||||||
git *commands.GitCommand
|
git *commands.GitCommand
|
||||||
os *oscommands.OSCommand
|
os *oscommands.OSCommand
|
||||||
|
|
||||||
getSelectedFileNode func() *filetree.FileNode
|
getSelectedFileNode func() *filetree.FileNode
|
||||||
getContexts func() context.ContextTree
|
getContexts func() context.ContextTree
|
||||||
getViewModel func() *filetree.FileTreeViewModel
|
|
||||||
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
||||||
getSubmodules func() []*models.SubmoduleConfig
|
getSubmodules func() []*models.SubmoduleConfig
|
||||||
setCommitMessage func(message string)
|
setCommitMessage func(message string)
|
||||||
@ -48,12 +48,12 @@ var _ types.IController = &FilesController{}
|
|||||||
|
|
||||||
func NewFilesController(
|
func NewFilesController(
|
||||||
c *types.ControllerCommon,
|
c *types.ControllerCommon,
|
||||||
getContext func() types.IListContext,
|
getContext func() *context.WorkingTreeContext,
|
||||||
|
getFiles func() []*models.File,
|
||||||
git *commands.GitCommand,
|
git *commands.GitCommand,
|
||||||
os *oscommands.OSCommand,
|
os *oscommands.OSCommand,
|
||||||
getSelectedFileNode func() *filetree.FileNode,
|
getSelectedFileNode func() *filetree.FileNode,
|
||||||
allContexts func() context.ContextTree,
|
allContexts func() context.ContextTree,
|
||||||
getViewModel func() *filetree.FileTreeViewModel,
|
|
||||||
enterSubmodule func(submodule *models.SubmoduleConfig) error,
|
enterSubmodule func(submodule *models.SubmoduleConfig) error,
|
||||||
getSubmodules func() []*models.SubmoduleConfig,
|
getSubmodules func() []*models.SubmoduleConfig,
|
||||||
setCommitMessage func(message string),
|
setCommitMessage func(message string),
|
||||||
@ -70,11 +70,11 @@ func NewFilesController(
|
|||||||
return &FilesController{
|
return &FilesController{
|
||||||
c: c,
|
c: c,
|
||||||
getContext: getContext,
|
getContext: getContext,
|
||||||
|
getFiles: getFiles,
|
||||||
git: git,
|
git: git,
|
||||||
os: os,
|
os: os,
|
||||||
getSelectedFileNode: getSelectedFileNode,
|
getSelectedFileNode: getSelectedFileNode,
|
||||||
getContexts: allContexts,
|
getContexts: allContexts,
|
||||||
getViewModel: getViewModel,
|
|
||||||
enterSubmodule: enterSubmodule,
|
enterSubmodule: enterSubmodule,
|
||||||
getSubmodules: getSubmodules,
|
getSubmodules: getSubmodules,
|
||||||
setCommitMessage: setCommitMessage,
|
setCommitMessage: setCommitMessage,
|
||||||
@ -185,6 +185,7 @@ func (self *FilesController) Keybindings(getKey func(key string) interface{}, co
|
|||||||
Description: self.c.Tr.LcViewResetToUpstreamOptions,
|
Description: self.c.Tr.LcViewResetToUpstreamOptions,
|
||||||
OpensMenu: true,
|
OpensMenu: true,
|
||||||
},
|
},
|
||||||
|
// here
|
||||||
{
|
{
|
||||||
Key: getKey(config.Files.ToggleTreeView),
|
Key: getKey(config.Files.ToggleTreeView),
|
||||||
Handler: self.toggleTreeView,
|
Handler: self.toggleTreeView,
|
||||||
@ -303,7 +304,7 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *FilesController) allFilesStaged() bool {
|
func (self *FilesController) allFilesStaged() bool {
|
||||||
for _, file := range self.getViewModel().GetAllFiles() {
|
for _, file := range self.getFiles() {
|
||||||
if file.HasUnstagedChanges {
|
if file.HasUnstagedChanges {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -433,7 +434,7 @@ func (self *FilesController) HandleCommitPress() error {
|
|||||||
return self.c.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.getViewModel().GetItemsLength() == 0 {
|
if len(self.getFiles()) == 0 {
|
||||||
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,7 +485,7 @@ func (self *FilesController) promptToStageAllAndRetry(retry func() error) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *FilesController) handleAmendCommitPress() error {
|
func (self *FilesController) handleAmendCommitPress() error {
|
||||||
if self.getViewModel().GetItemsLength() == 0 {
|
if len(self.getFiles()) == 0 {
|
||||||
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,7 +511,7 @@ func (self *FilesController) handleAmendCommitPress() error {
|
|||||||
// HandleCommitEditorPress - handle when the user wants to commit changes via
|
// HandleCommitEditorPress - handle when the user wants to commit changes via
|
||||||
// their editor rather than via the popup panel
|
// their editor rather than via the popup panel
|
||||||
func (self *FilesController) HandleCommitEditorPress() error {
|
func (self *FilesController) HandleCommitEditorPress() error {
|
||||||
if self.getViewModel().GetItemsLength() == 0 {
|
if len(self.getFiles()) == 0 {
|
||||||
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,7 +552,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
|
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
|
||||||
self.getViewModel().SetFilter(filter)
|
self.getContext().FileTreeViewModel.SetFilter(filter)
|
||||||
return self.c.PostRefreshUpdate(self.getContext())
|
return self.c.PostRefreshUpdate(self.getContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -642,7 +643,7 @@ func (self *FilesController) handleToggleDirCollapsed() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
self.getViewModel().ToggleCollapsed(node.GetPath())
|
self.getContext().FileTreeViewModel.ToggleCollapsed(node.GetPath())
|
||||||
|
|
||||||
if err := self.c.PostRefreshUpdate(self.getContexts().Files); err != nil {
|
if err := self.c.PostRefreshUpdate(self.getContexts().Files); err != nil {
|
||||||
self.c.Log.Error(err)
|
self.c.Log.Error(err)
|
||||||
@ -655,12 +656,12 @@ func (self *FilesController) toggleTreeView() error {
|
|||||||
// get path of currently selected file
|
// get path of currently selected file
|
||||||
path := self.getSelectedPath()
|
path := self.getSelectedPath()
|
||||||
|
|
||||||
self.getViewModel().ToggleShowTree()
|
self.getContext().FileTreeViewModel.ToggleShowTree()
|
||||||
|
|
||||||
// find that same node in the new format and move the cursor to it
|
// find that same node in the new format and move the cursor to it
|
||||||
if path != "" {
|
if path != "" {
|
||||||
self.getViewModel().ExpandToPath(path)
|
self.getContext().FileTreeViewModel.ExpandToPath(path)
|
||||||
index, found := self.getViewModel().GetIndexForPath(path)
|
index, found := self.getContext().FileTreeViewModel.GetIndexForPath(path)
|
||||||
if found {
|
if found {
|
||||||
self.getContext().GetPanelState().SetSelectedLineIdx(index)
|
self.getContext().GetPanelState().SetSelectedLineIdx(index)
|
||||||
}
|
}
|
||||||
|
@ -5,18 +5,12 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
|
|
||||||
func (gui *Gui) getSelectedFileNode() *filetree.FileNode {
|
func (gui *Gui) getSelectedFileNode() *filetree.FileNode {
|
||||||
selectedLine := gui.State.Panels.Files.SelectedLineIdx
|
return gui.State.Contexts.Files.GetSelectedFileNode()
|
||||||
if selectedLine == -1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return gui.State.FileTreeViewModel.GetItemAtIndex(selectedLine)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getSelectedFile() *models.File {
|
func (gui *Gui) getSelectedFile() *models.File {
|
||||||
@ -96,44 +90,6 @@ func (gui *Gui) promptToContinueRebase() error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's try to find our file again and move the cursor to that.
|
|
||||||
// If we can't find our file, it was probably just removed by the user. In that
|
|
||||||
// case, we go looking for where the next file has been moved to. Given that the
|
|
||||||
// user could have removed a whole directory, we continue iterating through the old
|
|
||||||
// nodes until we find one that exists in the new set of nodes, then move the cursor
|
|
||||||
// to that.
|
|
||||||
// prevNodes starts from our previously selected node because we don't need to consider anything above that
|
|
||||||
func (gui *Gui) findNewSelectedIdx(prevNodes []*filetree.FileNode, currNodes []*filetree.FileNode) int {
|
|
||||||
getPaths := func(node *filetree.FileNode) []string {
|
|
||||||
if node == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if node.File != nil && node.File.IsRename() {
|
|
||||||
return node.File.Names()
|
|
||||||
} else {
|
|
||||||
return []string{node.Path}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, prevNode := range prevNodes {
|
|
||||||
selectedPaths := getPaths(prevNode)
|
|
||||||
|
|
||||||
for idx, node := range currNodes {
|
|
||||||
paths := getPaths(node)
|
|
||||||
|
|
||||||
// If you started off with a rename selected, and now it's broken in two, we want you to jump to the new file, not the old file.
|
|
||||||
// This is because the new should be in the same position as the rename was meaning less cursor jumping
|
|
||||||
foundOldFileInRename := prevNode.File != nil && prevNode.File.IsRename() && node.Path == prevNode.File.PreviousName
|
|
||||||
foundNode := utils.StringArraysOverlap(paths, selectedPaths) && !foundOldFileInRename
|
|
||||||
if foundNode {
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) onFocusFile() error {
|
func (gui *Gui) onFocusFile() error {
|
||||||
gui.takeOverMergeConflictScrolling()
|
gui.takeOverMergeConflictScrolling()
|
||||||
return nil
|
return nil
|
||||||
|
@ -142,13 +142,13 @@ func TestGetFile(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid case",
|
name: "valid case",
|
||||||
viewModel: NewFileTreeViewModel([]*models.File{{Name: "blah/one"}, {Name: "blah/two"}}, nil, false),
|
viewModel: NewFileTreeViewModel(func() []*models.File { return []*models.File{{Name: "blah/one"}, {Name: "blah/two"}} }, nil, false),
|
||||||
path: "blah/two",
|
path: "blah/two",
|
||||||
expected: &models.File{Name: "blah/two"},
|
expected: &models.File{Name: "blah/two"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "not found",
|
name: "not found",
|
||||||
viewModel: NewFileTreeViewModel([]*models.File{{Name: "blah/one"}, {Name: "blah/two"}}, nil, false),
|
viewModel: NewFileTreeViewModel(func() []*models.File { return []*models.File{{Name: "blah/one"}, {Name: "blah/two"}} }, nil, false),
|
||||||
path: "blah/three",
|
path: "blah/three",
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
|
@ -19,7 +19,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FileTreeViewModel struct {
|
type FileTreeViewModel struct {
|
||||||
files []*models.File
|
getFiles func() []*models.File
|
||||||
tree *FileNode
|
tree *FileNode
|
||||||
showTree bool
|
showTree bool
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
@ -28,8 +28,9 @@ type FileTreeViewModel struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileTreeViewModel(files []*models.File, log *logrus.Entry, showTree bool) *FileTreeViewModel {
|
func NewFileTreeViewModel(getFiles func() []*models.File, log *logrus.Entry, showTree bool) *FileTreeViewModel {
|
||||||
viewModel := &FileTreeViewModel{
|
viewModel := &FileTreeViewModel{
|
||||||
|
getFiles: getFiles,
|
||||||
log: log,
|
log: log,
|
||||||
showTree: showTree,
|
showTree: showTree,
|
||||||
filter: DisplayAll,
|
filter: DisplayAll,
|
||||||
@ -37,8 +38,6 @@ func NewFileTreeViewModel(files []*models.File, log *logrus.Entry, showTree bool
|
|||||||
RWMutex: sync.RWMutex{},
|
RWMutex: sync.RWMutex{},
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.SetFiles(files)
|
|
||||||
|
|
||||||
return viewModel
|
return viewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,11 +50,9 @@ func (self *FileTreeViewModel) ExpandToPath(path string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileTreeViewModel) GetFilesForDisplay() []*models.File {
|
func (self *FileTreeViewModel) GetFilesForDisplay() []*models.File {
|
||||||
files := self.files
|
|
||||||
|
|
||||||
switch self.filter {
|
switch self.filter {
|
||||||
case DisplayAll:
|
case DisplayAll:
|
||||||
return files
|
return self.getFiles()
|
||||||
case DisplayStaged:
|
case DisplayStaged:
|
||||||
return self.FilterFiles(func(file *models.File) bool { return file.HasStagedChanges })
|
return self.FilterFiles(func(file *models.File) bool { return file.HasStagedChanges })
|
||||||
case DisplayUnstaged:
|
case DisplayUnstaged:
|
||||||
@ -69,7 +66,7 @@ func (self *FileTreeViewModel) GetFilesForDisplay() []*models.File {
|
|||||||
|
|
||||||
func (self *FileTreeViewModel) FilterFiles(test func(*models.File) bool) []*models.File {
|
func (self *FileTreeViewModel) FilterFiles(test func(*models.File) bool) []*models.File {
|
||||||
result := make([]*models.File, 0)
|
result := make([]*models.File, 0)
|
||||||
for _, file := range self.files {
|
for _, file := range self.getFiles() {
|
||||||
if test(file) {
|
if test(file) {
|
||||||
result = append(result, file)
|
result = append(result, file)
|
||||||
}
|
}
|
||||||
@ -93,7 +90,7 @@ func (self *FileTreeViewModel) GetItemAtIndex(index int) *FileNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileTreeViewModel) GetFile(path string) *models.File {
|
func (self *FileTreeViewModel) GetFile(path string) *models.File {
|
||||||
for _, file := range self.files {
|
for _, file := range self.getFiles() {
|
||||||
if file.Name == path {
|
if file.Name == path {
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
@ -120,13 +117,7 @@ func (self *FileTreeViewModel) GetItemsLength() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileTreeViewModel) GetAllFiles() []*models.File {
|
func (self *FileTreeViewModel) GetAllFiles() []*models.File {
|
||||||
return self.files
|
return self.getFiles()
|
||||||
}
|
|
||||||
|
|
||||||
func (self *FileTreeViewModel) SetFiles(files []*models.File) {
|
|
||||||
self.files = files
|
|
||||||
|
|
||||||
self.SetTree()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileTreeViewModel) SetTree() {
|
func (self *FileTreeViewModel) SetTree() {
|
||||||
|
@ -73,7 +73,7 @@ func TestFilterAction(t *testing.T) {
|
|||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
s := s
|
s := s
|
||||||
t.Run(s.name, func(t *testing.T) {
|
t.Run(s.name, func(t *testing.T) {
|
||||||
mngr := &FileTreeViewModel{files: s.files, filter: s.filter}
|
mngr := &FileTreeViewModel{getFiles: s.files, filter: s.filter}
|
||||||
result := mngr.GetFilesForDisplay()
|
result := mngr.GetFilesForDisplay()
|
||||||
assert.EqualValues(t, s.expected, result)
|
assert.EqualValues(t, s.expected, result)
|
||||||
})
|
})
|
||||||
|
@ -175,8 +175,8 @@ type PrevLayout struct {
|
|||||||
type GuiRepoState struct {
|
type GuiRepoState struct {
|
||||||
// the file panels (files and commit files) can render as a tree, so we have
|
// the file panels (files and commit files) can render as a tree, so we have
|
||||||
// managers for them which handle rendering a flat list of files in tree form
|
// managers for them which handle rendering a flat list of files in tree form
|
||||||
FileTreeViewModel *filetree.FileTreeViewModel
|
|
||||||
CommitFileTreeViewModel *filetree.CommitFileTreeViewModel
|
CommitFileTreeViewModel *filetree.CommitFileTreeViewModel
|
||||||
|
Files []*models.File
|
||||||
Submodules []*models.SubmoduleConfig
|
Submodules []*models.SubmoduleConfig
|
||||||
Branches []*models.Branch
|
Branches []*models.Branch
|
||||||
Commits []*models.Commit
|
Commits []*models.Commit
|
||||||
@ -433,14 +433,13 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
|
|||||||
contexts := gui.contextTree()
|
contexts := gui.contextTree()
|
||||||
|
|
||||||
screenMode := SCREEN_NORMAL
|
screenMode := SCREEN_NORMAL
|
||||||
initialContext := contexts.Files
|
var initialContext types.IListContext = contexts.Files
|
||||||
if filterPath != "" {
|
if filterPath != "" {
|
||||||
screenMode = SCREEN_HALF
|
screenMode = SCREEN_HALF
|
||||||
initialContext = contexts.BranchCommits
|
initialContext = contexts.BranchCommits
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State = &GuiRepoState{
|
gui.State = &GuiRepoState{
|
||||||
FileTreeViewModel: filetree.NewFileTreeViewModel(make([]*models.File, 0), gui.Log, showTree),
|
|
||||||
CommitFileTreeViewModel: filetree.NewCommitFileTreeViewModel(make([]*models.CommitFile, 0), gui.Log, showTree),
|
CommitFileTreeViewModel: filetree.NewCommitFileTreeViewModel(make([]*models.CommitFile, 0), gui.Log, showTree),
|
||||||
Commits: make([]*models.Commit, 0),
|
Commits: make([]*models.Commit, 0),
|
||||||
FilteredReflogCommits: make([]*models.Commit, 0),
|
FilteredReflogCommits: make([]*models.Commit, 0),
|
||||||
@ -586,7 +585,7 @@ func (gui *Gui) setControllers() {
|
|||||||
bisect: controllers.NewBisectHelper(controllerCommon, gui.git),
|
bisect: controllers.NewBisectHelper(controllerCommon, gui.git),
|
||||||
suggestions: NewSuggestionsHelper(controllerCommon, getState, gui.refreshSuggestions),
|
suggestions: NewSuggestionsHelper(controllerCommon, getState, gui.refreshSuggestions),
|
||||||
files: NewFilesHelper(controllerCommon, gui.git, osCommand),
|
files: NewFilesHelper(controllerCommon, gui.git, osCommand),
|
||||||
workingTree: NewWorkingTreeHelper(func() *filetree.FileTreeViewModel { return gui.State.FileTreeViewModel }),
|
workingTree: NewWorkingTreeHelper(func() []*models.File { return gui.State.Files }),
|
||||||
tags: controllers.NewTagsHelper(controllerCommon, gui.git),
|
tags: controllers.NewTagsHelper(controllerCommon, gui.git),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,12 +608,12 @@ func (gui *Gui) setControllers() {
|
|||||||
),
|
),
|
||||||
Files: controllers.NewFilesController(
|
Files: controllers.NewFilesController(
|
||||||
controllerCommon,
|
controllerCommon,
|
||||||
func() types.IListContext { return gui.State.Contexts.Files },
|
func() *context.WorkingTreeContext { return gui.State.Contexts.Files },
|
||||||
|
func() []*models.File { return gui.State.Files },
|
||||||
gui.git,
|
gui.git,
|
||||||
osCommand,
|
osCommand,
|
||||||
gui.getSelectedFileNode,
|
gui.getSelectedFileNode,
|
||||||
getContexts,
|
getContexts,
|
||||||
func() *filetree.FileTreeViewModel { return gui.State.FileTreeViewModel },
|
|
||||||
gui.enterSubmodule,
|
gui.enterSubmodule,
|
||||||
func() []*models.SubmoduleConfig { return gui.State.Submodules },
|
func() []*models.SubmoduleConfig { return gui.State.Submodules },
|
||||||
gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage }),
|
gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage }),
|
||||||
|
@ -28,21 +28,12 @@ func (gui *Gui) menuListContext() types.IListContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) filesListContext() types.IListContext {
|
func (gui *Gui) filesListContext() *context.WorkingTreeContext {
|
||||||
return &ListContext{
|
return context.NewWorkingTreeContext(
|
||||||
BaseContext: context.NewBaseContext(context.NewBaseContextOpts{
|
func() []*models.File { return gui.State.Files },
|
||||||
ViewName: "files",
|
func() *gocui.View { return gui.Views.Files },
|
||||||
WindowName: "files",
|
func(startIdx int, length int) [][]string {
|
||||||
Key: context.FILES_CONTEXT_KEY,
|
lines := presentation.RenderFileTree(gui.State.Contexts.Files.FileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.State.Submodules)
|
||||||
Kind: types.SIDE_CONTEXT,
|
|
||||||
}),
|
|
||||||
GetItemsLength: func() int { return gui.State.FileTreeViewModel.GetItemsLength() },
|
|
||||||
OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Files },
|
|
||||||
OnFocus: OnFocusWrapper(gui.onFocusFile),
|
|
||||||
OnRenderToMain: OnFocusWrapper(gui.withDiffModeCheck(gui.filesRenderToMain)),
|
|
||||||
Gui: gui,
|
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
|
||||||
lines := presentation.RenderFileTree(gui.State.FileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.State.Submodules)
|
|
||||||
mappedLines := make([][]string, len(lines))
|
mappedLines := make([][]string, len(lines))
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
mappedLines[i] = []string{line}
|
mappedLines[i] = []string{line}
|
||||||
@ -50,11 +41,11 @@ func (gui *Gui) filesListContext() types.IListContext {
|
|||||||
|
|
||||||
return mappedLines
|
return mappedLines
|
||||||
},
|
},
|
||||||
SelectedItem: func() (types.ListItem, bool) {
|
OnFocusWrapper(gui.onFocusFile),
|
||||||
item := gui.getSelectedFileNode()
|
OnFocusWrapper(gui.withDiffModeCheck(gui.filesRenderToMain)),
|
||||||
return item, item != nil
|
nil,
|
||||||
},
|
gui.c,
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) branchesListContext() types.IListContext {
|
func (gui *Gui) branchesListContext() types.IListContext {
|
||||||
|
@ -69,7 +69,7 @@ M file1
|
|||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
s := s
|
s := s
|
||||||
t.Run(s.name, func(t *testing.T) {
|
t.Run(s.name, func(t *testing.T) {
|
||||||
viewModel := filetree.NewFileTreeViewModel(s.files, utils.NewDummyLog(), true)
|
viewModel := filetree.NewFileTreeViewModel(func() []*models.File { return s.files }, utils.NewDummyLog(), true)
|
||||||
for _, path := range s.collapsedPaths {
|
for _, path := range s.collapsedPaths {
|
||||||
viewModel.ToggleCollapsed(path)
|
viewModel.ToggleCollapsed(path)
|
||||||
}
|
}
|
||||||
|
@ -371,7 +371,8 @@ func (gui *Gui) refreshStateFiles() error {
|
|||||||
|
|
||||||
selectedNode := gui.getSelectedFileNode()
|
selectedNode := gui.getSelectedFileNode()
|
||||||
|
|
||||||
prevNodes := gui.State.FileTreeViewModel.GetAllItems()
|
fileTreeViewModel := state.Contexts.Files.WorkingTreeViewModal
|
||||||
|
prevNodes := fileTreeViewModel.GetAllItems()
|
||||||
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx
|
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx
|
||||||
|
|
||||||
// If git thinks any of our files have inline merge conflicts, but they actually don't,
|
// If git thinks any of our files have inline merge conflicts, but they actually don't,
|
||||||
@ -383,7 +384,7 @@ func (gui *Gui) refreshStateFiles() error {
|
|||||||
// we call git status again.
|
// we call git status again.
|
||||||
pathsToStage := []string{}
|
pathsToStage := []string{}
|
||||||
prevConflictFileCount := 0
|
prevConflictFileCount := 0
|
||||||
for _, file := range state.FileTreeViewModel.GetAllFiles() {
|
for _, file := range gui.State.Files {
|
||||||
if file.HasMergeConflicts {
|
if file.HasMergeConflicts {
|
||||||
prevConflictFileCount++
|
prevConflictFileCount++
|
||||||
}
|
}
|
||||||
@ -419,10 +420,10 @@ func (gui *Gui) refreshStateFiles() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for when you stage the old file of a rename and the new file is in a collapsed dir
|
// for when you stage the old file of a rename and the new file is in a collapsed dir
|
||||||
state.FileTreeViewModel.RWMutex.Lock()
|
fileTreeViewModel.RWMutex.Lock()
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if selectedNode != nil && selectedNode.Path != "" && file.PreviousName == selectedNode.Path {
|
if selectedNode != nil && selectedNode.Path != "" && file.PreviousName == selectedNode.Path {
|
||||||
state.FileTreeViewModel.ExpandToPath(file.Name)
|
fileTreeViewModel.ExpandToPath(file.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,44 +433,68 @@ func (gui *Gui) refreshStateFiles() error {
|
|||||||
// extra state here to see if the user's set the filter themselves we can do that, but
|
// extra state here to see if the user's set the filter themselves we can do that, but
|
||||||
// I'd prefer to maintain as little state as possible.
|
// I'd prefer to maintain as little state as possible.
|
||||||
if conflictFileCount > 0 {
|
if conflictFileCount > 0 {
|
||||||
if state.FileTreeViewModel.GetFilter() == filetree.DisplayAll {
|
if fileTreeViewModel.GetFilter() == filetree.DisplayAll {
|
||||||
state.FileTreeViewModel.SetFilter(filetree.DisplayConflicted)
|
fileTreeViewModel.SetFilter(filetree.DisplayConflicted)
|
||||||
}
|
}
|
||||||
} else if state.FileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
} else if fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
||||||
state.FileTreeViewModel.SetFilter(filetree.DisplayAll)
|
fileTreeViewModel.SetFilter(filetree.DisplayAll)
|
||||||
}
|
}
|
||||||
|
|
||||||
state.FileTreeViewModel.SetFiles(files)
|
state.Files = files
|
||||||
state.FileTreeViewModel.RWMutex.Unlock()
|
fileTreeViewModel.SetTree()
|
||||||
|
fileTreeViewModel.RWMutex.Unlock()
|
||||||
|
|
||||||
if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil {
|
if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedNode != nil {
|
if selectedNode != nil {
|
||||||
newIdx := gui.findNewSelectedIdx(prevNodes[prevSelectedLineIdx:], state.FileTreeViewModel.GetAllItems())
|
newIdx := gui.findNewSelectedIdx(prevNodes[prevSelectedLineIdx:], fileTreeViewModel.GetAllItems())
|
||||||
if newIdx != -1 && newIdx != prevSelectedLineIdx {
|
if newIdx != -1 && newIdx != prevSelectedLineIdx {
|
||||||
newNode := state.FileTreeViewModel.GetItemAtIndex(newIdx)
|
|
||||||
// when not in tree mode, we show merge conflict files at the top, so you
|
|
||||||
// can work through them one by one without having to sift through a large
|
|
||||||
// set of files. If you have just fixed the merge conflicts of a file, we
|
|
||||||
// actually don't want to jump to that file's new position, because that
|
|
||||||
// file will now be ages away amidst the other files without merge
|
|
||||||
// conflicts: the user in this case would rather work on the next file
|
|
||||||
// with merge conflicts, which will have moved up to fill the gap left by
|
|
||||||
// the last file, meaning the cursor doesn't need to move at all.
|
|
||||||
leaveCursor := !state.FileTreeViewModel.InTreeMode() && newNode != nil &&
|
|
||||||
selectedNode.File != nil && selectedNode.File.HasMergeConflicts &&
|
|
||||||
newNode.File != nil && !newNode.File.HasMergeConflicts
|
|
||||||
|
|
||||||
if !leaveCursor {
|
|
||||||
state.Panels.Files.SelectedLineIdx = newIdx
|
state.Panels.Files.SelectedLineIdx = newIdx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui.refreshSelectedLine(state.Panels.Files, fileTreeViewModel.GetItemsLength())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's try to find our file again and move the cursor to that.
|
||||||
|
// If we can't find our file, it was probably just removed by the user. In that
|
||||||
|
// case, we go looking for where the next file has been moved to. Given that the
|
||||||
|
// user could have removed a whole directory, we continue iterating through the old
|
||||||
|
// nodes until we find one that exists in the new set of nodes, then move the cursor
|
||||||
|
// to that.
|
||||||
|
// prevNodes starts from our previously selected node because we don't need to consider anything above that
|
||||||
|
func (gui *Gui) findNewSelectedIdx(prevNodes []*filetree.FileNode, currNodes []*filetree.FileNode) int {
|
||||||
|
getPaths := func(node *filetree.FileNode) []string {
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if node.File != nil && node.File.IsRename() {
|
||||||
|
return node.File.Names()
|
||||||
|
} else {
|
||||||
|
return []string{node.Path}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.refreshSelectedLine(state.Panels.Files, state.FileTreeViewModel.GetItemsLength())
|
for _, prevNode := range prevNodes {
|
||||||
return nil
|
selectedPaths := getPaths(prevNode)
|
||||||
|
|
||||||
|
for idx, node := range currNodes {
|
||||||
|
paths := getPaths(node)
|
||||||
|
|
||||||
|
// If you started off with a rename selected, and now it's broken in two, we want you to jump to the new file, not the old file.
|
||||||
|
// This is because the new should be in the same position as the rename was meaning less cursor jumping
|
||||||
|
foundOldFileInRename := prevNode.File != nil && prevNode.File.IsRename() && node.Path == prevNode.File.PreviousName
|
||||||
|
foundNode := utils.StringArraysOverlap(paths, selectedPaths) && !foundOldFileInRename
|
||||||
|
if foundNode {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// the reflogs panel is the only panel where we cache data, in that we only
|
// the reflogs panel is the only panel where we cache data, in that we only
|
||||||
|
@ -2,21 +2,20 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorkingTreeHelper struct {
|
type WorkingTreeHelper struct {
|
||||||
getFileTreeViewModel func() *filetree.FileTreeViewModel
|
getFiles func() []*models.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWorkingTreeHelper(getFileTreeViewModel func() *filetree.FileTreeViewModel) *WorkingTreeHelper {
|
func NewWorkingTreeHelper(getFiles func() []*models.File) *WorkingTreeHelper {
|
||||||
return &WorkingTreeHelper{
|
return &WorkingTreeHelper{
|
||||||
getFileTreeViewModel: getFileTreeViewModel,
|
getFiles: getFiles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorkingTreeHelper) AnyStagedFiles() bool {
|
func (self *WorkingTreeHelper) AnyStagedFiles() bool {
|
||||||
files := self.getFileTreeViewModel().GetAllFiles()
|
files := self.getFiles()
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if file.HasStagedChanges {
|
if file.HasStagedChanges {
|
||||||
return true
|
return true
|
||||||
@ -26,7 +25,7 @@ func (self *WorkingTreeHelper) AnyStagedFiles() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorkingTreeHelper) AnyTrackedFiles() bool {
|
func (self *WorkingTreeHelper) AnyTrackedFiles() bool {
|
||||||
files := self.getFileTreeViewModel().GetAllFiles()
|
files := self.getFiles()
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if file.Tracked {
|
if file.Tracked {
|
||||||
return true
|
return true
|
||||||
@ -40,7 +39,7 @@ func (self *WorkingTreeHelper) IsWorkingTreeDirty() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorkingTreeHelper) FileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
|
func (self *WorkingTreeHelper) FileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
|
||||||
for _, file := range self.getFileTreeViewModel().GetAllFiles() {
|
for _, file := range self.getFiles() {
|
||||||
if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
|
if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user