summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-12-05 19:33:46 +1100
committerJesse Duffield <jessedduffield@gmail.com>2018-12-05 19:33:46 +1100
commitc0f9795910dd840fb83e6992f7f59c77ec4c13fc (patch)
tree05fe245b822008f458025a5dd75cae384bfda845 /pkg
parent658e5a9faf8409c62f11f3ad6d636d0255e450f4 (diff)
staging lines and hunks
Diffstat (limited to 'pkg')
-rw-r--r--pkg/commands/git.go21
-rw-r--r--pkg/commands/git_test.go79
-rw-r--r--pkg/commands/os.go26
-rw-r--r--pkg/commands/os_test.go32
-rw-r--r--pkg/git/patch_modifier.go59
-rw-r--r--pkg/git/patch_modifier_test.go25
-rw-r--r--pkg/git/patch_parser.go9
-rw-r--r--pkg/git/patch_parser_test.go65
-rw-r--r--pkg/git/testdata/addedFile.diff7
-rw-r--r--pkg/git/testdata/testPatchAfter3.diff25
-rw-r--r--pkg/git/testdata/testPatchAfter4.diff19
-rw-r--r--pkg/git/testdata/testPatchBefore2.diff57
-rw-r--r--pkg/gui/confirmation_panel.go12
-rw-r--r--pkg/gui/files_panel.go7
-rw-r--r--pkg/gui/gui.go1
-rw-r--r--pkg/gui/keybindings.go55
-rw-r--r--pkg/gui/staging_panel.go166
-rw-r--r--pkg/gui/view_helpers.go1
-rw-r--r--pkg/i18n/english.go15
-rw-r--r--pkg/utils/utils.go21
-rw-r--r--pkg/utils/utils_test.go92
21 files changed, 699 insertions, 95 deletions
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 262a57c2f..1d8e5a10d 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -3,7 +3,6 @@ package commands
import (
"errors"
"fmt"
- "io/ioutil"
"os"
"os/exec"
"strings"
@@ -486,7 +485,6 @@ func (c *GitCommand) getMergeBase() (string, error) {
output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git merge-base HEAD %s", baseBranch))
if err != nil {
// swallowing error because it's not a big deal; probably because there are no commits yet
- c.Log.Error(err)
}
return output, nil
}
@@ -595,24 +593,13 @@ func (c *GitCommand) Diff(file *File, plain bool) string {
}
func (c *GitCommand) ApplyPatch(patch string) (string, error) {
-
- content := []byte(patch)
- tmpfile, err := ioutil.TempFile("", "patch")
+ filename, err := c.OSCommand.CreateTempFile("patch", patch)
if err != nil {
c.Log.Error(err)
- return "", errors.New("Could not create patch file") // TODO: i18n
- }
-
- defer os.Remove(tmpfile.Name()) // clean up
-
- if _, err := tmpfile.Write(content); err != nil {
- c.Log.Error(err)
- return "", err
- }
- if err := tmpfile.Close(); err != nil {
- c.Log.Error(err)
return "", err
}
- return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply --cached %s", tmpfile.Name()))
+ defer func() { _ = c.OSCommand.RemoveFile(filename) }()
+
+ return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply --cached %s", filename))
}
diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go
index 0e1390535..206696b22 100644
--- a/pkg/commands/git_test.go
+++ b/pkg/commands/git_test.go
@@ -1770,6 +1770,7 @@ func TestGitCommandDiff(t *testing.T) {
testName string
command func(string, ...string) *exec.Cmd
file *File
+ plain bool
}
scenarios := []scenario{
@@ -1786,6 +1787,22 @@ func TestGitCommandDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
+ false,
+ },
+ {
+ "Default case",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"diff", "--", "test.txt"}, args)
+
+ return exec.Command("echo")
+ },
+ &File{
+ Name: "test.txt",
+ HasStagedChanges: false,
+ Tracked: true,
+ },
+ true,
},
{
"All changes staged",
@@ -1801,6 +1818,7 @@ func TestGitCommandDiff(t *testing.T) {
HasUnstagedChanges: false,
Tracked: true,
},
+ false,
},
{
"File not tracked and file has no staged changes",
@@ -1815,6 +1833,7 @@ func TestGitCommandDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: false,
},
+ false,
},
}
@@ -1822,7 +1841,7 @@ func TestGitCommandDiff(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = s.command
- gitCmd.Diff(s.file, false)
+ gitCmd.Diff(s.file, s.plain)
})
}
}
@@ -1979,3 +1998,61 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
})
}
}
+
+func TestGitCommandApplyPatch(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func(string, ...string) *exec.Cmd
+ test func(string, error)
+ }
+
+ scenarios := []scenario{
+ {
+ "valid case",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.Equal(t, "git", cmd)
+ assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2])
+ filename := args[2]
+ content, err := ioutil.ReadFile(filename)
+ assert.NoError(t, err)
+
+ assert.Equal(t, "test", string(content))
+
+ return exec.Command("echo", "done")
+ },
+ func(output string, err error) {
+ assert.NoError(t, err)
+ assert.EqualValues(t, "done\n", output)
+ },
+ },
+ {
+ "command returns error",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.Equal(t, "git", cmd)
+ assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2])
+ filename := args[2]
+ // TODO: Ideally we want to mock out OSCommand here so that we're not
+ // double handling testing it's CreateTempFile functionality,
+ // but it is going to take a bit of work to make a proper mock for it
+ // so I'm leaving it for another PR
+ content, err := ioutil.ReadFile(filename)
+ assert.NoError(t, err)
+
+ assert.Equal(t, "test", string(content))
+
+ return exec.Command("test")
+ },
+ func(output string, err error) {
+ assert.Error(t, err)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := newDummyGitCommand()
+ gitCmd.OSCommand.command = s.command
+ s.test(gitCmd.ApplyPatch("test"))
+ })
+ }
+}
diff --git a/pkg/commands/os.go b/pkg/commands/os.go
index 8b4b7879e..6b28a69bb 100644
--- a/pkg/commands/os.go
+++ b/pkg/commands/os.go
@@ -2,6 +2,7 @@ package commands
import (
"errors"
+ "io/ioutil"
"os"
"os/exec"
"strings"
@@ -176,3 +177,28 @@ func (c *OSCommand) AppendLineToFile(filename, line string) error {
_, err = f.WriteString("\n" + line)
return err
}
+
+// CreateTempFile writes a string to a new temp file and returns the file's name
+func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
+ tmpfile, err := ioutil.TempFile("", filename)
+ if err != nil {
+ c.Log.Error(err)
+ return "", err
+ }
+
+ if _, err := tmpfile.Write([]byte(content)); err != nil {
+ c.Log.Error(err)
+ return "", err
+ }
+ if err := tmpfile.Close(); err != nil {
+ c.Log.Error(err)
+ return "", err
+ }
+
+ return tmpfile.Name(), nil
+}
+
+// RemoveFile removes a file at the specified path
+func (c *OSCommand) RemoveFile(filename string) error {
+ return os.Remove(filename)
+}
diff --git a/pkg/commands/os_test.go b/pkg/commands/os_test.go
index ebb855cbe..a08c4b57d 100644
--- a/pkg/commands/os_test.go
+++ b/pkg/commands/os_test.go
@@ -1,6 +1,7 @@
package commands
import (
+ "io/ioutil"
"os"
"os/exec"
"testing"
@@ -364,3 +365,34 @@ func TestOSCommandFileType(t *testing.T) {
_ = os.RemoveAll(s.path)
}
}
+
+func TestOSCommandCreateTempFile(t *testing.T) {
+ type scenario struct {
+ testName string
+ filename string
+ content string
+ test func(string, error)
+ }
+
+ scenarios := []scenario{
+ {
+ "valid case",
+ "filename",
+ "content",
+ func(path string, err error) {
+ assert.NoError(t, err)
+
+ content, err := ioutil.ReadFile(path)
+ assert.NoError(t, err)
+
+ assert.Equal(t, "content", string(content))
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ s.test(newDummyOSCommand().CreateTempFile(s.filename, s.content))
+ })
+ }
+}
diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
index 05afcf5ba..3c523232e 100644
--- a/pkg/git/patch_modifier.go
+++ b/pkg/git/patch_modifier.go
@@ -6,11 +6,14 @@ import (
"strconv"
"strings"
+ "github.com/jesseduffield/lazygit/pkg/i18n"
+ "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
)
type PatchModifier struct {
Log *logrus.Entry
+ Tr *i18n.Localizer
}
// NewPatchModifier builds a new branch list builder
@@ -20,11 +23,49 @@ func NewPatchModifier(log *logrus.Entry) (*PatchModifier, error) {
}, nil
}
-// ModifyPatch takes the original patch, which may contain several hunks,
+// ModifyPatchForHunk takes the original patch, which may contain several hunks,
+// and removes any hunks that aren't the selected hunk
+func (p *PatchModifier) ModifyPatchForHunk(patch string, hunkStarts []int, currentLine int) (string, error) {
+ // get hunk start and end
+ lines := strings.Split(patch, "\n")
+ hunkStartIndex := utils.PrevIndex(hunkStarts, currentLine)
+ hunkStart := hunkStarts[hunkStartIndex]
+ nextHunkStartIndex := utils.NextIndex(hunkStarts, currentLine)
+ var hunkEnd int
+ if nextHunkStartIndex == 0 {
+ hunkEnd = len(lines) - 1
+ } else {
+ hunkEnd = hunkStarts[nextHunkStartIndex]
+ }
+
+ headerLength, err := p.getHeaderLength(lines)
+ if err != nil {
+ return "", err
+ }
+
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+ output += strings.Join(lines[hunkStart:hunkEnd], "\n") + "\n"
+
+ return output, nil
+}
+
+func (p *PatchModifier) getHeaderLength(patchLines []string) (int, error) {
+ for index, line := range patchLines {
+ if strings.HasPrefix(line, "@@") {
+ return index, nil
+ }
+ }
+ return 0, errors.New(p.Tr.SLocalize("CantFindHunks"))
+}
+
+// ModifyPatchForLine takes the original patch, which may contain several hunks,
// and the line number of the line we want to stage
-func (p *PatchModifier) ModifyPatch(patch string, lineNumber int) (string, error) {
+func (p *PatchModifier) ModifyPatchForLine(patch string, lineNumber int) (string, error) {
lines := strings.Split(patch, "\n")
- headerLength := 4
+ headerLength, err := p.getHeaderLength(lines)
+ if err != nil {
+ return "", err
+ }
output := strings.Join(lines[0:headerLength], "\n") + "\n"
hunkStart, err := p.getHunkStart(lines, lineNumber)
@@ -55,7 +96,8 @@ func (p *PatchModifier) getHunkStart(patchLines []string, lineNumber int) (int,
return hunkStart, nil
}
}
- return 0, errors.New("Could not find hunk")
+
+ return 0, errors.New(p.Tr.SLocalize("CantFindHunk"))
}
func (p *PatchModifier) getModifiedHunk(patchLines []string, hunkStart int, lineNumber int) ([]string, error) {
@@ -101,13 +143,8 @@ func (p *PatchModifier) getModifiedHunk(patchLines []string, hunkStart int, line
// @@ -14,8 +14,9 @@ import (
func (p *PatchModifier) updatedHeader(currentHeader string, lineChanges int) (string, error) {
// current counter is the number after the second comma
- re := regexp.MustCompile(`^[^,]+,[^,]+,(\d+)`)
- matches := re.FindStringSubmatch(currentHeader)
- if len(matches) < 2 {
- re = regexp.MustCompile(`^[^,]+,[^+]+\+(\d+)`)
- matches = re.FindStringSubmatch(currentHeader)
- }
- prevLengthString := matches[1]
+ re := regexp.MustCompile(`(\d+) @@`)
+ prevLengthString := re.FindStringSubmatch(currentHeader)[1]
prevLength, err := strconv.Atoi(prevLengthString)
if err != nil {
diff --git a/pkg/git/patch_modifier_test.go b/pkg/git/patch_modifier_test.go
index af7be3751..bc2073d55 100644
--- a/pkg/git/patch_modifier_test.go
+++ b/pkg/git/patch_modifier_test.go
@@ -19,7 +19,7 @@ func newDummyPatchModifier() *PatchModifier {
Log: newDummyLog(),
}
}
-func TestModifyPatch(t *testing.T) {
+func TestModifyPatchForLine(t *testing.T) {
type scenario struct {
testName string
patchFilename string
@@ -43,6 +43,27 @@ func TestModifyPatch(t *testing.T) {
false,
"testdata/testPatchAfter2.diff",
},
+ {
+ "Adding one line in top hunk in diff with multiple hunks",
+ "testdata/testPatchBefore2.diff",
+ 20,
+ false,
+ "testdata/testPatchAfter3.diff",
+ },
+ {
+ "Adding one line in top hunk in diff with multiple hunks",
+ "testdata/testPatchBefore2.diff",
+ 53,
+ false,
+ "testdata/testPatchAfter4.diff",
+ },
+ {
+ "adding unstaged file with a single line",
+ "testdata/addedFile.diff",
+ 6,
+ false,
+ "testdata/addedFile.diff",
+ },
}
for _, s := range scenarios {
@@ -52,7 +73,7 @@ func TestModifyPatch(t *testing.T) {
if err != nil {
panic("Cannot open file at " + s.patchFilename)
}
- afterPatch, err := p.ModifyPatch(string(beforePatch), s.lineNumber)
+ afterPatch, err := p.ModifyPatchForLine(string(beforePatch), s.lineNumber)
if s.shouldError {
assert.Error(t, err)
} else {
diff --git a/pkg/git/patch_parser.go b/pkg/git/patch_parser.go
index 8fa4d355b..1dbacd01c 100644
--- a/pkg/git/patch_parser.go
+++ b/pkg/git/patch_parser.go
@@ -21,15 +21,16 @@ func (p *PatchParser) ParsePatch(patch string) ([]int, []int, error) {
lines := strings.Split(patch, "\n")
hunkStarts := []int{}
stageableLines := []int{}
- headerLength := 4
- for offsetIndex, line := range lines[headerLength:] {
- index := offsetIndex + headerLength
+ pastHeader := false
+ for index, line := range lines {
if strings.HasPrefix(line, "@@") {
+ pastHeader = true
hunkStarts = append(hunkStarts, index)
}
- if strings.HasPrefix(line, "-") || strings.HasPrefix(line, "+") {
+ if pastHeader && (strings.HasPrefix(line, "-") || strings.HasPrefix(line, "+")) {
stageableLines = append(stageableLines, index)
}
}
+ p.Log.WithField("staging", "staging").Info(stageableLines)
return hunkStarts, stageableLines, nil
}
diff --git a/pkg/git/patch_parser_test.go b/pkg/git/patch_parser_test.go
new file mode 100644
index 000000000..6670aaea2
--- /dev/null
+++ b/pkg/git/patch_parser_test.go
@@ -0,0 +1,65 @@
+package git
+
+import (
+ "io/ioutil"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func newDummyPatchParser() *PatchParser {
+ return &PatchParser{
+ Log: newDummyLog(),
+ }
+}
+func TestParsePatch(t *testing.T) {
+ type scenario struct {
+ testName string
+ patchFilename string
+ shouldError bool
+ expectedStageableLines []int
+ expectedHunkStarts []int
+ }
+
+ scenarios := []scenario{
+ {
+ "Diff with one hunk",
+ "testdata/testPatchBefore.diff",
+ false,
+ []int{8, 9, 10, 11},
+ []int{4},
+ },
+ {
+ "Diff with two hunks",
+ "testdata/testPatchBefore2.diff",
+ false,
+ []int{8, 9, 10, 11, 12, 13, 20, 21, 22, 23, 24, 25, 26, 27, 28, 33, 34, 35, 36, 37, 45, 46, 47, 48, 49, 50, 51, 52, 53},
+ []int{4, 41},
+ },
+ {
+ "Unstaged file",
+ "testdata/addedFile.diff",
+ false,
+ []int{6},
+ []int{5},
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ p := newDummyPatchParser()
+ beforePatch, err := ioutil.ReadFile(s.patchFilename)
+ if err != nil {
+ panic("Cannot open file at " + s.patchFilename)
+ }
+ hunkStarts, stageableLines, err := p.ParsePatch(string(beforePatch))
+ if s.shouldError {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, s.expectedStageableLines, stageableLines)
+ assert.Equal(t, s.expectedHunkStarts, hunkStarts)
+ }
+ })
+ }
+}
diff --git a/pkg/git/testdata/addedFile.diff b/pkg/git/testdata/addedFile.diff
new file mode 100644
index 000000000..53966c4a1
--- /dev/null
+++ b/pkg/git/testdata/addedFile.diff
@@ -0,0 +1,7 @@
+diff --git a/blah b/blah
+new file mode 100644
+index 0000000..907b308
+--- /dev/null
++++ b/blah
+@@ -0,0 +1 @@
++blah
diff --git a/pkg/git/testdata/testPatchAfter3.diff b/pkg/git/testdata/testPatchAfter3.diff
new file mode 100644
index 000000000..03492450d
--- /dev/null
+++ b/pkg/git/testdata/testPatchAfter3.diff
@@ -0,0 +1,25 @@
+diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
+index a8fc600..6d8f7d7 100644
+--- a/pkg/git/patch_modifier.go
++++ b/pkg/git/patch_modifier.go
+@@ -36,18 +36,19 @@ func (p *PatchModifier) ModifyPatchForHunk(patch string, hunkStarts []int, curre
+ hunkEnd = hunkStarts[nextHunkStartIndex]
+ }
+
+ headerLength := 4
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+ output += strings.Join(lines[hunkStart:hunkEnd], "\n") + "\n"
+
+ return output, nil
+ }
+
++func getHeaderLength(patchLines []string) (int, error) {
+ // ModifyPatchForLine takes the original patch, which may contain several hunks,
+ // and the line number of the line we want to stage
+ func (p *PatchModifier) ModifyPatchForLine(patch string, lineNumber int) (string, error) {
+ lines := strings.Split(patch, "\n")
+ headerLength := 4
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+
+ hunkStart, err := p.getHunkStart(lines, lineNumber)
+
diff --git a/pkg/git/testdata/testPatchAfter4.diff b/pkg/git/testdata/testPatchAfter4.diff
new file mode 100644
index 000000000..99f894d9d
--- /dev/null
+++ b/pkg/git/testdata/testPatchAfter4.diff
@@ -0,0 +1,19 @@
+diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
+index a8fc600..6d8f7d7 100644
+--- a/pkg/git/patch_modifier.go
++++ b/pkg/git/patch_modifier.go
+@@ -124,13 +140,14 @@ func (p *PatchModifier) getModifiedHunk(patchLines []string, hunkStart int, line
+ // @@ -14,8 +14,9 @@ import (
+ func (p *PatchModifier) updatedHeader(currentHeader string, lineChanges int) (string, error) {
+ // current counter is the number after the second comma
+ re := regexp.MustCompile(`^[^,]+,[^,]+,(\d+)`)
+ matches := re.FindStringSubmatch(currentHeader)
+ if len(matches) < 2 {
+ re = regexp.MustCompile(`^[^,]+,[^+]+\+(\d+)`)
+ matches = re.FindStringSubmatch(currentHeader)
+ }
+ prevLengthString := matches[1]
++ prevLengthString := re.FindStringSubmatch(currentHeader)[1]
+
+ prevLength, err := strconv.Atoi(prevLengthString)
+ if err != nil {
diff --git a/pkg/git/testdata/testPatchBefore2.diff b/pkg/git/testdata/testPatchBefore2.diff
new file mode 100644
index 000000000..552c04f5e
--- /dev/null
+++ b/pkg/git/testdata/testPatchBefore2.diff
@@ -0,0 +1,57 @@
+diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
+index a8fc600..6d8f7d7 100644
+--- a/pkg/git/patch_modifier.go
++++ b/pkg/git/patch_modifier.go
+@@ -36,18 +36,34 @@ func (p *PatchModifier) ModifyPatchForHunk(patch string, hunkStarts []int, curre
+ hunkEnd = hunkStarts[nextHunkStartIndex]
+ }
+
+- headerLength := 4
++ headerLength, err := getHeaderLength(lines)
++ if err != nil {
++ return "", err
++ }
++
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+ output += strings.Join(lines[hunkStart:hunkEnd], "\n") + "\n"
+
+ return output, nil
+ }
+
++func getHeaderLength(patchLines []string) (int, error) {
++ for index, line := range patchLines {
++ if strings.HasPrefix(line, "@@") {
++ return index, nil
++ }
++ }
++ return 0, errors.New("Could not find any hunks in this patch")
++}
++
+ // ModifyPatchForLine takes the original patch, which may contain several hunks,
+ // and the line number of the line we want to stage
+ func (p *PatchModifier) ModifyPatchForLine(patch string, lineNumber int) (string, error) {
+ lines := strings.Split(patch, "\n")
+- headerLength := 4
++ headerLength, err := getHeaderLength(lines)
++ if err != nil {
++ return "", err
++ }
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+
+ hunkStart, err := p.getHunkStart(lines, lineNumber)
+@@ -124,13 +140,8 @@ func (p *PatchModifier) getModifiedHunk(patchLines []string, hunkStart int, line
+ // @@ -14,8 +14,9 @@ import (
+ func (p *PatchModifier) updatedHeader(currentHeader string, lineChanges int) (string, error) {
+ // current counter is the number after the second comma
+- re := regexp.MustCompile(`^[^,]+,[^,]+,(\d+)`)
+- matches := re.FindStringSubmatch(currentHeader)
+- if len(matches) < 2 {
+- re = regexp.MustCompile(`^[^,]+,[^+]+\+(\d+)`)
+- matches = re.FindStringSubmatch(currentHeader)
+- }
+- prevLengthString := matches[1]
++ re := regexp.MustCompile(`(\d+) @@`)
++ prevLengthString := re.FindStringSubmatch(currentHeader)[1]
+
+ prevLength, err := strconv.Atoi(prevLengthString)
+ if err != nil {
diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go
index 58577e430..afe53ca2c 100644
--- a/pkg/gui/confirmation_panel.go
+++ b/pkg/gui/confirmation_panel.go
@@ -8,6 +8,7 @@ package gui
import (
"strings"
+ "time"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
@@ -73,6 +74,7 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt
return nil, err
}
confirmationView.Title = title
+ confirmationView.Wrap = true
confirmationView.FgColor = gocui.ColorWhite
}
confirmationView.Clear()
@@ -137,7 +139,15 @@ func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title,
}
func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error {
- gui.Log.Error(message)
+ go func() {
+ // when reporting is switched on this log call sometimes introduces
+ // a delay on the error panel popping up. Here I'm adding a second wait
+ // so that the error is logged while the user is reading the error message
+ time.Sleep(time.Second)
+ gui.Log.Error(message)
+ }()
+
+ // gui.Log.WithField("staging", "staging").Info("creating confirmation panel")
currentView := g.CurrentView()
colorFunction := color.New(color.FgRed).SprintFunc()
coloredMessage := colorFunction(strings.TrimSpace(message))
diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go
index 3404903ef..a16ef4909 100644
--- a/pkg/gui/files_panel.go
+++ b/pkg/gui/files_panel.go
@@ -57,10 +57,13 @@ func (gui *Gui) handleSwitchToStagingPanel(g *gocui.Gui, v *gocui.View) error {
}
return nil
}
- if !file.Tracked || !file.HasUnstagedChanges {
+ if !file.HasUnstagedChanges {
+ gui.Log.WithField("staging", "staging").Info("making error panel")
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileStagingRequirements"))
}
- gui.switchFocus(g, v, stagingView)
+ if err := gui.switchFocus(g, v, stagingView); err != nil {
+ return err
+ }
return gui.refreshStagingPanel()
}
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 67415c5ce..9f25121d5 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -222,7 +222,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
return err
}
v.Title = gui.Tr.SLocalize("StagingTitle")
- v.Wrap = true
v.Highlight = true
v.FgColor = gocui.ColorWhite
if _, err := g.SetViewOnBottom("staging"); err != nil {
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 2c47ab4f3..4158bedb7 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -392,25 +392,64 @@ func (gui *Gui) GetKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleMenuClose,
}, {
- ViewName: "staging",
- Key: gocui.KeyEsc,
- Modifier: gocui.ModNone,
- Handler: gui.handleStagingEscape,
+ ViewName: "staging",
+ Key: gocui.KeyEsc,
+ Modifier: gocui.ModNone,
+ Handler: gui.handleStagingEscape,
+ KeyReadable: "esc",
+ Description: gui.Tr.SLocalize("EscapeStaging"),
}, {
ViewName: "staging",
Key: gocui.KeyArrowUp,
Modifier: gocui.ModNone,
- Handler: gui.handleStagingKeyUp,
+ Handler: gui.handleStagingPrevLine,
}, {
ViewName: "staging",
Key: gocui.KeyArrowDown,
Modifier: gocui.ModNone,
- Handler: gui.handleStagingKeyDown,
+ Handler: gui.handleStagingNextLine,
}, {
ViewName: "staging",
- Key: gocui.KeySpace,
+ Key: 'k',
+ Modifier: gocui.ModNone,
+ Handler: gui.handleStagingPrevLine,
+ }, {
+ ViewName: "staging",
+ Key: 'j',
+ Modifier: gocui.ModNone,
+ Handler: gui.handleStagingNextLine,
+ }, {
+ ViewName: "staging",
+ Key: gocui.KeyArrowLeft,
+ Modifier: gocui.ModNone,
+ Handler: gui.handleStagingPrevHunk,
+ }, {
+ ViewName: "staging",
+ Key: gocui.KeyArrowRight,
+ Modifier: gocui.ModNone,
+ Handler: gui.handleStagingNextHunk,
+ }, {
+ ViewName: "staging",
+ Key: 'h',
+ Modifier: gocui.ModNone,
+ Handler: gui.handleStagingPrevHunk,
+ }, {
+ ViewName: "staging",
+ Key: 'l',
Modifier: gocui.ModNone,
- Handler: gui.handleStageLine,
+ Handler: gui.handleStagingNextHunk,
+ }, {
+ ViewName: "staging",
+ Key: gocui.KeySpace,
+ Modifier: gocui.ModNone,
+ Handler: gui.handleStageLine,
+ Description: gui.Tr.SLocalize("StageLine"),
+ }, {
+ ViewName: "staging",
+ Key: 'a',
+ Modifier: gocui.ModNone,
+ Handler: gui.handleStageHunk,
+ Description: gui.Tr.SLocalize("StageHunk"),
},
}
diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go
index be207c2fb..cba7d7638 100644
--- a/pkg/gui/staging_panel.go
+++ b/pkg/gui/staging_panel.go
@@ -2,19 +2,13 @@ package gui
import (
"errors"
- "io/ioutil"
-
- "github.com/davecgh/go-spew/spew"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/git"
+ "github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) refreshStagingPanel() error {
- // get the currently selected file. Get the diff of that file directly, not
- // using any custom diff tools.
- // parse the file to find out where the chunks and unstaged changes are
-
file, err := gui.getSelectedFile(gui.g)
if err != nil {
if err != gui.Errors.ErrNoFiles {
@@ -31,10 +25,6 @@ func (gui *Gui) refreshStagingPanel() error {
diff := gui.GitCommand.Diff(file, true)
colorDiff := gui.GitCommand.Diff(file, false)
- gui.Log.WithField("staging", "staging").Info("DIFF IS:")
- gui.Log.WithField("staging", "staging").Info(spew.Sdump(diff))
- gui.Log.WithField("staging", "staging").Info("hello")
-
if len(diff) < 2 {
return gui.handleStagingEscape(gui.g, nil)
}
@@ -73,9 +63,9 @@ func (gui *Gui) refreshStagingPanel() error {
return errors.New("No lines to stage")
}
- stagingView := gui.getStagingView(gui.g)
- stagingView.SetCursor(0, stageableLines[currentLineIndex])
- stagingView.SetOrigin(0, 0)
+ if err := gui.focusLineAndHunk(); err != nil {
+