mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-15 00:15:32 +02:00
WIP
start moving to new interface WIP WIP WIP WIP WIP
This commit is contained in:
@ -58,6 +58,7 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
|
|||||||
}
|
}
|
||||||
files = append(files, file)
|
files = append(files, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
137
pkg/commands/models/status_line_node.go
Normal file
137
pkg/commands/models/status_line_node.go
Normal file
@ -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
|
||||||
|
}
|
76
pkg/commands/models/status_line_node_test.go
Normal file
76
pkg/commands/models/status_line_node_test.go
Normal file
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -21,13 +21,26 @@ import (
|
|||||||
|
|
||||||
// list panel functions
|
// 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 {
|
func (gui *Gui) getSelectedFile() *models.File {
|
||||||
selectedLine := gui.State.Panels.Files.SelectedLineIdx
|
selectedLine := gui.State.Panels.Files.SelectedLineIdx
|
||||||
if selectedLine == -1 {
|
if selectedLine == -1 {
|
||||||
return nil
|
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 {
|
func (gui *Gui) selectFile(alreadySelected bool) error {
|
||||||
@ -131,7 +144,7 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
|
|||||||
// specific functions
|
// specific functions
|
||||||
|
|
||||||
func (gui *Gui) stagedFiles() []*models.File {
|
func (gui *Gui) stagedFiles() []*models.File {
|
||||||
files := gui.State.Files
|
files := gui.State.StatusLineManager.GetAllFiles()
|
||||||
result := make([]*models.File, 0)
|
result := make([]*models.File, 0)
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if file.HasStagedChanges {
|
if file.HasStagedChanges {
|
||||||
@ -142,7 +155,7 @@ func (gui *Gui) stagedFiles() []*models.File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) trackedFiles() []*models.File {
|
func (gui *Gui) trackedFiles() []*models.File {
|
||||||
files := gui.State.Files
|
files := gui.State.StatusLineManager.GetAllFiles()
|
||||||
result := make([]*models.File, 0, len(files))
|
result := make([]*models.File, 0, len(files))
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if file.Tracked {
|
if file.Tracked {
|
||||||
@ -216,7 +229,7 @@ func (gui *Gui) handleFilePress() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) allFilesStaged() bool {
|
func (gui *Gui) allFilesStaged() bool {
|
||||||
for _, file := range gui.State.Files {
|
for _, file := range gui.State.StatusLineManager.GetAllFiles() {
|
||||||
if file.HasUnstagedChanges {
|
if file.HasUnstagedChanges {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -450,8 +463,11 @@ func (gui *Gui) refreshStateFiles() error {
|
|||||||
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx
|
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx
|
||||||
|
|
||||||
// get files to stage
|
// get files to stage
|
||||||
files := gui.GitCommand.GetStatusFiles(commands.GetStatusFileOptions{})
|
noRenames := gui.State.StatusLineManager.TreeMode
|
||||||
gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files, selectedFile)
|
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 {
|
if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil {
|
||||||
return err
|
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
|
// let's try to find our file again and move the cursor to that
|
||||||
if selectedFile != nil {
|
if selectedFile != nil {
|
||||||
for idx, f := range gui.State.Files {
|
for idx, node := range gui.State.StatusLineManager.GetAllItems() {
|
||||||
selectedFileHasMoved := f.Matches(selectedFile) && idx != prevSelectedLineIdx
|
// TODO: check that this works
|
||||||
|
selectedFileHasMoved := node.File != nil && node.File.Matches(selectedFile) && idx != prevSelectedLineIdx
|
||||||
if selectedFileHasMoved {
|
if selectedFileHasMoved {
|
||||||
gui.State.Panels.Files.SelectedLineIdx = idx
|
gui.State.Panels.Files.SelectedLineIdx = idx
|
||||||
break
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -661,7 +678,7 @@ func (gui *Gui) openFile(filename string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) anyFilesWithMergeConflicts() bool {
|
func (gui *Gui) anyFilesWithMergeConflicts() bool {
|
||||||
for _, file := range gui.State.Files {
|
for _, file := range gui.State.StatusLineManager.GetAllFiles() {
|
||||||
if file.HasMergeConflicts {
|
if file.HasMergeConflicts {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -303,12 +303,12 @@ type guiStateMutexes struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type guiState struct {
|
type guiState struct {
|
||||||
Files []*models.File
|
StatusLineManager *StatusLineManager
|
||||||
Submodules []*models.SubmoduleConfig
|
Submodules []*models.SubmoduleConfig
|
||||||
Branches []*models.Branch
|
Branches []*models.Branch
|
||||||
Commits []*models.Commit
|
Commits []*models.Commit
|
||||||
StashEntries []*models.StashEntry
|
StashEntries []*models.StashEntry
|
||||||
CommitFiles []*models.CommitFile
|
CommitFiles []*models.CommitFile
|
||||||
// Suggestions will sometimes appear when typing into a prompt
|
// Suggestions will sometimes appear when typing into a prompt
|
||||||
Suggestions []*types.Suggestion
|
Suggestions []*types.Suggestion
|
||||||
// FilteredReflogCommits are the ones that appear in the reflog panel.
|
// FilteredReflogCommits are the ones that appear in the reflog panel.
|
||||||
@ -378,7 +378,7 @@ func (gui *Gui) resetState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gui.State = &guiState{
|
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),
|
Commits: make([]*models.Commit, 0),
|
||||||
FilteredReflogCommits: make([]*models.Commit, 0),
|
FilteredReflogCommits: make([]*models.Commit, 0),
|
||||||
ReflogCommits: make([]*models.Commit, 0),
|
ReflogCommits: make([]*models.Commit, 0),
|
||||||
|
@ -262,7 +262,7 @@ func (gui *Gui) filesListContext() *ListContext {
|
|||||||
return &ListContext{
|
return &ListContext{
|
||||||
ViewName: "files",
|
ViewName: "files",
|
||||||
ContextKey: FILES_CONTEXT_KEY,
|
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 },
|
GetPanelState: func() IListPanelState { return gui.State.Panels.Files },
|
||||||
OnFocus: gui.focusAndSelectFile,
|
OnFocus: gui.focusAndSelectFile,
|
||||||
OnClickSelectedItem: gui.handleFilePress,
|
OnClickSelectedItem: gui.handleFilePress,
|
||||||
@ -270,7 +270,15 @@ func (gui *Gui) filesListContext() *ListContext {
|
|||||||
ResetMainViewOriginOnFocus: false,
|
ResetMainViewOriginOnFocus: false,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: SIDE_CONTEXT,
|
||||||
GetDisplayStrings: func() [][]string {
|
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) {
|
SelectedItem: func() (ListItem, bool) {
|
||||||
item := gui.getSelectedFile()
|
item := gui.getSelectedFile()
|
||||||
|
@ -11,41 +11,45 @@ func GetFileListDisplayStrings(files []*models.File, diffName string, submoduleC
|
|||||||
lines := make([][]string, len(files))
|
lines := make([][]string, len(files))
|
||||||
|
|
||||||
for i := range files {
|
for i := range files {
|
||||||
diffed := files[i].Name == diffName
|
lines[i] = getFileDisplayStrings(files[i], diffName, submoduleConfigs)
|
||||||
lines[i] = getFileDisplayStrings(files[i], diffed, submoduleConfigs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFileDisplayStrings returns the display string of branch
|
// 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
|
// potentially inefficient to be instantiating these color
|
||||||
// objects with each render
|
// objects with each render
|
||||||
red := color.New(color.FgRed)
|
red := color.New(color.FgRed)
|
||||||
green := color.New(color.FgGreen)
|
green := color.New(color.FgGreen)
|
||||||
diffColor := color.New(theme.DiffTerminalColor)
|
diffColor := color.New(theme.DiffTerminalColor)
|
||||||
if !f.Tracked && !f.HasStagedChanges {
|
|
||||||
return []string{red.Sprint(f.DisplayString)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var restColor *color.Color
|
var restColor *color.Color
|
||||||
if diffed {
|
if name == diffName {
|
||||||
restColor = diffColor
|
restColor = diffColor
|
||||||
} else if f.HasUnstagedChanges {
|
} else if hasUnstagedChanges {
|
||||||
restColor = red
|
restColor = red
|
||||||
} else {
|
} else {
|
||||||
restColor = green
|
restColor = green
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is just making things look nice when the background attribute is 'reverse'
|
// this is just making things look nice when the background attribute is 'reverse'
|
||||||
firstChar := f.DisplayString[0:1]
|
firstChar := shortStatus[0:1]
|
||||||
firstCharCl := green
|
firstCharCl := green
|
||||||
if firstChar == " " {
|
if firstChar == "?" {
|
||||||
|
firstCharCl = red
|
||||||
|
} else if firstChar == " " {
|
||||||
firstCharCl = restColor
|
firstCharCl = restColor
|
||||||
}
|
}
|
||||||
|
|
||||||
secondChar := f.DisplayString[1:2]
|
secondChar := shortStatus[1:2]
|
||||||
secondCharCl := red
|
secondCharCl := red
|
||||||
if secondChar == " " {
|
if secondChar == " " {
|
||||||
secondCharCl = restColor
|
secondCharCl = restColor
|
||||||
@ -53,11 +57,11 @@ func getFileDisplayStrings(f *models.File, diffed bool, submoduleConfigs []*mode
|
|||||||
|
|
||||||
output := firstCharCl.Sprint(firstChar)
|
output := firstCharCl.Sprint(firstChar)
|
||||||
output += secondCharCl.Sprint(secondChar)
|
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)
|
output += utils.ColoredString(" (submodule)", theme.DefaultTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
return []string{output}
|
return output
|
||||||
}
|
}
|
||||||
|
87
pkg/gui/status_line_manager.go
Normal file
87
pkg/gui/status_line_manager.go
Normal file
@ -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
|
||||||
|
}
|
47
pkg/gui/status_tree.go
Normal file
47
pkg/gui/status_tree.go
Normal file
@ -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
|
||||||
|
}
|
@ -98,7 +98,7 @@ func (gui *Gui) handleResetSubmodule(submodule *models.SubmoduleConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) fileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
|
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}) {
|
if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user