From 332a3c4cbfd263c34d5f53dd971701d2ca69ab4e Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 31 Mar 2021 22:08:55 +1100 Subject: [PATCH] file tree for commit files --- pkg/commands/loading_commit_files.go | 1 - pkg/commands/models/commit_file.go | 4 +- pkg/gui/commit_files_panel.go | 91 +++++++++++----- pkg/gui/custom_commands.go | 54 +++++----- pkg/gui/files_panel.go | 9 +- pkg/gui/filetree/build_tree.go | 47 ++++++++ pkg/gui/filetree/collapsed_paths.go | 25 +++++ .../filetree/commit_file_change_manager.go | 95 ++++++++++++++++ pkg/gui/filetree/constants.go | 9 ++ pkg/gui/filetree/file_change_manager.go | 101 ++++-------------- pkg/gui/filetree/inode.go | 57 +++++++++- pkg/gui/filtering_menu_panel.go | 8 +- pkg/gui/gui.go | 23 ++-- pkg/gui/line_by_line_panel.go | 4 +- pkg/gui/list_context.go | 18 +++- pkg/gui/patch_building_panel.go | 16 +-- pkg/gui/presentation/commit_files.go | 44 +++----- 17 files changed, 406 insertions(+), 200 deletions(-) create mode 100644 pkg/gui/filetree/collapsed_paths.go create mode 100644 pkg/gui/filetree/commit_file_change_manager.go create mode 100644 pkg/gui/filetree/constants.go diff --git a/pkg/commands/loading_commit_files.go b/pkg/commands/loading_commit_files.go index 2c71cc068..1b5dc02d4 100644 --- a/pkg/commands/loading_commit_files.go +++ b/pkg/commands/loading_commit_files.go @@ -38,7 +38,6 @@ func (c *GitCommand) getCommitFilesFromFilenames(filenames string, parent string } commitFiles = append(commitFiles, &models.CommitFile{ - Parent: parent, Name: name, ChangeStatus: changeStatus, PatchStatus: status, diff --git a/pkg/commands/models/commit_file.go b/pkg/commands/models/commit_file.go index 17faf0142..57a766a46 100644 --- a/pkg/commands/models/commit_file.go +++ b/pkg/commands/models/commit_file.go @@ -2,9 +2,7 @@ package models // CommitFile : A git commit file type CommitFile struct { - // Parent is the identifier of the parent object e.g. a commit SHA if this commit file is for a commit, or a stash entry ref like 'stash@{1}' - Parent string - Name string + Name string // PatchStatus tells us whether the file has been wholly or partially added to a patch. We might want to pull this logic up into the gui package and make it a map like we do with cherry picked commits PatchStatus int // one of 'WHOLE' 'PART' 'NONE' diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index 98a8d98a9..cd4019dd5 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -3,30 +3,49 @@ package gui import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/filetree" ) -func (gui *Gui) getSelectedCommitFile() *models.CommitFile { +// todo: rename to getSelectedCommitFileChangeNode, or decide to remove the change part in the context of files +func (gui *Gui) getSelectedCommitFileNode() *filetree.CommitFileChangeNode { selectedLine := gui.State.Panels.CommitFiles.SelectedLineIdx - if selectedLine == -1 || selectedLine > len(gui.State.CommitFiles)-1 { + if selectedLine == -1 || selectedLine > gui.State.CommitFileChangeManager.GetItemsLength()-1 { return nil } - return gui.State.CommitFiles[selectedLine] + return gui.State.CommitFileChangeManager.GetItemAtIndex(selectedLine) +} + +// todo: rename to getSelectedCommitFileChange +func (gui *Gui) getSelectedCommitFile() *models.CommitFile { + node := gui.getSelectedCommitFileNode() + if node == nil { + return nil + } + return node.File +} + +func (gui *Gui) getSelectedCommitFilePath() string { + node := gui.getSelectedCommitFileNode() + if node == nil { + return "" + } + return node.GetPath() } func (gui *Gui) handleCommitFileSelect() error { gui.escapeLineByLinePanel() - commitFile := gui.getSelectedCommitFile() - if commitFile == nil { + node := gui.getSelectedCommitFileNode() + if node == nil { return nil } - to := commitFile.Parent + to := gui.State.CommitFileChangeManager.GetParent() from, reverse := gui.getFromAndReverseArgsForDiff(to) cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowFileDiffCmdStr(from, to, reverse, commitFile.Name, false), + gui.GitCommand.ShowFileDiffCmdStr(from, to, reverse, node.GetPath(), false), ) task := gui.createRunPtyTask(cmd) @@ -40,12 +59,13 @@ func (gui *Gui) handleCommitFileSelect() error { } func (gui *Gui) handleCheckoutCommitFile(g *gocui.Gui, v *gocui.View) error { - file := gui.getSelectedCommitFile() - if file == nil { + node := gui.getSelectedCommitFileNode() + if node == nil { return nil } - if err := gui.GitCommand.CheckoutFile(file.Parent, file.Name); err != nil { + // TODO: verify this works for directories + if err := gui.GitCommand.CheckoutFile(gui.State.CommitFileChangeManager.GetParent(), node.GetPath()); err != nil { return gui.surfaceError(err) } @@ -57,7 +77,7 @@ func (gui *Gui) handleDiscardOldFileChange(g *gocui.Gui, v *gocui.View) error { return err } - fileName := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLineIdx].Name + fileName := gui.getSelectedCommitFileName() return gui.ask(askOpts{ title: gui.Tr.DiscardFileChangesTitle, @@ -88,35 +108,37 @@ func (gui *Gui) refreshCommitFilesView() error { if err != nil { return gui.surfaceError(err) } - gui.State.CommitFiles = files + gui.State.CommitFileChangeManager.SetFiles(files, to) return gui.postRefreshUpdate(gui.Contexts.CommitFiles.Context) } func (gui *Gui) handleOpenOldCommitFile(g *gocui.Gui, v *gocui.View) error { - file := gui.getSelectedCommitFile() - if file == nil { + node := gui.getSelectedCommitFileNode() + if node == nil { return nil } - return gui.openFile(file.Name) + return gui.openFile(node.GetPath()) } func (gui *Gui) handleEditCommitFile(g *gocui.Gui, v *gocui.View) error { - file := gui.getSelectedCommitFile() - if file == nil { + node := gui.getSelectedCommitFileNode() + if node == nil { return nil } - return gui.editFile(file.Name) + return gui.editFile(node.GetPath()) } func (gui *Gui) handleToggleFileForPatch(g *gocui.Gui, v *gocui.View) error { - commitFile := gui.getSelectedCommitFile() - if commitFile == nil { + node := gui.getSelectedCommitFileNode() + if node == nil { return nil } + // TODO: if file is nil, toggle all leaves underneath on/off + toggleTheFile := func() error { if !gui.GitCommand.PatchManager.Active() { if err := gui.startPatchManager(); err != nil { @@ -124,7 +146,7 @@ func (gui *Gui) handleToggleFileForPatch(g *gocui.Gui, v *gocui.View) error { } } - if err := gui.GitCommand.PatchManager.ToggleFileWhole(commitFile.Name); err != nil { + if err := gui.GitCommand.PatchManager.ToggleFileWhole(node.GetPath()); err != nil { return err } @@ -135,7 +157,7 @@ func (gui *Gui) handleToggleFileForPatch(g *gocui.Gui, v *gocui.View) error { return gui.refreshCommitFilesView() } - if gui.GitCommand.PatchManager.Active() && gui.GitCommand.PatchManager.To != commitFile.Parent { + if gui.GitCommand.PatchManager.Active() && gui.GitCommand.PatchManager.To != gui.State.CommitFileChangeManager.GetParent() { return gui.ask(askOpts{ title: gui.Tr.DiscardPatch, prompt: gui.Tr.DiscardPatchConfirm, @@ -164,11 +186,15 @@ func (gui *Gui) handleEnterCommitFile(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) enterCommitFile(selectedLineIdx int) error { - commitFile := gui.getSelectedCommitFile() - if commitFile == nil { + node := gui.getSelectedCommitFileNode() + if node == nil { return nil } + if node.File == nil { + return gui.handleToggleCommitFileDirCollapsed() + } + enterTheFile := func(selectedLineIdx int) error { if !gui.GitCommand.PatchManager.Active() { if err := gui.startPatchManager(); err != nil { @@ -182,7 +208,7 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error { return gui.handleRefreshPatchBuildingPanel(selectedLineIdx) } - if gui.GitCommand.PatchManager.Active() && gui.GitCommand.PatchManager.To != commitFile.Parent { + if gui.GitCommand.PatchManager.Active() && gui.GitCommand.PatchManager.To != gui.State.CommitFileChangeManager.GetParent() { return gui.ask(askOpts{ title: gui.Tr.DiscardPatch, prompt: gui.Tr.DiscardPatchConfirm, @@ -200,6 +226,21 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error { return enterTheFile(selectedLineIdx) } +func (gui *Gui) handleToggleCommitFileDirCollapsed() error { + node := gui.getSelectedCommitFileNode() + if node == nil { + return nil + } + + gui.State.CommitFileChangeManager.ToggleCollapsed(node.GetPath()) + + if err := gui.postRefreshUpdate(gui.Contexts.CommitFiles.Context); err != nil { + gui.Log.Error(err) + } + + return nil +} + func (gui *Gui) switchToCommitFilesContext(refName string, canRebase bool, context Context, windowName string) error { // sometimes the commitFiles view is already shown in another window, so we need to ensure that window // no longer considers the commitFiles view as its main view. diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go index 670d78df6..d3fe62840 100644 --- a/pkg/gui/custom_commands.go +++ b/pkg/gui/custom_commands.go @@ -12,36 +12,38 @@ import ( ) type CustomCommandObjects struct { - SelectedLocalCommit *models.Commit - SelectedReflogCommit *models.Commit - SelectedSubCommit *models.Commit - SelectedFile *models.File - SelectedPath string - SelectedLocalBranch *models.Branch - SelectedRemoteBranch *models.RemoteBranch - SelectedRemote *models.Remote - SelectedTag *models.Tag - SelectedStashEntry *models.StashEntry - SelectedCommitFile *models.CommitFile - CheckedOutBranch *models.Branch - PromptResponses []string + SelectedLocalCommit *models.Commit + SelectedReflogCommit *models.Commit + SelectedSubCommit *models.Commit + SelectedFile *models.File + SelectedPath string + SelectedLocalBranch *models.Branch + SelectedRemoteBranch *models.RemoteBranch + SelectedRemote *models.Remote + SelectedTag *models.Tag + SelectedStashEntry *models.StashEntry + SelectedCommitFile *models.CommitFile + SelectedCommitFilePath string + CheckedOutBranch *models.Branch + PromptResponses []string } func (gui *Gui) resolveTemplate(templateStr string, promptResponses []string) (string, error) { objects := CustomCommandObjects{ - SelectedFile: gui.getSelectedFile(), - SelectedPath: gui.getSelectedPath(), - SelectedLocalCommit: gui.getSelectedLocalCommit(), - SelectedReflogCommit: gui.getSelectedReflogCommit(), - SelectedLocalBranch: gui.getSelectedBranch(), - SelectedRemoteBranch: gui.getSelectedRemoteBranch(), - SelectedRemote: gui.getSelectedRemote(), - SelectedTag: gui.getSelectedTag(), - SelectedStashEntry: gui.getSelectedStashEntry(), - SelectedCommitFile: gui.getSelectedCommitFile(), - SelectedSubCommit: gui.getSelectedSubCommit(), - CheckedOutBranch: gui.currentBranch(), - PromptResponses: promptResponses, + SelectedFile: gui.getSelectedFile(), + SelectedPath: gui.getSelectedPath(), + SelectedLocalCommit: gui.getSelectedLocalCommit(), + SelectedReflogCommit: gui.getSelectedReflogCommit(), + SelectedLocalBranch: gui.getSelectedBranch(), + SelectedRemoteBranch: gui.getSelectedRemoteBranch(), + SelectedRemote: gui.getSelectedRemote(), + SelectedTag: gui.getSelectedTag(), + SelectedStashEntry: gui.getSelectedStashEntry(), + SelectedCommitFile: gui.getSelectedCommitFile(), + SelectedCommitFilePath: gui.getSelectedCommitFilePath(), + SelectedSubCommit: gui.getSelectedSubCommit(), + CheckedOutBranch: gui.currentBranch(), + PromptResponses: promptResponses, } return utils.ResolveTemplate(templateStr, objects) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index b1cb66f82..dce047470 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -32,12 +32,7 @@ func (gui *Gui) getSelectedFileChangeNode() *filetree.FileChangeNode { } func (gui *Gui) getSelectedFile() *models.File { - selectedLine := gui.State.Panels.Files.SelectedLineIdx - if selectedLine == -1 { - return nil - } - - node := gui.State.FileChangeManager.GetItemAtIndex(selectedLine) + node := gui.getSelectedFileChangeNode() if node == nil { return nil } @@ -844,7 +839,7 @@ func (gui *Gui) handleToggleDirCollapsed() error { return nil } - gui.State.FileChangeManager.ToggleCollapsed(node) + gui.State.FileChangeManager.ToggleCollapsed(node.GetPath()) if err := gui.postRefreshUpdate(gui.Contexts.Files.Context); err != nil { gui.Log.Error(err) diff --git a/pkg/gui/filetree/build_tree.go b/pkg/gui/filetree/build_tree.go index 267bb22dd..d6fe9b98d 100644 --- a/pkg/gui/filetree/build_tree.go +++ b/pkg/gui/filetree/build_tree.go @@ -49,6 +49,53 @@ func BuildTreeFromFiles(files []*models.File) *FileChangeNode { return root } +func BuildFlatTreeFromCommitFiles(files []*models.CommitFile) *CommitFileChangeNode { + rootAux := BuildTreeFromCommitFiles(files) + sortedFiles := rootAux.GetLeaves() + + return &CommitFileChangeNode{Children: sortedFiles} +} + +func BuildTreeFromCommitFiles(files []*models.CommitFile) *CommitFileChangeNode { + root := &CommitFileChangeNode{} + + var curr *CommitFileChangeNode + for _, file := range files { + split := strings.Split(file.Name, string(os.PathSeparator)) + curr = root + outer: + for i := range split { + var setFile *models.CommitFile + isFile := i == len(split)-1 + if isFile { + setFile = file + } + + path := filepath.Join(split[:i+1]...) + + for _, existingChild := range curr.Children { + if existingChild.Path == path { + curr = existingChild + continue outer + } + } + + newChild := &CommitFileChangeNode{ + Path: path, + File: setFile, + } + curr.Children = append(curr.Children, newChild) + + curr = newChild + } + } + + root.Sort() + root.Compress() + + return root +} + func BuildFlatTreeFromFiles(files []*models.File) *FileChangeNode { rootAux := BuildTreeFromFiles(files) sortedFiles := rootAux.GetLeaves() diff --git a/pkg/gui/filetree/collapsed_paths.go b/pkg/gui/filetree/collapsed_paths.go new file mode 100644 index 000000000..60860414f --- /dev/null +++ b/pkg/gui/filetree/collapsed_paths.go @@ -0,0 +1,25 @@ +package filetree + +import ( + "os" + "strings" +) + +type CollapsedPaths map[string]bool + +func (cp CollapsedPaths) ExpandToPath(path string) { + // need every directory along the way + split := strings.Split(path, string(os.PathSeparator)) + for i := range split { + dir := strings.Join(split[0:i+1], string(os.PathSeparator)) + cp[dir] = false + } +} + +func (cp CollapsedPaths) IsCollapsed(path string) bool { + return cp[path] +} + +func (cp CollapsedPaths) ToggleCollapsed(path string) { + cp[path] = !cp[path] +} diff --git a/pkg/gui/filetree/commit_file_change_manager.go b/pkg/gui/filetree/commit_file_change_manager.go new file mode 100644 index 000000000..d6b70a2bd --- /dev/null +++ b/pkg/gui/filetree/commit_file_change_manager.go @@ -0,0 +1,95 @@ +package filetree + +import ( + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" + "github.com/sirupsen/logrus" +) + +type CommitFileChangeManager struct { + files []*models.CommitFile + tree *CommitFileChangeNode + showTree bool + log *logrus.Entry + collapsedPaths CollapsedPaths + // parent is the identifier of the parent object e.g. a commit SHA if this commit file is for a commit, or a stash entry ref like 'stash@{1}' + parent string +} + +func (m *CommitFileChangeManager) GetParent() string { + return m.parent +} + +func NewCommitFileChangeManager(files []*models.CommitFile, log *logrus.Entry, showTree bool) *CommitFileChangeManager { + return &CommitFileChangeManager{ + files: files, + log: log, + showTree: showTree, + collapsedPaths: CollapsedPaths{}, + } +} + +func (m *CommitFileChangeManager) ExpandToPath(path string) { + m.collapsedPaths.ExpandToPath(path) +} + +func (m *CommitFileChangeManager) ToggleShowTree() { + m.showTree = !m.showTree + m.SetTree() +} + +func (m *CommitFileChangeManager) GetItemAtIndex(index int) *CommitFileChangeNode { + // need to traverse the three depth first until we get to the index. + return m.tree.GetNodeAtIndex(index+1, m.collapsedPaths) // ignoring root +} + +func (m *CommitFileChangeManager) GetIndexForPath(path string) (int, bool) { + index, found := m.tree.GetIndexForPath(path, m.collapsedPaths) + return index - 1, found +} + +func (m *CommitFileChangeManager) GetAllItems() []*CommitFileChangeNode { + if m.tree == nil { + return nil + } + + return m.tree.Flatten(m.collapsedPaths)[1:] // ignoring root +} + +func (m *CommitFileChangeManager) GetItemsLength() int { + return m.tree.Size(m.collapsedPaths) - 1 // ignoring root +} + +func (m *CommitFileChangeManager) GetAllFiles() []*models.CommitFile { + return m.files +} + +func (m *CommitFileChangeManager) SetFiles(files []*models.CommitFile, parent string) { + m.files = files + m.parent = parent + + m.SetTree() +} + +func (m *CommitFileChangeManager) SetTree() { + if m.showTree { + m.tree = BuildTreeFromCommitFiles(m.files) + } else { + m.tree = BuildFlatTreeFromCommitFiles(m.files) + } +} + +func (m *CommitFileChangeManager) IsCollapsed(path string) bool { + return m.collapsedPaths.IsCollapsed(path) +} + +func (m *CommitFileChangeManager) ToggleCollapsed(path string) { + m.collapsedPaths.ToggleCollapsed(path) +} + +func (m *CommitFileChangeManager) Render(diffName string) []string { + return renderAux(m.tree, m.collapsedPaths, "", -1, func(n INode, depth int) string { + castN := n.(*CommitFileChangeNode) + return presentation.GetCommitFileLine(castN.NameAtDepth(depth), diffName, castN.File) + }) +} diff --git a/pkg/gui/filetree/constants.go b/pkg/gui/filetree/constants.go new file mode 100644 index 000000000..d510650e2 --- /dev/null +++ b/pkg/gui/filetree/constants.go @@ -0,0 +1,9 @@ +package filetree + +const EXPANDED_ARROW = "▼" +const COLLAPSED_ARROW = "►" + +const INNER_ITEM = "├─ " +const LAST_ITEM = "└─ " +const NESTED = "│ " +const NOTHING = " " diff --git a/pkg/gui/filetree/file_change_manager.go b/pkg/gui/filetree/file_change_manager.go index 30bd7cf1e..b663e127b 100644 --- a/pkg/gui/filetree/file_change_manager.go +++ b/pkg/gui/filetree/file_change_manager.go @@ -1,29 +1,17 @@ package filetree import ( - "fmt" - "os" - "strings" - "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/sirupsen/logrus" ) -const EXPANDED_ARROW = "▼" -const COLLAPSED_ARROW = "►" - -const INNER_ITEM = "├─ " -const LAST_ITEM = "└─ " -const NESTED = "│ " -const NOTHING = " " - type FileChangeManager struct { files []*models.File tree *FileChangeNode showTree bool log *logrus.Entry - collapsedPaths map[string]bool + collapsedPaths CollapsedPaths } func NewFileChangeManager(files []*models.File, log *logrus.Entry, showTree bool) *FileChangeManager { @@ -31,10 +19,14 @@ func NewFileChangeManager(files []*models.File, log *logrus.Entry, showTree bool files: files, log: log, showTree: showTree, - collapsedPaths: map[string]bool{}, + collapsedPaths: CollapsedPaths{}, } } +func (m *FileChangeManager) ExpandToPath(path string) { + m.collapsedPaths.ExpandToPath(path) +} + func (m *FileChangeManager) ToggleShowTree() { m.showTree = !m.showTree m.SetTree() @@ -80,74 +72,17 @@ func (m *FileChangeManager) SetTree() { } } +func (m *FileChangeManager) IsCollapsed(path string) bool { + return m.collapsedPaths.IsCollapsed(path) +} + +func (m *FileChangeManager) ToggleCollapsed(path string) { + m.collapsedPaths.ToggleCollapsed(path) +} + func (m *FileChangeManager) Render(diffName string, submoduleConfigs []*models.SubmoduleConfig) []string { - return m.renderAux(m.tree, "", -1, diffName, submoduleConfigs) -} - -func (m *FileChangeManager) IsCollapsed(s *FileChangeNode) bool { - return m.collapsedPaths[s.GetPath()] -} - -func (m *FileChangeManager) ToggleCollapsed(s *FileChangeNode) { - m.collapsedPaths[s.GetPath()] = !m.collapsedPaths[s.GetPath()] -} - -func (m *FileChangeManager) renderAux(s *FileChangeNode, prefix string, depth int, diffName string, submoduleConfigs []*models.SubmoduleConfig) []string { - isRoot := depth == -1 - if s == nil { - return []string{} - } - - getLine := func() string { - return prefix + presentation.GetFileLine(s.GetHasUnstagedChanges(), s.GetHasStagedChanges(), s.NameAtDepth(depth), diffName, submoduleConfigs, s.File) - } - - if s.IsLeaf() { - if isRoot { - return []string{} - } - return []string{getLine()} - } - - if m.IsCollapsed(s) { - return []string{fmt.Sprintf("%s %s", getLine(), COLLAPSED_ARROW)} - } - - arr := []string{} - if !isRoot { - arr = append(arr, fmt.Sprintf("%s %s", getLine(), EXPANDED_ARROW)) - } - - newPrefix := prefix - if strings.HasSuffix(prefix, LAST_ITEM) { - newPrefix = strings.TrimSuffix(prefix, LAST_ITEM) + NOTHING - } else if strings.HasSuffix(prefix, INNER_ITEM) { - newPrefix = strings.TrimSuffix(prefix, INNER_ITEM) + NESTED - } - - for i, child := range s.Children { - isLast := i == len(s.Children)-1 - - var childPrefix string - if isRoot { - childPrefix = newPrefix - } else if isLast { - childPrefix = newPrefix + LAST_ITEM - } else { - childPrefix = newPrefix + INNER_ITEM - } - - arr = append(arr, m.renderAux(child, childPrefix, depth+1+s.CompressionLevel, diffName, submoduleConfigs)...) - } - - return arr -} - -func (m *FileChangeManager) ExpandToPath(path string) { - // need every directory along the way - split := strings.Split(path, string(os.PathSeparator)) - for i := range split { - dir := strings.Join(split[0:i+1], string(os.PathSeparator)) - m.collapsedPaths[dir] = false - } + return renderAux(m.tree, m.collapsedPaths, "", -1, func(n INode, depth int) string { + castN := n.(*FileChangeNode) + return presentation.GetFileLine(castN.GetHasUnstagedChanges(), castN.GetHasStagedChanges(), castN.NameAtDepth(depth), diffName, submoduleConfigs, castN.File) + }) } diff --git a/pkg/gui/filetree/inode.go b/pkg/gui/filetree/inode.go index b3f9c73ee..bbd1c3fb6 100644 --- a/pkg/gui/filetree/inode.go +++ b/pkg/gui/filetree/inode.go @@ -1,6 +1,10 @@ package filetree -import "sort" +import ( + "fmt" + "sort" + "strings" +) type INode interface { IsLeaf() bool @@ -194,3 +198,54 @@ func getLeaves(node INode) []INode { return output } + +func renderAux(s INode, collapsedPaths CollapsedPaths, prefix string, depth int, renderLine func(INode, int) string) []string { + isRoot := depth == -1 + if s == nil { + return []string{} + } + + renderLineWithPrefix := func() string { + return prefix + renderLine(s, depth) + } + + if s.IsLeaf() { + if isRoot { + return []string{} + } + return []string{renderLineWithPrefix()} + } + + if collapsedPaths.IsCollapsed(s.GetPath()) { + return []string{fmt.Sprintf("%s %s", renderLineWithPrefix(), COLLAPSED_ARROW)} + } + + arr := []string{} + if !isRoot { + arr = append(arr, fmt.Sprintf("%s %s", renderLineWithPrefix(), EXPANDED_ARROW)) + } + + newPrefix := prefix + if strings.HasSuffix(prefix, LAST_ITEM) { + newPrefix = strings.TrimSuffix(prefix, LAST_ITEM) + NOTHING + } else if strings.HasSuffix(prefix, INNER_ITEM) { + newPrefix = strings.TrimSuffix(prefix, INNER_ITEM) + NESTED + } + + for i, child := range s.GetChildren() { + isLast := i == len(s.GetChildren())-1 + + var childPrefix string + if isRoot { + childPrefix = newPrefix + } else if isLast { + childPrefix = newPrefix + LAST_ITEM + } else { + childPrefix = newPrefix + INNER_ITEM + } + + arr = append(arr, renderAux(child, collapsedPaths, childPrefix, depth+1+s.GetCompressionLevel(), renderLine)...) + } + + return arr +} diff --git a/pkg/gui/filtering_menu_panel.go b/pkg/gui/filtering_menu_panel.go index 705e083eb..683bff3e8 100644 --- a/pkg/gui/filtering_menu_panel.go +++ b/pkg/gui/filtering_menu_panel.go @@ -17,12 +17,12 @@ func (gui *Gui) handleCreateFilteringMenuPanel(g *gocui.Gui, v *gocui.View) erro case "files": node := gui.getSelectedFileChangeNode() if node != nil { - fileName = node.Path + fileName = node.GetPath() } case "commitFiles": - file := gui.getSelectedCommitFile() - if file != nil { - fileName = file.Name + node := gui.getSelectedCommitFileNode() + if node != nil { + fileName = node.GetPath() } } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index fbb2ab223..13d827751 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -304,12 +304,12 @@ type guiStateMutexes struct { } type guiState struct { - FileChangeManager *filetree.FileChangeManager - Submodules []*models.SubmoduleConfig - Branches []*models.Branch - Commits []*models.Commit - StashEntries []*models.StashEntry - CommitFiles []*models.CommitFile + FileChangeManager *filetree.FileChangeManager + CommitFileChangeManager *filetree.CommitFileChangeManager + Submodules []*models.SubmoduleConfig + Branches []*models.Branch + Commits []*models.Commit + StashEntries []*models.StashEntry // Suggestions will sometimes appear when typing into a prompt Suggestions []*types.Suggestion // FilteredReflogCommits are the ones that appear in the reflog panel. @@ -381,11 +381,12 @@ func (gui *Gui) resetState() { showTree := gui.Config.GetUserConfig().Gui.ShowFileTree gui.State = &guiState{ - FileChangeManager: filetree.NewFileChangeManager(make([]*models.File, 0), gui.Log, showTree), - Commits: make([]*models.Commit, 0), - FilteredReflogCommits: make([]*models.Commit, 0), - ReflogCommits: make([]*models.Commit, 0), - StashEntries: make([]*models.StashEntry, 0), + FileChangeManager: filetree.NewFileChangeManager(make([]*models.File, 0), gui.Log, showTree), + CommitFileChangeManager: filetree.NewCommitFileChangeManager(make([]*models.CommitFile, 0), gui.Log, showTree), + Commits: make([]*models.Commit, 0), + FilteredReflogCommits: make([]*models.Commit, 0), + ReflogCommits: make([]*models.Commit, 0), + StashEntries: make([]*models.StashEntry, 0), Panels: &panelStates{ // TODO: work out why some of these are -1 and some are 0. Last time I checked there was a good reason but I'm less certain now Files: &filePanelState{listPanelState{SelectedLineIdx: -1}}, diff --git a/pkg/gui/line_by_line_panel.go b/pkg/gui/line_by_line_panel.go index e21fef4ca..759108f06 100644 --- a/pkg/gui/line_by_line_panel.go +++ b/pkg/gui/line_by_line_panel.go @@ -227,7 +227,9 @@ func (gui *Gui) handleMouseScrollDown(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) getSelectedCommitFileName() string { - return gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLineIdx].Name + idx := gui.State.Panels.CommitFiles.SelectedLineIdx + + return gui.State.CommitFileChangeManager.GetItemAtIndex(idx).GetPath() } func (gui *Gui) refreshMainViewForLineByLine(state *lBlPanelState) error { diff --git a/pkg/gui/list_context.go b/pkg/gui/list_context.go index 319d7ee40..441d79ade 100644 --- a/pkg/gui/list_context.go +++ b/pkg/gui/list_context.go @@ -1,8 +1,10 @@ package gui import ( + "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/presentation" + "github.com/jesseduffield/lazygit/pkg/utils" ) type ListContext struct { @@ -452,17 +454,27 @@ func (gui *Gui) commitFilesListContext() *ListContext { ViewName: "commitFiles", WindowName: "commits", ContextKey: COMMIT_FILES_CONTEXT_KEY, - GetItemsLength: func() int { return len(gui.State.CommitFiles) }, + GetItemsLength: func() int { return gui.State.CommitFileChangeManager.GetItemsLength() }, GetPanelState: func() IListPanelState { return gui.State.Panels.CommitFiles }, OnFocus: gui.handleCommitFileSelect, Gui: gui, ResetMainViewOriginOnFocus: true, Kind: SIDE_CONTEXT, GetDisplayStrings: func() [][]string { - return presentation.GetCommitFileListDisplayStrings(gui.State.CommitFiles, gui.State.Modes.Diffing.Ref) + if gui.State.CommitFileChangeManager.GetItemsLength() == 0 { + return [][]string{{utils.ColoredString("(none)", color.FgRed)}} + } + + lines := gui.State.CommitFileChangeManager.Render(gui.State.Modes.Diffing.Ref) + mappedLines := make([][]string, len(lines)) + for i, line := range lines { + mappedLines[i] = []string{line} + } + + return mappedLines }, SelectedItem: func() (ListItem, bool) { - item := gui.getSelectedCommitFile() + item := gui.getSelectedCommitFileNode() return item, item != nil }, } diff --git a/pkg/gui/patch_building_panel.go b/pkg/gui/patch_building_panel.go index 28926ddb4..8d2d70bf7 100644 --- a/pkg/gui/patch_building_panel.go +++ b/pkg/gui/patch_building_panel.go @@ -28,19 +28,19 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int, state *lBlPanelSt gui.getSecondaryView().Title = "Custom Patch" // get diff from commit file that's currently selected - commitFile := gui.getSelectedCommitFile() - if commitFile == nil { + node := gui.getSelectedCommitFileNode() + if node == nil { return nil } - to := commitFile.Parent + to := gui.State.CommitFileChangeManager.GetParent() from, reverse := gui.getFromAndReverseArgsForDiff(to) - diff, err := gui.GitCommand.ShowFileDiff(from, to, reverse, commitFile.Name, true) + diff, err := gui.GitCommand.ShowFileDiff(from, to, reverse, node.GetPath(), true) if err != nil { return err } - secondaryDiff := gui.GitCommand.PatchManager.RenderPatchForFile(commitFile.Name, true, false, true) + secondaryDiff := gui.GitCommand.PatchManager.RenderPatchForFile(node.GetPath(), true, false, true) if err != nil { return err } @@ -78,12 +78,12 @@ func (gui *Gui) handleToggleSelectionForPatch() error { } // add range of lines to those set for the file - commitFile := gui.getSelectedCommitFile() - if commitFile == nil { + node := gui.getSelectedCommitFileNode() + if node == nil { return nil } - if err := toggleFunc(commitFile.Name, state.FirstLineIdx, state.LastLineIdx); err != nil { + if err := toggleFunc(node.GetPath(), state.FirstLineIdx, state.LastLineIdx); err != nil { // might actually want to return an error here gui.Log.Error(err) } diff --git a/pkg/gui/presentation/commit_files.go b/pkg/gui/presentation/commit_files.go index d90e3e990..2e87e61c2 100644 --- a/pkg/gui/presentation/commit_files.go +++ b/pkg/gui/presentation/commit_files.go @@ -8,41 +8,31 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -func GetCommitFileListDisplayStrings(commitFiles []*models.CommitFile, diffName string) [][]string { - if len(commitFiles) == 0 { - return [][]string{{utils.ColoredString("(none)", color.FgRed)}} - } - - lines := make([][]string, len(commitFiles)) - - for i := range commitFiles { - diffed := commitFiles[i].Name == diffName - lines[i] = getCommitFileDisplayStrings(commitFiles[i], diffed) - } - - return lines -} - -// getCommitFileDisplayStrings returns the display string of branch -func getCommitFileDisplayStrings(f *models.CommitFile, diffed bool) []string { +func GetCommitFileLine(name string, diffName string, commitFile *models.CommitFile) string { yellow := color.New(color.FgYellow) green := color.New(color.FgGreen) defaultColor := color.New(theme.DefaultTextColor) diffTerminalColor := color.New(theme.DiffTerminalColor) - var colour *color.Color - switch f.PatchStatus { - case patch.UNSELECTED: - colour = defaultColor - case patch.WHOLE: - colour = green - case patch.PART: - colour = yellow + if commitFile == nil { + return name } - if diffed { + + colour := defaultColor + if diffName == name { colour = diffTerminalColor + } else if commitFile != nil { + switch commitFile.PatchStatus { + case patch.UNSELECTED: + colour = defaultColor + case patch.WHOLE: + colour = green + case patch.PART: + colour = yellow + } } - return []string{utils.ColoredString(f.ChangeStatus, getColorForChangeStatus(f.ChangeStatus)), colour.Sprint(f.Name)} + + return utils.ColoredString(commitFile.ChangeStatus, getColorForChangeStatus(commitFile.ChangeStatus)) + " " + colour.Sprint(name) } func getColorForChangeStatus(changeStatus string) color.Attribute {