1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-01-10 04:07:18 +02:00
start moving to new interface

WIP

WIP

WIP

WIP

WIP
This commit is contained in:
Jesse Duffield 2020-11-15 10:45:55 +11:00
parent 049849264e
commit 45939171ea
10 changed files with 411 additions and 34 deletions

View File

@ -58,6 +58,7 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
}
files = append(files, file)
}
return files
}

View 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
}

View 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)
})
}
}

View File

@ -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
}

View File

@ -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),

View File

@ -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()

View File

@ -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
}

View 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
View 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
}

View File

@ -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
}