Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5504fa5d0 | ||
|
|
273aba38d4 | ||
|
|
cab0aa462c | ||
|
|
b03e2270a0 | ||
|
|
21049be233 | ||
|
|
f89c47b83d | ||
|
|
44f1f22068 | ||
|
|
a229547048 | ||
|
|
4f700c23ba | ||
|
|
b69fc19b35 |
@@ -114,12 +114,12 @@ scoop install lazygit
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Packages for Arch Linux are available via AUR (Arch User Repository).
|
||||
Packages for Arch Linux are available via pacman and AUR (Arch User Repository).
|
||||
|
||||
There are two packages. The stable one which is built with the latest release
|
||||
and the git version which builds from the most recent commit.
|
||||
|
||||
- Stable: <https://aur.archlinux.org/packages/lazygit/>
|
||||
- Stable: `sudo pacman -S lazygit`
|
||||
- Development: <https://aur.archlinux.org/packages/lazygit-git/>
|
||||
|
||||
Instruction of how to install AUR content can be found here:
|
||||
|
||||
2
go.mod
2
go.mod
@@ -20,7 +20,7 @@ require (
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20210405093708-e79dab8f7772
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe // indirect
|
||||
github.com/jesseduffield/yaml v2.1.0+incompatible
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -102,10 +102,6 @@ github.com/jesseduffield/gocui v0.3.1-0.20210405041826-439abd8b6e07 h1:BymGR28au
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20210405041826-439abd8b6e07/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20210405093708-e79dab8f7772 h1:dg9krj10Udac4IcvlVCOAPktQkfggkgtqRmbDKk7Pzw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20210405093708-e79dab8f7772/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20210406065811-95ef6e13779b h1:3+4+muhhikpls5FePXSRNFgcdoPx8dTdqaCy3AqLz98=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20210406065811-95ef6e13779b/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064 h1:Oe+QJuUIOd2TU+A3BW5sT1eXqceoBcOOfyoHlGf7F8Y=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe h1:qsVhCf2RFyyKIUe/+gJblbCpXMUki9rZrHuEctg6M/E=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
||||
github.com/jesseduffield/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE=
|
||||
|
||||
@@ -16,8 +16,13 @@ func (gui *Gui) runSyncOrAsyncCommand(sub *exec.Cmd, err error) (bool, error) {
|
||||
if err != nil {
|
||||
return false, gui.surfaceError(err)
|
||||
}
|
||||
if sub != nil {
|
||||
return false, gui.runSubprocessWithSuspense(sub)
|
||||
if sub == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err = gui.runSubprocessWithSuspense(sub)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -36,12 +41,14 @@ func (gui *Gui) handleCommitConfirm() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = gui.returnFromContext()
|
||||
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.clearEditorView(gui.Views.CommitMessage)
|
||||
_ = gui.returnFromContext()
|
||||
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ func (gui *Gui) handleSubmitCredential() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshSidePanels(refreshOptions{})
|
||||
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCloseCredentialsView() error {
|
||||
@@ -78,6 +78,7 @@ func (gui *Gui) handleCredentialsPopup(cmdErr error) {
|
||||
if strings.Contains(errMessage, "Invalid username, password or passphrase") {
|
||||
errMessage = gui.Tr.PassUnameWrong
|
||||
}
|
||||
_ = gui.returnFromContext()
|
||||
// we are not logging this error because it may contain a password or a passphrase
|
||||
_ = gui.createErrorPanel(errMessage)
|
||||
} else {
|
||||
|
||||
@@ -729,6 +729,10 @@ func (gui *Gui) pushFiles() error {
|
||||
|
||||
// if we have pullables we'll ask if the user wants to force push
|
||||
currentBranch := gui.currentBranch()
|
||||
if currentBranch == nil {
|
||||
// need to wait for branches to refresh
|
||||
return nil
|
||||
}
|
||||
|
||||
if currentBranch.Pullables == "?" {
|
||||
// see if we have this branch in our config with an upstream
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package filetree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -14,18 +12,17 @@ func BuildTreeFromFiles(files []*models.File) *FileNode {
|
||||
|
||||
var curr *FileNode
|
||||
for _, file := range files {
|
||||
split := strings.Split(file.Name, string(os.PathSeparator))
|
||||
splitPath := split(file.Name)
|
||||
curr = root
|
||||
outer:
|
||||
for i := range split {
|
||||
for i := range splitPath {
|
||||
var setFile *models.File
|
||||
isFile := i == len(split)-1
|
||||
isFile := i == len(splitPath)-1
|
||||
if isFile {
|
||||
setFile = file
|
||||
}
|
||||
|
||||
path := filepath.Join(split[:i+1]...)
|
||||
|
||||
path := join(splitPath[:i+1])
|
||||
for _, existingChild := range curr.Children {
|
||||
if existingChild.Path == path {
|
||||
curr = existingChild
|
||||
@@ -61,17 +58,17 @@ func BuildTreeFromCommitFiles(files []*models.CommitFile) *CommitFileNode {
|
||||
|
||||
var curr *CommitFileNode
|
||||
for _, file := range files {
|
||||
split := strings.Split(file.Name, string(os.PathSeparator))
|
||||
splitPath := split(file.Name)
|
||||
curr = root
|
||||
outer:
|
||||
for i := range split {
|
||||
for i := range splitPath {
|
||||
var setFile *models.CommitFile
|
||||
isFile := i == len(split)-1
|
||||
isFile := i == len(splitPath)-1
|
||||
if isFile {
|
||||
setFile = file
|
||||
}
|
||||
|
||||
path := filepath.Join(split[:i+1]...)
|
||||
path := join(splitPath[:i+1])
|
||||
|
||||
for _, existingChild := range curr.Children {
|
||||
if existingChild.Path == path {
|
||||
@@ -108,3 +105,11 @@ func BuildFlatTreeFromFiles(files []*models.File) *FileNode {
|
||||
|
||||
return &FileNode{Children: sortedFiles}
|
||||
}
|
||||
|
||||
func split(str string) []string {
|
||||
return strings.Split(str, "/")
|
||||
}
|
||||
|
||||
func join(strs []string) string {
|
||||
return strings.Join(strs, "/")
|
||||
}
|
||||
|
||||
484
pkg/gui/filetree/build_tree_test.go
Normal file
484
pkg/gui/filetree/build_tree_test.go
Normal file
@@ -0,0 +1,484 @@
|
||||
package filetree
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBuildTreeFromFiles(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
files []*models.File
|
||||
expected *FileNode
|
||||
}{
|
||||
{
|
||||
name: "no files",
|
||||
files: []*models.File{},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
Children: []*FileNode{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files in same directory",
|
||||
files: []*models.File{
|
||||
{
|
||||
Name: "dir1/a",
|
||||
},
|
||||
{
|
||||
Name: "dir1/b",
|
||||
},
|
||||
},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
Children: []*FileNode{
|
||||
{
|
||||
Path: "dir1",
|
||||
Children: []*FileNode{
|
||||
{
|
||||
File: &models.File{Name: "dir1/a"},
|
||||
Path: "dir1/a",
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "dir1/b"},
|
||||
Path: "dir1/b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be compressed",
|
||||
files: []*models.File{
|
||||
{
|
||||
Name: "dir1/a",
|
||||
},
|
||||
{
|
||||
Name: "dir2/b",
|
||||
},
|
||||
},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
Children: []*FileNode{
|
||||
{
|
||||
File: &models.File{Name: "dir1/a"},
|
||||
Path: "dir1/a",
|
||||
CompressionLevel: 1,
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "dir2/b"},
|
||||
Path: "dir2/b",
|
||||
CompressionLevel: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be sorted",
|
||||
files: []*models.File{
|
||||
{
|
||||
Name: "b",
|
||||
},
|
||||
{
|
||||
Name: "a",
|
||||
},
|
||||
},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
Children: []*FileNode{
|
||||
{
|
||||
File: &models.File{Name: "a"},
|
||||
Path: "a",
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "b"},
|
||||
Path: "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be sorted including a merge conflict file",
|
||||
files: []*models.File{
|
||||
{
|
||||
Name: "b",
|
||||
},
|
||||
{
|
||||
Name: "z",
|
||||
HasMergeConflicts: true,
|
||||
},
|
||||
{
|
||||
Name: "a",
|
||||
},
|
||||
},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
// it is a little strange that we're not bubbling up our merge conflict
|
||||
// here but we are technically still in in tree mode and that's the rule
|
||||
Children: []*FileNode{
|
||||
{
|
||||
File: &models.File{Name: "a"},
|
||||
Path: "a",
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "b"},
|
||||
Path: "b",
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "z", HasMergeConflicts: true},
|
||||
Path: "z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := BuildTreeFromFiles(s.files)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFlatTreeFromFiles(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
files []*models.File
|
||||
expected *FileNode
|
||||
}{
|
||||
{
|
||||
name: "no files",
|
||||
files: []*models.File{},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
Children: []*FileNode{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files in same directory",
|
||||
files: []*models.File{
|
||||
{
|
||||
Name: "dir1/a",
|
||||
},
|
||||
{
|
||||
Name: "dir1/b",
|
||||
},
|
||||
},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
Children: []*FileNode{
|
||||
{
|
||||
File: &models.File{Name: "dir1/a"},
|
||||
Path: "dir1/a",
|
||||
CompressionLevel: 0,
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "dir1/b"},
|
||||
Path: "dir1/b",
|
||||
CompressionLevel: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be compressed",
|
||||
files: []*models.File{
|
||||
{
|
||||
Name: "dir1/a",
|
||||
},
|
||||
{
|
||||
Name: "dir2/b",
|
||||
},
|
||||
},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
Children: []*FileNode{
|
||||
{
|
||||
File: &models.File{Name: "dir1/a"},
|
||||
Path: "dir1/a",
|
||||
CompressionLevel: 1,
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "dir2/b"},
|
||||
Path: "dir2/b",
|
||||
CompressionLevel: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be sorted",
|
||||
files: []*models.File{
|
||||
{
|
||||
Name: "b",
|
||||
},
|
||||
{
|
||||
Name: "a",
|
||||
},
|
||||
},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
Children: []*FileNode{
|
||||
{
|
||||
File: &models.File{Name: "a"},
|
||||
Path: "a",
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "b"},
|
||||
Path: "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be sorted including a merge conflict file",
|
||||
files: []*models.File{
|
||||
{
|
||||
Name: "z",
|
||||
HasMergeConflicts: true,
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
},
|
||||
{
|
||||
Name: "a",
|
||||
},
|
||||
},
|
||||
expected: &FileNode{
|
||||
Path: "",
|
||||
Children: []*FileNode{
|
||||
{
|
||||
File: &models.File{Name: "z", HasMergeConflicts: true},
|
||||
Path: "z",
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "a"},
|
||||
Path: "a",
|
||||
},
|
||||
{
|
||||
File: &models.File{Name: "b"},
|
||||
Path: "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := BuildFlatTreeFromFiles(s.files)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildTreeFromCommitFiles(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
files []*models.CommitFile
|
||||
expected *CommitFileNode
|
||||
}{
|
||||
{
|
||||
name: "no files",
|
||||
files: []*models.CommitFile{},
|
||||
expected: &CommitFileNode{
|
||||
Path: "",
|
||||
Children: []*CommitFileNode{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files in same directory",
|
||||
files: []*models.CommitFile{
|
||||
{
|
||||
Name: "dir1/a",
|
||||
},
|
||||
{
|
||||
Name: "dir1/b",
|
||||
},
|
||||
},
|
||||
expected: &CommitFileNode{
|
||||
Path: "",
|
||||
Children: []*CommitFileNode{
|
||||
{
|
||||
Path: "dir1",
|
||||
Children: []*CommitFileNode{
|
||||
{
|
||||
File: &models.CommitFile{Name: "dir1/a"},
|
||||
Path: "dir1/a",
|
||||
},
|
||||
{
|
||||
File: &models.CommitFile{Name: "dir1/b"},
|
||||
Path: "dir1/b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be compressed",
|
||||
files: []*models.CommitFile{
|
||||
{
|
||||
Name: "dir1/a",
|
||||
},
|
||||
{
|
||||
Name: "dir2/b",
|
||||
},
|
||||
},
|
||||
expected: &CommitFileNode{
|
||||
Path: "",
|
||||
Children: []*CommitFileNode{
|
||||
{
|
||||
File: &models.CommitFile{Name: "dir1/a"},
|
||||
Path: "dir1/a",
|
||||
CompressionLevel: 1,
|
||||
},
|
||||
{
|
||||
File: &models.CommitFile{Name: "dir2/b"},
|
||||
Path: "dir2/b",
|
||||
CompressionLevel: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be sorted",
|
||||
files: []*models.CommitFile{
|
||||
{
|
||||
Name: "b",
|
||||
},
|
||||
{
|
||||
Name: "a",
|
||||
},
|
||||
},
|
||||
expected: &CommitFileNode{
|
||||
Path: "",
|
||||
Children: []*CommitFileNode{
|
||||
{
|
||||
File: &models.CommitFile{Name: "a"},
|
||||
Path: "a",
|
||||
},
|
||||
{
|
||||
File: &models.CommitFile{Name: "b"},
|
||||
Path: "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := BuildTreeFromCommitFiles(s.files)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
files []*models.CommitFile
|
||||
expected *CommitFileNode
|
||||
}{
|
||||
{
|
||||
name: "no files",
|
||||
files: []*models.CommitFile{},
|
||||
expected: &CommitFileNode{
|
||||
Path: "",
|
||||
Children: []*CommitFileNode{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files in same directory",
|
||||
files: []*models.CommitFile{
|
||||
{
|
||||
Name: "dir1/a",
|
||||
},
|
||||
{
|
||||
Name: "dir1/b",
|
||||
},
|
||||
},
|
||||
expected: &CommitFileNode{
|
||||
Path: "",
|
||||
Children: []*CommitFileNode{
|
||||
{
|
||||
File: &models.CommitFile{Name: "dir1/a"},
|
||||
Path: "dir1/a",
|
||||
CompressionLevel: 0,
|
||||
},
|
||||
{
|
||||
File: &models.CommitFile{Name: "dir1/b"},
|
||||
Path: "dir1/b",
|
||||
CompressionLevel: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be compressed",
|
||||
files: []*models.CommitFile{
|
||||
{
|
||||
Name: "dir1/a",
|
||||
},
|
||||
{
|
||||
Name: "dir2/b",
|
||||
},
|
||||
},
|
||||
expected: &CommitFileNode{
|
||||
Path: "",
|
||||
Children: []*CommitFileNode{
|
||||
{
|
||||
File: &models.CommitFile{Name: "dir1/a"},
|
||||
Path: "dir1/a",
|
||||
CompressionLevel: 1,
|
||||
},
|
||||
{
|
||||
File: &models.CommitFile{Name: "dir2/b"},
|
||||
Path: "dir2/b",
|
||||
CompressionLevel: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paths that can be sorted",
|
||||
files: []*models.CommitFile{
|
||||
{
|
||||
Name: "b",
|
||||
},
|
||||
{
|
||||
Name: "a",
|
||||
},
|
||||
},
|
||||
expected: &CommitFileNode{
|
||||
Path: "",
|
||||
Children: []*CommitFileNode{
|
||||
{
|
||||
File: &models.CommitFile{Name: "a"},
|
||||
Path: "a",
|
||||
},
|
||||
{
|
||||
File: &models.CommitFile{Name: "b"},
|
||||
Path: "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := BuildFlatTreeFromCommitFiles(s.files)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,12 @@
|
||||
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))
|
||||
splitPath := split(path)
|
||||
for i := range splitPath {
|
||||
dir := join(splitPath[0 : i+1])
|
||||
cp[dir] = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package filetree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
)
|
||||
|
||||
@@ -169,8 +165,8 @@ func (s *CommitFileNode) AnyFile(test func(file *models.CommitFile) bool) bool {
|
||||
}
|
||||
|
||||
func (s *CommitFileNode) NameAtDepth(depth int) string {
|
||||
splitName := strings.Split(s.Path, string(os.PathSeparator))
|
||||
name := filepath.Join(splitName[depth:]...)
|
||||
splitName := split(s.Path)
|
||||
name := join(splitName[depth:])
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@ package filetree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
)
|
||||
@@ -171,18 +168,18 @@ func (s *FileNode) AnyFile(test func(file *models.File) bool) bool {
|
||||
}
|
||||
|
||||
func (s *FileNode) NameAtDepth(depth int) string {
|
||||
splitName := strings.Split(s.Path, string(os.PathSeparator))
|
||||
name := filepath.Join(splitName[depth:]...)
|
||||
splitName := split(s.Path)
|
||||
name := join(splitName[depth:])
|
||||
|
||||
if s.File != nil && s.File.IsRename() {
|
||||
splitPrevName := strings.Split(s.File.PreviousName, string(os.PathSeparator))
|
||||
splitPrevName := split(s.File.PreviousName)
|
||||
|
||||
prevName := s.File.PreviousName
|
||||
// if the file has just been renamed inside the same directory, we can shave off
|
||||
// the prefix for the previous path too. Otherwise we'll keep it unchanged
|
||||
sameParentDir := len(splitName) == len(splitPrevName) && filepath.Join(splitName[0:depth]...) == filepath.Join(splitPrevName[0:depth]...)
|
||||
sameParentDir := len(splitName) == len(splitPrevName) && join(splitName[0:depth]) == join(splitPrevName[0:depth])
|
||||
if sameParentDir {
|
||||
prevName = filepath.Join(splitPrevName[depth:]...)
|
||||
prevName = join(splitPrevName[depth:])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s%s", prevName, " → ", name)
|
||||
|
||||
@@ -515,15 +515,6 @@ func (gui *Gui) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !gui.Config.GetUserConfig().DisableStartupPopups {
|
||||
popupTasks := []func(chan struct{}) error{}
|
||||
storedPopupVersion := gui.Config.GetAppState().StartupPopupVersion
|
||||
if storedPopupVersion < StartupPopupVersion {
|
||||
popupTasks = append(popupTasks, gui.showIntroPopupMessage)
|
||||
}
|
||||
gui.showInitialPopups(popupTasks)
|
||||
}
|
||||
|
||||
gui.waitForIntro.Add(1)
|
||||
if gui.Config.GetUserConfig().Git.AutoFetch {
|
||||
go utils.Safe(gui.startBackgroundFetch)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !windows
|
||||
|
||||
package gui
|
||||
|
||||
import (
|
||||
|
||||
@@ -24,21 +24,140 @@ func (gui *Gui) informationStr() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) createAllViews() error {
|
||||
viewNameMappings := []struct {
|
||||
viewPtr **gocui.View
|
||||
name string
|
||||
}{
|
||||
{viewPtr: &gui.Views.Status, name: "status"},
|
||||
{viewPtr: &gui.Views.Files, name: "files"},
|
||||
{viewPtr: &gui.Views.Branches, name: "branches"},
|
||||
{viewPtr: &gui.Views.Commits, name: "commits"},
|
||||
{viewPtr: &gui.Views.Stash, name: "stash"},
|
||||
{viewPtr: &gui.Views.CommitFiles, name: "commitFiles"},
|
||||
{viewPtr: &gui.Views.Main, name: "main"},
|
||||
{viewPtr: &gui.Views.Secondary, name: "secondary"},
|
||||
{viewPtr: &gui.Views.Options, name: "options"},
|
||||
{viewPtr: &gui.Views.AppStatus, name: "appStatus"},
|
||||
{viewPtr: &gui.Views.Information, name: "information"},
|
||||
{viewPtr: &gui.Views.Search, name: "search"},
|
||||
{viewPtr: &gui.Views.SearchPrefix, name: "searchPrefix"},
|
||||
{viewPtr: &gui.Views.CommitMessage, name: "commitMessage"},
|
||||
{viewPtr: &gui.Views.Credentials, name: "credentials"},
|
||||
{viewPtr: &gui.Views.Menu, name: "menu"},
|
||||
{viewPtr: &gui.Views.Suggestions, name: "suggestions"},
|
||||
{viewPtr: &gui.Views.Confirmation, name: "confirmation"},
|
||||
{viewPtr: &gui.Views.Limit, name: "limit"},
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, mapping := range viewNameMappings {
|
||||
*mapping.viewPtr, err = gui.prepareView(mapping.name)
|
||||
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.Views.Options.Frame = false
|
||||
gui.Views.Options.FgColor = theme.OptionsColor
|
||||
|
||||
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
|
||||
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
|
||||
gui.Views.SearchPrefix.Frame = false
|
||||
gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
|
||||
|
||||
gui.Views.Stash.Title = gui.Tr.StashTitle
|
||||
gui.Views.Stash.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Stash.ContainsList = true
|
||||
|
||||
gui.Views.Commits.Title = gui.Tr.CommitsTitle
|
||||
gui.Views.Commits.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Commits.ContainsList = true
|
||||
|
||||
gui.Views.CommitFiles.Title = gui.Tr.CommitFiles
|
||||
gui.Views.CommitFiles.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.CommitFiles.ContainsList = true
|
||||
|
||||
gui.Views.Branches.Title = gui.Tr.BranchesTitle
|
||||
gui.Views.Branches.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Branches.ContainsList = true
|
||||
|
||||
gui.Views.Files.Highlight = true
|
||||
gui.Views.Files.Title = gui.Tr.FilesTitle
|
||||
gui.Views.Files.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Files.ContainsList = true
|
||||
|
||||
gui.Views.Secondary.Title = gui.Tr.DiffTitle
|
||||
gui.Views.Secondary.Wrap = true
|
||||
gui.Views.Secondary.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Secondary.IgnoreCarriageReturns = true
|
||||
|
||||
gui.Views.Main.Title = gui.Tr.DiffTitle
|
||||
gui.Views.Main.Wrap = true
|
||||
gui.Views.Main.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Main.IgnoreCarriageReturns = true
|
||||
|
||||
gui.Views.Limit.Title = gui.Tr.NotEnoughSpace
|
||||
gui.Views.Limit.Wrap = true
|
||||
|
||||
gui.Views.Status.Title = gui.Tr.StatusTitle
|
||||
gui.Views.Status.FgColor = theme.GocuiDefaultTextColor
|
||||
|
||||
gui.Views.Search.BgColor = gocui.ColorDefault
|
||||
gui.Views.Search.FgColor = gocui.ColorGreen
|
||||
gui.Views.Search.Frame = false
|
||||
gui.Views.Search.Editable = true
|
||||
|
||||
gui.Views.AppStatus.BgColor = gocui.ColorDefault
|
||||
gui.Views.AppStatus.FgColor = gocui.ColorCyan
|
||||
gui.Views.AppStatus.Frame = false
|
||||
gui.Views.AppStatus.Visible = false
|
||||
|
||||
gui.Views.CommitMessage.Visible = false
|
||||
gui.Views.CommitMessage.Title = gui.Tr.CommitMessage
|
||||
gui.Views.CommitMessage.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.CommitMessage.Editable = true
|
||||
gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor)
|
||||
|
||||
gui.Views.Confirmation.Visible = false
|
||||
|
||||
gui.Views.Credentials.Visible = false
|
||||
gui.Views.Credentials.Title = gui.Tr.CredentialsUsername
|
||||
gui.Views.Credentials.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Credentials.Editable = true
|
||||
|
||||
gui.Views.Suggestions.Visible = false
|
||||
|
||||
gui.Views.Menu.Visible = false
|
||||
|
||||
gui.Views.Information.BgColor = gocui.ColorDefault
|
||||
gui.Views.Information.FgColor = gocui.ColorGreen
|
||||
gui.Views.Information.Frame = false
|
||||
|
||||
if _, err := gui.g.SetCurrentView(gui.defaultSideContext().GetViewName()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// layout is called for every screen re-render e.g. when the screen is resized
|
||||
func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
if !gui.ViewsSetup {
|
||||
if err := gui.createAllViews(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
g.Highlight = true
|
||||
width, height := g.Size()
|
||||
|
||||
minimumHeight := 9
|
||||
minimumWidth := 10
|
||||
var err error
|
||||
gui.Views.Limit, err = g.SetView("limit", 0, 0, width-1, height-1, 0)
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Limit.Title = gui.Tr.NotEnoughSpace
|
||||
gui.Views.Limit.Wrap = true
|
||||
_, err = g.SetView("limit", 0, 0, width-1, height-1, 0)
|
||||
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Limit.Visible = height < minimumHeight || width < minimumWidth
|
||||
|
||||
@@ -98,140 +217,36 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return view, err
|
||||
}
|
||||
|
||||
gui.Views.Main, err = setViewFromDimensions("main", "main", true)
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Main.Title = gui.Tr.DiffTitle
|
||||
gui.Views.Main.Wrap = true
|
||||
gui.Views.Main.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Main.IgnoreCarriageReturns = true
|
||||
args := []struct {
|
||||
viewName string
|
||||
windowName string
|
||||
frame bool
|
||||
}{
|
||||
{viewName: "main", windowName: "main", frame: true},
|
||||
{viewName: "secondary", windowName: "secondary", frame: true},
|
||||
{viewName: "status", windowName: "status", frame: true},
|
||||
{viewName: "files", windowName: "files", frame: true},
|
||||
{viewName: "branches", windowName: "branches", frame: true},
|
||||
{viewName: "commitFiles", windowName: gui.State.Contexts.CommitFiles.GetWindowName(), frame: true},
|
||||
{viewName: "commits", windowName: "commits", frame: true},
|
||||
{viewName: "stash", windowName: "stash", frame: true},
|
||||
{viewName: "options", windowName: "options", frame: false},
|
||||
{viewName: "searchPrefix", windowName: "searchPrefix", frame: false},
|
||||
{viewName: "search", windowName: "search", frame: false},
|
||||
{viewName: "appStatus", windowName: "appStatus", frame: false},
|
||||
{viewName: "information", windowName: "information", frame: false},
|
||||
}
|
||||
|
||||
gui.Views.Secondary, err = setViewFromDimensions("secondary", "secondary", true)
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
for _, arg := range args {
|
||||
_, err = setViewFromDimensions(arg.viewName, arg.windowName, arg.frame)
|
||||
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Secondary.Title = gui.Tr.DiffTitle
|
||||
gui.Views.Secondary.Wrap = true
|
||||
gui.Views.Secondary.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Secondary.IgnoreCarriageReturns = true
|
||||
}
|
||||
|
||||
if gui.Views.Status, err = setViewFromDimensions("status", "status", true); err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Status.Title = gui.Tr.StatusTitle
|
||||
gui.Views.Status.FgColor = theme.GocuiDefaultTextColor
|
||||
}
|
||||
|
||||
gui.Views.Files, err = setViewFromDimensions("files", "files", true)
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Files.Highlight = true
|
||||
gui.Views.Files.Title = gui.Tr.FilesTitle
|
||||
gui.Views.Files.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Files.ContainsList = true
|
||||
}
|
||||
|
||||
gui.Views.Branches, err = setViewFromDimensions("branches", "branches", true)
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Branches.Title = gui.Tr.BranchesTitle
|
||||
gui.Views.Branches.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Branches.ContainsList = true
|
||||
}
|
||||
|
||||
gui.Views.CommitFiles, err = setViewFromDimensions("commitFiles", gui.State.Contexts.CommitFiles.GetWindowName(), true)
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.CommitFiles.Title = gui.Tr.CommitFiles
|
||||
gui.Views.CommitFiles.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.CommitFiles.ContainsList = true
|
||||
}
|
||||
// if the commit files view is the view to be displayed for its window, we'll display it
|
||||
gui.Views.CommitFiles.Visible = gui.getViewNameForWindow(gui.State.Contexts.CommitFiles.GetWindowName()) == "commitFiles"
|
||||
|
||||
gui.Views.Commits, err = setViewFromDimensions("commits", "commits", true)
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Commits.Title = gui.Tr.CommitsTitle
|
||||
gui.Views.Commits.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Commits.ContainsList = true
|
||||
}
|
||||
|
||||
gui.Views.Stash, err = setViewFromDimensions("stash", "stash", true)
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Stash.Title = gui.Tr.StashTitle
|
||||
gui.Views.Stash.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Stash.ContainsList = true
|
||||
}
|
||||
|
||||
if gui.Views.Options, err = setViewFromDimensions("options", "options", false); err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Options.Frame = false
|
||||
gui.Views.Options.FgColor = theme.OptionsColor
|
||||
}
|
||||
|
||||
// this view takes up one character. Its only purpose is to show the slash when searching
|
||||
if gui.Views.SearchPrefix, err = setViewFromDimensions("searchPrefix", "searchPrefix", false); err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
|
||||
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
|
||||
gui.Views.SearchPrefix.Frame = false
|
||||
gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
|
||||
}
|
||||
|
||||
if gui.Views.Search, err = setViewFromDimensions("search", "search", false); err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.Views.Search.BgColor = gocui.ColorDefault
|
||||
gui.Views.Search.FgColor = gocui.ColorGreen
|
||||
gui.Views.Search.Frame = false
|
||||
gui.Views.Search.Editable = true
|
||||
}
|
||||
|
||||
if gui.Views.AppStatus, err = setViewFromDimensions("appStatus", "appStatus", false); err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.AppStatus.BgColor = gocui.ColorDefault
|
||||
gui.Views.AppStatus.FgColor = gocui.ColorCyan
|
||||
gui.Views.AppStatus.Frame = false
|
||||
gui.Views.AppStatus.Visible = false
|
||||
}
|
||||
|
||||
gui.Views.Information, err = setViewFromDimensions("information", "information", false)
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Information.BgColor = gocui.ColorDefault
|
||||
gui.Views.Information.FgColor = gocui.ColorGreen
|
||||
gui.Views.Information.Frame = false
|
||||
gui.renderString(gui.Views.Information, INFO_SECTION_PADDING+informationStr)
|
||||
}
|
||||
if gui.State.OldInformation != informationStr {
|
||||
gui.setViewContent(gui.Views.Information, informationStr)
|
||||
gui.State.OldInformation = informationStr
|
||||
@@ -291,7 +306,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return gui.resizeCurrentPopupPanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) setHiddenView(viewName string) (*gocui.View, error) {
|
||||
func (gui *Gui) prepareView(viewName string) (*gocui.View, error) {
|
||||
// arbitrarily giving the view enough size so that we don't get an error, but
|
||||
// it's expected that the view will be given the correct size before being shown
|
||||
return gui.g.SetView(viewName, 0, 0, 10, 10, 0)
|
||||
@@ -317,11 +332,6 @@ func (gui *Gui) onInitialViewsCreationForRepo() error {
|
||||
}
|
||||
|
||||
func (gui *Gui) onInitialViewsCreation() error {
|
||||
// creating some views which are hidden at the start but we need to exist so that we can set an initial ordering
|
||||
if err := gui.createHiddenViews(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now we order the views (in order of bottom first)
|
||||
layerOneViews := []*gocui.View{
|
||||
// first layer. Ordering within this layer does not matter because there are
|
||||
@@ -340,15 +350,15 @@ func (gui *Gui) onInitialViewsCreation() error {
|
||||
gui.Views.AppStatus,
|
||||
gui.Views.Information,
|
||||
gui.Views.Search,
|
||||
gui.Views.SearchPrefix,
|
||||
gui.Views.SearchPrefix, // this view takes up one character. Its only purpose is to show the slash when searching
|
||||
|
||||
// popups. Ordering within this layer does not matter because there should
|
||||
// only be one popup shown at a time
|
||||
gui.Views.CommitMessage,
|
||||
gui.Views.Credentials,
|
||||
gui.Views.Menu,
|
||||
gui.Views.Suggestions,
|
||||
gui.Views.Confirmation,
|
||||
gui.Views.Credentials,
|
||||
|
||||
// this guy will cover everything else when it appears
|
||||
gui.Views.Limit,
|
||||
@@ -375,6 +385,15 @@ func (gui *Gui) onInitialViewsCreation() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !gui.Config.GetUserConfig().DisableStartupPopups {
|
||||
popupTasks := []func(chan struct{}) error{}
|
||||
storedPopupVersion := gui.Config.GetAppState().StartupPopupVersion
|
||||
if storedPopupVersion < StartupPopupVersion {
|
||||
popupTasks = append(popupTasks, gui.showIntroPopupMessage)
|
||||
}
|
||||
gui.showInitialPopups(popupTasks)
|
||||
}
|
||||
|
||||
if gui.showRecentRepos {
|
||||
if err := gui.handleCreateRecentReposMenu(); err != nil {
|
||||
return err
|
||||
@@ -388,58 +407,3 @@ func (gui *Gui) onInitialViewsCreation() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) createHiddenViews() error {
|
||||
// doesn't matter where this view starts because it will be hidden
|
||||
var err error
|
||||
if gui.Views.CommitMessage, err = gui.setHiddenView("commitMessage"); err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.CommitMessage.Visible = false
|
||||
gui.Views.CommitMessage.Title = gui.Tr.CommitMessage
|
||||
gui.Views.CommitMessage.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.CommitMessage.Editable = true
|
||||
gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor)
|
||||
}
|
||||
|
||||
// doesn't matter where this view starts because it will be hidden
|
||||
if gui.Views.Credentials, err = gui.setHiddenView("credentials"); err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Credentials.Visible = false
|
||||
gui.Views.Credentials.Title = gui.Tr.CredentialsUsername
|
||||
gui.Views.Credentials.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Credentials.Editable = true
|
||||
}
|
||||
|
||||
// not worrying about setting attributes because that will be done when the view is actually shown
|
||||
gui.Views.Confirmation, err = gui.setHiddenView("confirmation")
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Confirmation.Visible = false
|
||||
}
|
||||
|
||||
// not worrying about setting attributes because that will be done when the view is actually shown
|
||||
gui.Views.Suggestions, err = gui.setHiddenView("suggestions")
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Suggestions.Visible = false
|
||||
}
|
||||
|
||||
// not worrying about setting attributes because that will be done when the view is actually shown
|
||||
gui.Views.Menu, err = gui.setHiddenView("menu")
|
||||
if err != nil {
|
||||
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
gui.Views.Menu.Visible = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -83,23 +83,13 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
|
||||
gui.Log,
|
||||
view,
|
||||
func() {
|
||||
// we could clear here, but that actually has the effect of causing a flicker
|
||||
// where the view may contain no content momentarily as the gui refreshes.
|
||||
// Instead, we're rewinding the write pointer so that we will just start
|
||||
// overwriting the existing content from the top down. Once we've reached
|
||||
// the end of the content do display, we call view.FlushStaleCells() to
|
||||
// clear out the remaining content from the previous render.
|
||||
view.Rewind()
|
||||
view.Clear()
|
||||
},
|
||||
func() {
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
return nil
|
||||
})
|
||||
},
|
||||
func() {
|
||||
view.FlushStaleCells()
|
||||
},
|
||||
)
|
||||
})
|
||||
gui.viewBufferManagerMap[view.Name()] = manager
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ func RunTests(
|
||||
logf("path: %s", testPath)
|
||||
|
||||
// three retries at normal speed for the sake of flakey tests
|
||||
speeds = append(speeds, 1, 0.5, 0.5)
|
||||
speeds = append(speeds, 1)
|
||||
for i, speed := range speeds {
|
||||
logf("%s: attempting test at speed %f\n", test.Name, speed)
|
||||
|
||||
|
||||
@@ -33,13 +33,12 @@ type ViewBufferManager struct {
|
||||
readLines chan int
|
||||
|
||||
// beforeStart is the function that is called before starting a new task
|
||||
beforeStart func()
|
||||
refreshView func()
|
||||
flushStaleCells func()
|
||||
beforeStart func()
|
||||
refreshView func()
|
||||
}
|
||||
|
||||
func NewViewBufferManager(log *logrus.Entry, writer io.Writer, beforeStart func(), refreshView func(), flushStaleCells func()) *ViewBufferManager {
|
||||
return &ViewBufferManager{Log: log, writer: writer, beforeStart: beforeStart, refreshView: refreshView, flushStaleCells: flushStaleCells, readLines: make(chan int, 1024)}
|
||||
func NewViewBufferManager(log *logrus.Entry, writer io.Writer, beforeStart func(), refreshView func()) *ViewBufferManager {
|
||||
return &ViewBufferManager{Log: log, writer: writer, beforeStart: beforeStart, refreshView: refreshView, readLines: make(chan int, 1024)}
|
||||
}
|
||||
|
||||
func (m *ViewBufferManager) ReadLines(n int) {
|
||||
@@ -76,7 +75,7 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string
|
||||
loaded := false
|
||||
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(time.Millisecond * 200)
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
defer ticker.Stop()
|
||||
select {
|
||||
case <-ticker.C:
|
||||
@@ -115,9 +114,6 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string
|
||||
default:
|
||||
}
|
||||
if !ok {
|
||||
// if we're here then there's nothing left to scan from the source
|
||||
// so we're at the EOF and can flush the stale content
|
||||
m.flushStaleCells()
|
||||
m.refreshView()
|
||||
break outer
|
||||
}
|
||||
|
||||
0
test/integration/initialOpen/config/config.yml
Normal file
0
test/integration/initialOpen/config/config.yml
Normal file
@@ -0,0 +1 @@
|
||||
asd
|
||||
1
test/integration/initialOpen/expected/.git_keep/HEAD
Normal file
1
test/integration/initialOpen/expected/.git_keep/HEAD
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
10
test/integration/initialOpen/expected/.git_keep/config
Normal file
10
test/integration/initialOpen/expected/.git_keep/config
Normal file
@@ -0,0 +1,10 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[user]
|
||||
email = CI@example.com
|
||||
name = CI
|
||||
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
BIN
test/integration/initialOpen/expected/.git_keep/index
Normal file
BIN
test/integration/initialOpen/expected/.git_keep/index
Normal file
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
||||
.DS_Store
|
||||
@@ -0,0 +1,2 @@
|
||||
0000000000000000000000000000000000000000 46f86259c48ec60496e43d9c962e32f40e7cdefb CI <CI@example.com> 1617799345 +1000 commit (initial): myfile1
|
||||
46f86259c48ec60496e43d9c962e32f40e7cdefb e4776798a2a73374b45e6321b60b5578b9fb590c CI <CI@example.com> 1617799348 +1000 commit: asd
|
||||
@@ -0,0 +1,2 @@
|
||||
0000000000000000000000000000000000000000 46f86259c48ec60496e43d9c962e32f40e7cdefb CI <CI@example.com> 1617799345 +1000 commit (initial): myfile1
|
||||
46f86259c48ec60496e43d9c962e32f40e7cdefb e4776798a2a73374b45e6321b60b5578b9fb590c CI <CI@example.com> 1617799348 +1000 commit: asd
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
x<01>ÎM
|
||||
1@a×=E÷‚¤iš6 "¸šcô'EÁq†±‚ÇwŽàöñ-^]æù1¬8ŒMÕ2zˆÁµ–¢SHHÑ7Œ\‚v-T™5oú–¸'Æ •’VVòMª0ªÇN ±6íÅäϸ/›½Mö|›®úÍóúÔS]æ‹uìbñ”ìÑ€Ùë>5ôOnò»™¨q8™
|
||||
@@ -0,0 +1 @@
|
||||
e4776798a2a73374b45e6321b60b5578b9fb590c
|
||||
1
test/integration/initialOpen/expected/myfile1
Normal file
1
test/integration/initialOpen/expected/myfile1
Normal file
@@ -0,0 +1 @@
|
||||
test1
|
||||
1
test/integration/initialOpen/expected/myfile2
Normal file
1
test/integration/initialOpen/expected/myfile2
Normal file
@@ -0,0 +1 @@
|
||||
test1
|
||||
1
test/integration/initialOpen/recording.json
Normal file
1
test/integration/initialOpen/recording.json
Normal file
@@ -0,0 +1 @@
|
||||
{"KeyEvents":[{"Timestamp":891,"Mod":0,"Key":27,"Ch":0},{"Timestamp":1344,"Mod":0,"Key":256,"Ch":32},{"Timestamp":1639,"Mod":0,"Key":256,"Ch":99},{"Timestamp":2128,"Mod":0,"Key":256,"Ch":97},{"Timestamp":2176,"Mod":0,"Key":256,"Ch":115},{"Timestamp":2280,"Mod":0,"Key":256,"Ch":100},{"Timestamp":2592,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2960,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]}
|
||||
14
test/integration/initialOpen/setup.sh
Normal file
14
test/integration/initialOpen/setup.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd $1
|
||||
|
||||
git init
|
||||
|
||||
git config user.email "CI@example.com"
|
||||
git config user.name "CI"
|
||||
|
||||
echo test1 > myfile1
|
||||
git add .
|
||||
git commit -am "myfile1"
|
||||
|
||||
echo test1 > myfile2
|
||||
1
test/integration/initialOpen/test.json
Normal file
1
test/integration/initialOpen/test.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "description": "testing the initial popup appears when first starting lazygit", "speed": 15 }
|
||||
43
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
43
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@@ -518,20 +518,16 @@ func (v *View) writeCells(x, y int, cells []cell) {
|
||||
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
|
||||
// be called to clear the view's buffer.
|
||||
func (v *View) Write(p []byte) (n int, err error) {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
v.tainted = true
|
||||
v.writeMutex.Lock()
|
||||
v.makeWriteable(v.wx, v.wy)
|
||||
v.writeRunes(bytes.Runes(p))
|
||||
v.writeMutex.Unlock()
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (v *View) WriteRunes(p []rune) {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
v.tainted = true
|
||||
|
||||
// Fill with empty cells, if writing outside current view buffer
|
||||
@@ -549,8 +545,6 @@ func (v *View) writeRunes(p []rune) {
|
||||
for _, r := range p {
|
||||
switch r {
|
||||
case '\n':
|
||||
// clear the rest of the line
|
||||
v.lines[v.wy] = v.lines[v.wy][0:v.wx]
|
||||
v.wy++
|
||||
if v.wy >= len(v.lines) {
|
||||
v.lines = append(v.lines, nil)
|
||||
@@ -648,15 +642,6 @@ func (v *View) Read(p []byte) (n int, err error) {
|
||||
|
||||
// Rewind sets read and write pos to (0, 0).
|
||||
func (v *View) Rewind() {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
v.rewind()
|
||||
}
|
||||
|
||||
func (v *View) rewind() {
|
||||
v.ei.reset()
|
||||
|
||||
if err := v.SetReadPos(0, 0); err != nil {
|
||||
// SetReadPos returns error only if x and y are negative
|
||||
// we are passing 0, 0, thus no error should occur.
|
||||
@@ -847,31 +832,13 @@ func (v *View) realPosition(vx, vy int) (x, y int, err error) {
|
||||
// And resets reading and writing offsets.
|
||||
func (v *View) Clear() {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
v.rewind()
|
||||
v.Rewind()
|
||||
v.tainted = true
|
||||
v.ei.reset()
|
||||
v.lines = nil
|
||||
v.viewLines = nil
|
||||
v.clearRunes()
|
||||
}
|
||||
|
||||
// This is for when we've done a rewind for the sake of avoiding a flicker and
|
||||
// we've reached the end of the new content to display: we need to clear the remaining
|
||||
// content from the previous round.
|
||||
func (v *View) FlushStaleCells() {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
// need to wipe the end of the current line and all following lines
|
||||
if len(v.lines) > 0 && v.wy < len(v.lines) {
|
||||
// why this needs to be +1 but the 0:v.wx part doesn't, I'm not sure
|
||||
v.lines = v.lines[0 : v.wy+1]
|
||||
|
||||
if len(v.lines[v.wy]) > 0 && v.wx < len(v.lines[v.wy]) {
|
||||
v.lines[v.wy] = v.lines[v.wy][0:v.wx]
|
||||
}
|
||||
}
|
||||
v.writeMutex.Unlock()
|
||||
}
|
||||
|
||||
// clearRunes erases all the cells in the view.
|
||||
|
||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -149,7 +149,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20210405093708-e79dab8f7772
|
||||
## explicit
|
||||
github.com/jesseduffield/gocui
|
||||
# github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe
|
||||
|
||||
Reference in New Issue
Block a user