summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2020-11-15 10:45:55 +1100
committerJesse Duffield <jessedduffield@gmail.com>2021-03-30 21:57:00 +1100
commit45939171ea6537562270975348fe2919c9c60b95 (patch)
treeb9c4138c32b54577a3d6f17acdc0f4132c2ebc9e /pkg
parent049849264e561d2867d0d94940b1f66dcac77e6b (diff)
WIP
start moving to new interface WIP WIP WIP WIP WIP
Diffstat (limited to 'pkg')
-rw-r--r--pkg/commands/loading_files.go1
-rw-r--r--pkg/commands/models/status_line_node.go137
-rw-r--r--pkg/commands/models/status_line_node_test.go76
-rw-r--r--pkg/gui/files_panel.go37
-rw-r--r--pkg/gui/gui.go14
-rw-r--r--pkg/gui/list_context.go12
-rw-r--r--pkg/gui/presentation/files.go32
-rw-r--r--pkg/gui/status_line_manager.go87
-rw-r--r--pkg/gui/status_tree.go47
-rw-r--r--pkg/gui/submodules_panel.go2
10 files changed, 411 insertions, 34 deletions
diff --git a/pkg/commands/loading_files.go b/pkg/commands/loading_files.go
index f2a285f58..411c0251a 100644
--- a/pkg/commands/loading_files.go
+++ b/pkg/commands/loading_files.go
@@ -58,6 +58,7 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
}
files = append(files, file)
}
+
return files
}
diff --git a/pkg/commands/models/status_line_node.go b/pkg/commands/models/status_line_node.go
new file mode 100644
index 000000000..042a0fceb
--- /dev/null
+++ b/pkg/commands/models/status_line_node.go
@@ -0,0 +1,137 @@
+package models
+
+import (
+ "sort"
+)
+
+type StatusLineNode struct {
+ Children []*StatusLineNode
+ File *File
+ Name string
+ Collapsed bool
+}
+
+func (s *StatusLineNode) GetShortStatus() string {
+ // need to see if any child has unstaged changes.
+ if s.IsLeaf() {
+ return s.File.ShortStatus
+ }
+
+ firstChar := " "
+ secondChar := " "
+ if s.HasStagedChanges() {
+ firstChar = "M"
+ }
+ if s.HasUnstagedChanges() {
+ secondChar = "M"
+ }
+
+ return firstChar + secondChar
+}
+
+func (s *StatusLineNode) HasUnstagedChanges() bool {
+ if s.IsLeaf() {
+ return s.File.HasUnstagedChanges
+ }
+
+ for _, child := range s.Children {
+ if child.HasUnstagedChanges() {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (s *StatusLineNode) HasStagedChanges() bool {
+ if s.IsLeaf() {
+ return s.File.HasStagedChanges
+ }
+
+ for _, child := range s.Children {
+ if child.HasStagedChanges() {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (s *StatusLineNode) GetNodeAtIndex(index int) *StatusLineNode {
+ node, _ := s.getNodeAtIndexAux(index)
+
+ return node
+}
+
+func (s *StatusLineNode) getNodeAtIndexAux(index int) (*StatusLineNode, int) {
+ offset := 1
+
+ if index == 0 {
+ return s, offset
+ }
+
+ for _, child := range s.Children {
+ node, offsetChange := child.getNodeAtIndexAux(index - offset)
+ offset += offsetChange
+ if node != nil {
+ return node, offset
+ }
+ }
+
+ return nil, offset
+}
+
+func (s *StatusLineNode) IsLeaf() bool {
+ return len(s.Children) == 0
+}
+
+func (s *StatusLineNode) Size() int {
+ output := 1
+
+ for _, child := range s.Children {
+ output += child.Size()
+ }
+
+ return output
+}
+
+func (s *StatusLineNode) Flatten() []*StatusLineNode {
+ arr := []*StatusLineNode{s}
+
+ for _, child := range s.Children {
+ arr = append(arr, child.Flatten()...)
+ }
+
+ return arr
+}
+
+func (s *StatusLineNode) SortTree() {
+ s.sortChildren()
+
+ for _, child := range s.Children {
+ child.SortTree()
+ }
+}
+
+func (s *StatusLineNode) sortChildren() {
+ if s.IsLeaf() {
+ return
+ }
+
+ sortedChildren := make([]*StatusLineNode, len(s.Children))
+ copy(sortedChildren, s.Children)
+
+ sort.Slice(sortedChildren, func(i, j int) bool {
+ if !sortedChildren[i].IsLeaf() && sortedChildren[j].IsLeaf() {
+ return true
+ }
+ if sortedChildren[i].IsLeaf() && !sortedChildren[j].IsLeaf() {
+ return false
+ }
+
+ return sortedChildren[i].Name < sortedChildren[j].Name
+ })
+
+ // TODO: think about making this in-place
+ s.Children = sortedChildren
+}
diff --git a/pkg/commands/models/status_line_node_test.go b/pkg/commands/models/status_line_node_test.go
new file mode 100644
index 000000000..f8114fcf1
--- /dev/null
+++ b/pkg/commands/models/status_line_node_test.go
@@ -0,0 +1,76 @@
+package models
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRender(t *testing.T) {
+ scenarios := []struct {
+ name string
+ root *StatusLineNode
+ expected []string
+ }{
+ {
+ name: "nil node",
+ root: nil,
+ expected: []string{},
+ },
+ {
+ name: "leaf node",
+ root: &StatusLineNode{
+ Name: "",
+ Children: []*StatusLineNode{
+ {File: &File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Name: "test"},
+ },
+ },
+ expected: []string{" M test"},
+ },
+ {
+ name: "big example",
+ root: &StatusLineNode{
+ Name: "",
+ Children: []*StatusLineNode{
+ {
+ Name: "dir1",
+ Collapsed: true,
+ Children: []*StatusLineNode{
+ {
+ File: &File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
+ Name: "file2",
+ },
+ },
+ },
+ {
+ Name: "dir2",
+ Children: []*StatusLineNode{
+ {
+ File: &File{Name: "file3", ShortStatus: " M", HasStagedChanges: true},
+ Name: "file3",
+ },
+ {
+ File: &File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true},
+ Name: "file4",
+ },
+ },
+ },
+ {
+ File: &File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
+ Name: "file1",
+ },
+ },
+ },
+
+ expected: []string{"M dir1 ►", "MM dir2 ▼", " M file3", " M file4", "M file1"},
+ },
+ }
+
+ for _, s := range scenarios {
+ s := s
+ t.Run(s.name, func(t *testing.T) {
+ result := s.root.Render()[1:]
+ assert.EqualValues(t, s.expected, result)
+ })
+ }
+}
diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go
index 52bd6c346..8a388f9a0 100644
--- a/pkg/gui/files_panel.go
+++ b/pkg/gui/files_panel.go
@@ -21,13 +21,26 @@ import (
// list panel functions
+// func (gui *Gui) getSelectedStatusNode() *models.StatusLineNode {
+// selectedLine := gui.State.Panels.Files.SelectedLineIdx
+// if selectedLine == -1 {
+// return nil
+// }
+
+// return gui.State.StatusLineManager.GetItemAtIndex(selectedLine)
+// }
+
func (gui *Gui) getSelectedFile() *models.File {
selectedLine := gui.State.Panels.Files.SelectedLineIdx
if selectedLine == -1 {
return nil
}
- return gui.State.Files[selectedLine]
+ node := gui.State.StatusLineManager.GetItemAtIndex(selectedLine)
+ if node == nil {
+ return nil
+ }
+ return node.File
}
func (gui *Gui) selectFile(alreadySelected bool) error {
@@ -131,7 +144,7 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
// specific functions
func (gui *Gui) stagedFiles() []*models.File {
- files := gui.State.Files
+ files := gui.State.StatusLineManager.GetAllFiles()
result := make([]*models.File, 0)
for _, file := range files {
if file.HasStagedChanges {
@@ -142,7 +155,7 @@ func (gui *Gui) stagedFiles() []*models.File {
}
func (gui *Gui) trackedFiles() []*models.File {
- files := gui.State.Files
+ files := gui.State.StatusLineManager.GetAllFiles()
result := make([]*models.File, 0, len(files))
for _, file := range files {
if file.Tracked {
@@ -216,7 +229,7 @@ func (gui *Gui) handleFilePress() error {
}
func (gui *Gui) allFilesStaged() bool {
- for _, file := range gui.State.Files {
+ for _, file := range gui.State.StatusLineManager.GetAllFiles() {
if file.HasUnstagedChanges {
return false
}
@@ -450,8 +463,11 @@ func (gui *Gui) refreshStateFiles() error {
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx
// get files to stage
- files := gui.GitCommand.GetStatusFiles(commands.GetStatusFileOptions{})
- gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files, selectedFile)
+ noRenames := gui.State.StatusLineManager.TreeMode
+ files := gui.GitCommand.GetStatusFiles(commands.GetStatusFileOptions{NoRenames: noRenames})
+ gui.State.StatusLineManager.SetFiles(
+ gui.GitCommand.MergeStatusFiles(gui.State.StatusLineManager.GetAllFiles(), files, selectedFile),
+ )
if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil {
return err
@@ -459,8 +475,9 @@ func (gui *Gui) refreshStateFiles() error {
// let's try to find our file again and move the cursor to that
if selectedFile != nil {
- for idx, f := range gui.State.Files {
- selectedFileHasMoved := f.Matches(selectedFile) && idx != prevSelectedLineIdx
+ for idx, node := range gui.State.StatusLineManager.GetAllItems() {
+ // TODO: check that this works
+ selectedFileHasMoved := node.File != nil && node.File.Matches(selectedFile) && idx != prevSelectedLineIdx
if selectedFileHasMoved {
gui.State.Panels.Files.SelectedLineIdx = idx
break
@@ -468,7 +485,7 @@ func (gui *Gui) refreshStateFiles() error {
}
}
- gui.refreshSelectedLine(gui.State.Panels.Files, len(gui.State.Files))
+ gui.refreshSelectedLine(gui.State.Panels.Files, gui.State.StatusLineManager.GetItemsLength())
return nil
}
@@ -661,7 +678,7 @@ func (gui *Gui) openFile(filename string) error {
}
func (gui *Gui) anyFilesWithMergeConflicts() bool {
- for _, file := range gui.State.Files {
+ for _, file := range gui.State.StatusLineManager.GetAllFiles() {
if file.HasMergeConflicts {
return true
}
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 77bd82f63..8f6badb6e 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -303,12 +303,12 @@ type guiStateMutexes struct {
}
type guiState struct {
- Files []*models.File
- Submodules []*models.SubmoduleConfig
- Branches []*models.Branch
- Commits []*models.Commit
- StashEntries []*models.StashEntry
- CommitFiles []*models.CommitFile
+ StatusLineManager *StatusLineManager
+ Submodules []*models.SubmoduleConfig
+ Branches []*models.Branch
+ Commits []*models.Commit
+ StashEntries []*models.StashEntry
+ CommitFiles []*models.CommitFile
// Suggestions will sometimes appear when typing into a prompt
Suggestions []*types.Suggestion
// FilteredReflogCommits are the ones that appear in the reflog panel.
@@ -378,7 +378,7 @@ func (gui *Gui) resetState() {
}
gui.State = &guiState{
- Files: make([]*models.File, 0),
+ StatusLineManager: &StatusLineManager{Files: make([]*models.File, 0), Log: gui.Log, TreeMode: true},
Commits: make([]*models.Commit, 0),
FilteredReflogCommits: make([]*models.Commit, 0),
ReflogCommits: make([]*models.Commit, 0),
diff --git a/pkg/gui/list_context.go b/pkg/gui/list_context.go
index 908f35f7b..7a7c6a8e8 100644
--- a/pkg/gui/list_context.go
+++ b/pkg/gui/list_context.go
@@ -262,7 +262,7 @@ func (gui *Gui) filesListContext() *ListContext {
return &ListContext{
ViewName: "files",
ContextKey: FILES_CONTEXT_KEY,
- GetItemsLength: func() int { return len(gui.State.Files) },
+ GetItemsLength: func() int { return gui.State.StatusLineManager.GetItemsLength() },
GetPanelState: func() IListPanelState { return gui.State.Panels.Files },
OnFocus: gui.focusAndSelectFile,
OnClickSelectedItem: gui.handleFilePress,
@@ -270,7 +270,15 @@ func (gui *Gui) filesListContext() *ListContext {
ResetMainViewOriginOnFocus: false,
Kind: SIDE_CONTEXT,
GetDisplayStrings: func() [][]string {
- return presentation.GetFileListDisplayStrings(gui.State.Files, gui.State.Modes.Diffing.Ref, gui.State.Submodules)
+ lines := gui.State.StatusLineManager.Render(gui.State.Modes.Diffing.Ref, gui.State.Submodules)
+ mappedLines := make([][]string, len(lines))
+ for i, line := range lines {
+ mappedLines[i] = []string{line}
+ }
+
+ return mappedLines
+
+ return presentation.GetFileListDisplayStrings(gui.State.StatusLineManager.GetAllFiles(), gui.State.Modes.Diffing.Ref, gui.State.Submodules)
},
SelectedItem: func() (ListItem, bool) {
item := gui.getSelectedFile()
diff --git a/pkg/gui/presentation/files.go b/pkg/gui/presentation/files.go
index 4adb78ea2..d64437b2c 100644
--- a/pkg/gui/presentation/files.go
+++ b/pkg/gui/presentation/files.go
@@ -11,41 +11,45 @@ func GetFileListDisplayStrings(files []*models.File, diffName string, submoduleC
lines := make([][]string, len(files))
for i := range files {
- diffed := files[i].Name == diffName
- lines[i] = getFileDisplayStrings(files[i], diffed, submoduleConfigs)
+ lines[i] = getFileDisplayStrings(files[i], diffName, submoduleConfigs)
}
return lines
}
// getFileDisplayStrings returns the display string of branch
-func getFileDisplayStrings(f *models.File, diffed bool, submoduleConfigs []*models.SubmoduleConfig) []string {
+func getFileDisplayStrings(f *models.File, diffName string, submoduleConfigs []*models.SubmoduleConfig) []string {
+ output := GetStatusNodeLine(f.HasUnstagedChanges, f.ShortStatus, f.Name, diffName, submoduleConfigs, f)
+
+ return []string{output}
+}
+
+func GetStatusNodeLine(hasUnstagedChanges bool, shortStatus string, name string, diffName string, submoduleConfigs []*models.SubmoduleConfig, file *models.File) string {
// potentially inefficient to be instantiating these color
// objects with each render
red := color.New(color.FgRed)
green := color.New(color.FgGreen)
diffColor := color.New(theme.DiffTerminalColor)
- if !f.Tracked && !f.HasStagedChanges {
- return []string{red.Sprint(f.DisplayString)}
- }
var restColor *color.Color
- if diffed {
+ if name == diffName {
restColor = diffColor
- } else if f.HasUnstagedChanges {
+ } else if hasUnstagedChanges {
restColor = red
} else {
restColor = green
}
// this is just making things look nice when the background attribute is 'reverse'
- firstChar := f.DisplayString[0:1]
+ firstChar := shortStatus[0:1]
firstCharCl := green
- if firstChar == " " {
+ if firstChar == "?" {
+ firstCharCl = red
+ } else if firstChar == " " {
firstCharCl = restColor
}
- secondChar := f.DisplayString[1:2]
+ secondChar := shortStatus[1:2]
secondCharCl := red
if secondChar == " " {
secondCharCl = restColor
@@ -53,11 +57,11 @@ func getFileDisplayStrings(f *models.File, diffed bool, submoduleConfigs []*mode
output := firstCharCl.Sprint(firstChar)
output += secondCharCl.Sprint(secondChar)
- output += restColor.Sprintf(" %s", f.Name)
+ output += restColor.Sprintf(" %s", name)
- if f.IsSubmodule(submoduleConfigs) {
+ if file != nil && file.IsSubmodule(submoduleConfigs) {
output += utils.ColoredString(" (submodule)", theme.DefaultTextColor)
}
- return []string{output}
+ return output
}
diff --git a/pkg/gui/status_line_manager.go b/pkg/gui/status_line_manager.go
new file mode 100644
index 000000000..2dd69b0cf
--- /dev/null
+++ b/pkg/gui/status_line_manager.go
@@ -0,0 +1,87 @@
+package gui
+
+import (
+ "fmt"
+ "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 = "►"
+
+type StatusLineManager struct {
+ Files []*models.File
+ Tree *models.StatusLineNode
+ TreeMode bool
+ Log *logrus.Entry
+}
+
+func (m *StatusLineManager) GetItemAtIndex(index int) *models.StatusLineNode {
+ if m.TreeMode {
+ // need to traverse the three depth first until we get to the index.
+ return m.Tree.GetNodeAtIndex(index + 1) // ignoring root
+ }
+
+ m.Log.Warn(index)
+ if index > len(m.Files)-1 {
+ return nil
+ }
+
+ return &models.StatusLineNode{File: m.Files[index]}
+}
+
+func (m *StatusLineManager) GetAllItems() []*models.StatusLineNode {
+ return m.Tree.Flatten()[1:] // ignoring root
+}
+
+func (m *StatusLineManager) GetItemsLength() int {
+ return m.Tree.Size() - 1 // ignoring root
+}
+
+func (m *StatusLineManager) GetAllFiles() []*models.File {
+ return m.Files
+}
+
+func (m *StatusLineManager) SetFiles(files []*models.File) {
+ m.Files = files
+ m.Tree = GetTreeFromStatusFiles(files)
+}
+
+func (m *StatusLineManager) Render(diffName string, submoduleConfigs []*models.SubmoduleConfig) []string {
+ return m.renderAux(m.Tree, -1, diffName, submoduleConfigs)
+}
+
+func (m *StatusLineManager) renderAux(s *models.StatusLineNode, depth int, diffName string, submoduleConfigs []*models.SubmoduleConfig) []string {
+ if s == nil {
+ return []string{}
+ }
+
+ getLine := func() string {
+ return strings.Repeat(" ", depth) + presentation.GetStatusNodeLine(s.HasUnstagedChanges(), s.GetShortStatus(), s.Name, diffName, submoduleConfigs, s.File)
+ }
+
+ if s.IsLeaf() {
+ if depth == -1 {
+ return []string{}
+ }
+ return []string{getLine()}
+ }
+
+ if s.Collapsed {
+ return []string{fmt.Sprintf("%s%s %s", strings.Repeat(" ", depth), s.Name, COLLAPSED_ARROW)}
+ }
+
+ arr := []string{}
+ if depth > -1 {
+ arr = append(arr, fmt.Sprintf("%s%s %s", strings.Repeat(" ", depth), s.Name, EXPANDED_ARROW))
+ }
+
+ for _, child := range s.Children {
+ arr = append(arr, m.renderAux(child, depth+1, diffName, submoduleConfigs)...)
+ }
+
+ return arr
+}
diff --git a/pkg/gui/status_tree.go b/pkg/gui/status_tree.go
new file mode 100644
index 000000000..758f308a7
--- /dev/null
+++ b/pkg/gui/status_tree.go
@@ -0,0 +1,47 @@
+package gui
+
+import (
+ "os"
+ "sort"
+ "strings"
+
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+)
+
+func GetTreeFromStatusFiles(files []*models.File) *models.StatusLineNode {
+ root := &models.StatusLineNode{}
+
+ sort.SliceStable(files, func(i, j int) bool {
+ return files[i].Name < files[j].Name
+ })
+
+ var curr *models.StatusLineNode
+ for _, file := range files {
+ split := strings.Split(file.Name, string(os.PathSeparator))
+ curr = root
+ outer:
+ for i, dir := range split {
+ var setFile *models.File
+ if i == len(split)-1 {
+ setFile = file
+ }
+ for _, existingChild := range curr.Children {
+ if existingChild.Name == dir {
+ curr = existingChild
+ continue outer
+ }
+ }
+ newChild := &models.StatusLineNode{
+ Name: dir,
+ File: setFile,
+ }
+ curr.Children = append(curr.Children, newChild)
+
+ curr = newChild
+ }
+ }
+
+ root.SortTree()
+
+ return root
+}
diff --git a/pkg/gui/submodules_panel.go b/pkg/gui/submodules_panel.go
index 470a20e11..d4b76b7dd 100644
--- a/pkg/gui/submodules_panel.go
+++ b/pkg/gui/submodules_panel.go
@@ -98,7 +98,7 @@ func (gui *Gui) handleResetSubmodule(submodule *models.SubmoduleConfig) error {
}
func (gui *Gui) fileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
- for _, file := range gui.State.Files {
+ for _, file := range gui.State.StatusLineManager.GetAllFiles() {
if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
return file
}