mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-15 00:15:32 +02:00
support split view in staging panel and staging ranges
This commit is contained in:
@ -582,13 +582,13 @@ func (c *GitCommand) CheckRemoteBranchExists(branch *Branch) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Diff returns the diff of a file
|
// Diff returns the diff of a file
|
||||||
func (c *GitCommand) Diff(file *File, plain bool) string {
|
func (c *GitCommand) Diff(file *File, plain bool, cached bool) string {
|
||||||
cachedArg := ""
|
cachedArg := ""
|
||||||
trackedArg := "--"
|
trackedArg := "--"
|
||||||
colorArg := "--color"
|
colorArg := "--color"
|
||||||
split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename
|
split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename
|
||||||
fileName := c.OSCommand.Quote(split[len(split)-1])
|
fileName := c.OSCommand.Quote(split[len(split)-1])
|
||||||
if file.HasStagedChanges && !file.HasUnstagedChanges {
|
if cached {
|
||||||
cachedArg = "--cached"
|
cachedArg = "--cached"
|
||||||
}
|
}
|
||||||
if !file.Tracked && !file.HasStagedChanges {
|
if !file.Tracked && !file.HasStagedChanges {
|
||||||
@ -605,7 +605,7 @@ func (c *GitCommand) Diff(file *File, plain bool) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) ApplyPatch(patch string) (string, error) {
|
func (c *GitCommand) ApplyPatch(patch string, reverse bool, cached bool) (string, error) {
|
||||||
filename, err := c.OSCommand.CreateTempFile("patch", patch)
|
filename, err := c.OSCommand.CreateTempFile("patch", patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Log.Error(err)
|
c.Log.Error(err)
|
||||||
@ -614,7 +614,17 @@ func (c *GitCommand) ApplyPatch(patch string) (string, error) {
|
|||||||
|
|
||||||
defer func() { _ = c.OSCommand.Remove(filename) }()
|
defer func() { _ = c.OSCommand.Remove(filename) }()
|
||||||
|
|
||||||
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply --cached %s", c.OSCommand.Quote(filename)))
|
reverseFlag := ""
|
||||||
|
if reverse {
|
||||||
|
reverseFlag = "--reverse"
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedFlag := ""
|
||||||
|
if cached {
|
||||||
|
cachedFlag = "--cached"
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply %s %s %s", cachedFlag, reverseFlag, c.OSCommand.Quote(filename)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) FastForward(branchName string) error {
|
func (c *GitCommand) FastForward(branchName string) error {
|
||||||
|
@ -1539,6 +1539,7 @@ func TestGitCommandDiff(t *testing.T) {
|
|||||||
command func(string, ...string) *exec.Cmd
|
command func(string, ...string) *exec.Cmd
|
||||||
file *File
|
file *File
|
||||||
plain bool
|
plain bool
|
||||||
|
cached bool
|
||||||
}
|
}
|
||||||
|
|
||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
@ -1556,9 +1557,26 @@ func TestGitCommandDiff(t *testing.T) {
|
|||||||
Tracked: true,
|
Tracked: true,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Default case",
|
"cached",
|
||||||
|
func(cmd string, args ...string) *exec.Cmd {
|
||||||
|
assert.EqualValues(t, "git", cmd)
|
||||||
|
assert.EqualValues(t, []string{"diff", "--color", "--cached", "--", "test.txt"}, args)
|
||||||
|
|
||||||
|
return exec.Command("echo")
|
||||||
|
},
|
||||||
|
&File{
|
||||||
|
Name: "test.txt",
|
||||||
|
HasStagedChanges: false,
|
||||||
|
Tracked: true,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"plain",
|
||||||
func(cmd string, args ...string) *exec.Cmd {
|
func(cmd string, args ...string) *exec.Cmd {
|
||||||
assert.EqualValues(t, "git", cmd)
|
assert.EqualValues(t, "git", cmd)
|
||||||
assert.EqualValues(t, []string{"diff", "--", "test.txt"}, args)
|
assert.EqualValues(t, []string{"diff", "--", "test.txt"}, args)
|
||||||
@ -1571,21 +1589,6 @@ func TestGitCommandDiff(t *testing.T) {
|
|||||||
Tracked: true,
|
Tracked: true,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
|
||||||
{
|
|
||||||
"All changes staged",
|
|
||||||
func(cmd string, args ...string) *exec.Cmd {
|
|
||||||
assert.EqualValues(t, "git", cmd)
|
|
||||||
assert.EqualValues(t, []string{"diff", "--color", "--cached", "--", "test.txt"}, args)
|
|
||||||
|
|
||||||
return exec.Command("echo")
|
|
||||||
},
|
|
||||||
&File{
|
|
||||||
Name: "test.txt",
|
|
||||||
HasStagedChanges: true,
|
|
||||||
HasUnstagedChanges: false,
|
|
||||||
Tracked: true,
|
|
||||||
},
|
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1602,6 +1605,7 @@ func TestGitCommandDiff(t *testing.T) {
|
|||||||
Tracked: false,
|
Tracked: false,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1609,7 +1613,7 @@ func TestGitCommandDiff(t *testing.T) {
|
|||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
gitCmd := NewDummyGitCommand()
|
gitCmd := NewDummyGitCommand()
|
||||||
gitCmd.OSCommand.command = s.command
|
gitCmd.OSCommand.command = s.command
|
||||||
gitCmd.Diff(s.file, s.plain)
|
gitCmd.Diff(s.file, s.plain, s.cached)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1730,7 +1734,7 @@ func TestGitCommandApplyPatch(t *testing.T) {
|
|||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
gitCmd := NewDummyGitCommand()
|
gitCmd := NewDummyGitCommand()
|
||||||
gitCmd.OSCommand.command = s.command
|
gitCmd.OSCommand.command = s.command
|
||||||
s.test(gitCmd.ApplyPatch("test"))
|
s.test(gitCmd.ApplyPatch("test", false, true))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,14 +72,33 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View, alreadySelected bo
|
|||||||
return gui.refreshMergePanel()
|
return gui.refreshMergePanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
content := gui.GitCommand.Diff(file, false)
|
content := gui.GitCommand.Diff(file, false, false)
|
||||||
|
contentCached := gui.GitCommand.Diff(file, false, true)
|
||||||
|
leftContent := content
|
||||||
|
if file.HasStagedChanges && file.HasUnstagedChanges {
|
||||||
|
gui.State.SplitMainPanel = true
|
||||||
|
} else {
|
||||||
|
gui.State.SplitMainPanel = false
|
||||||
|
if file.HasUnstagedChanges {
|
||||||
|
leftContent = content
|
||||||
|
} else {
|
||||||
|
leftContent = contentCached
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if alreadySelected {
|
if alreadySelected {
|
||||||
g.Update(func(*gocui.Gui) error {
|
g.Update(func(*gocui.Gui) error {
|
||||||
return gui.setViewContent(gui.g, gui.getMainView(), content)
|
if err := gui.setViewContent(gui.g, gui.getSecondaryView(), contentCached); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.setViewContent(gui.g, gui.getMainView(), leftContent)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return gui.renderString(g, "main", content)
|
if err := gui.renderString(g, "secondary", contentCached); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.renderString(g, "main", leftContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshFiles() error {
|
func (gui *Gui) refreshFiles() error {
|
||||||
@ -180,7 +199,7 @@ func (gui *Gui) handleEnterFile(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if file.HasInlineMergeConflicts {
|
if file.HasInlineMergeConflicts {
|
||||||
return gui.handleSwitchToMerge(g, v)
|
return gui.handleSwitchToMerge(g, v)
|
||||||
}
|
}
|
||||||
if !file.HasUnstagedChanges || file.HasMergeConflicts {
|
if file.HasMergeConflicts {
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileStagingRequirements"))
|
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileStagingRequirements"))
|
||||||
}
|
}
|
||||||
if err := gui.changeContext("main", "staging"); err != nil {
|
if err := gui.changeContext("main", "staging"); err != nil {
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/config"
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/git"
|
||||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||||
@ -83,10 +84,13 @@ type Gui struct {
|
|||||||
// non-mutative, so that we don't accidentally end up
|
// non-mutative, so that we don't accidentally end up
|
||||||
// with mismatches of data. We might change this in the future
|
// with mismatches of data. We might change this in the future
|
||||||
type stagingPanelState struct {
|
type stagingPanelState struct {
|
||||||
SelectedLine int
|
SelectedLineIdx int
|
||||||
StageableLines []int
|
FirstLineIdx int
|
||||||
HunkStarts []int
|
LastLineIdx int
|
||||||
Diff string
|
Diff string
|
||||||
|
PatchParser *git.PatchParser
|
||||||
|
SelectMode int // one of LINE, HUNK, or RANGE
|
||||||
|
IndexFocused bool // this is for if we show the left or right panel
|
||||||
}
|
}
|
||||||
|
|
||||||
type mergingPanelState struct {
|
type mergingPanelState struct {
|
||||||
@ -147,6 +151,7 @@ type guiState struct {
|
|||||||
WorkingTreeState string // one of "merging", "rebasing", "normal"
|
WorkingTreeState string // one of "merging", "rebasing", "normal"
|
||||||
Contexts map[string]string
|
Contexts map[string]string
|
||||||
CherryPickedCommits []*commands.Commit
|
CherryPickedCommits []*commands.Commit
|
||||||
|
SplitMainPanel bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGui builds a new gui handler
|
// NewGui builds a new gui handler
|
||||||
@ -258,6 +263,7 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
|
|||||||
if v == nil {
|
if v == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
gui.State.SplitMainPanel = false
|
||||||
if v.Name() == "branches" {
|
if v.Name() == "branches" {
|
||||||
// This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus
|
// This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus
|
||||||
// inside renderListPanel it checks to see if the panel has focus
|
// inside renderListPanel it checks to see if the panel has focus
|
||||||
@ -359,7 +365,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
|
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
|
||||||
leftSideWidth := width / 3
|
|
||||||
|
|
||||||
appStatus := gui.statusManager.getStatusString()
|
appStatus := gui.statusManager.getStatusString()
|
||||||
appStatusOptionsBoundary := 0
|
appStatusOptionsBoundary := 0
|
||||||
@ -376,7 +381,23 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
g.DeleteView("limit")
|
g.DeleteView("limit")
|
||||||
|
|
||||||
textColor := theme.GocuiDefaultTextColor
|
textColor := theme.GocuiDefaultTextColor
|
||||||
v, err := g.SetView("main", leftSideWidth+panelSpacing, 0, width-1, height-2, gocui.LEFT)
|
leftSideWidth := width / 3
|
||||||
|
panelSplitX := width - 1
|
||||||
|
if gui.State.SplitMainPanel {
|
||||||
|
units := 7
|
||||||
|
leftSideWidth = width / units
|
||||||
|
panelSplitX = (1 + ((units - 1) / 2)) * width / units
|
||||||
|
}
|
||||||
|
|
||||||
|
main := "main"
|
||||||
|
secondary := "secondary"
|
||||||
|
swappingMainPanels := gui.State.Panels.Staging != nil && gui.State.Panels.Staging.IndexFocused
|
||||||
|
if swappingMainPanels {
|
||||||
|
main = "secondary"
|
||||||
|
secondary = "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := g.SetView(main, leftSideWidth+panelSpacing, 0, panelSplitX, height-2, gocui.LEFT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != "unknown view" {
|
if err.Error() != "unknown view" {
|
||||||
return err
|
return err
|
||||||
@ -386,6 +407,20 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
v.FgColor = textColor
|
v.FgColor = textColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hiddenViewOffset := 0
|
||||||
|
if !gui.State.SplitMainPanel {
|
||||||
|
hiddenViewOffset = 9999
|
||||||
|
}
|
||||||
|
secondaryView, err := g.SetView(secondary, panelSplitX+1+hiddenViewOffset, hiddenViewOffset, width-1+hiddenViewOffset, height-2+hiddenViewOffset, gocui.LEFT)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() != "unknown view" {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secondaryView.Title = gui.Tr.SLocalize("DiffTitle")
|
||||||
|
secondaryView.Wrap = true
|
||||||
|
secondaryView.FgColor = gocui.ColorWhite
|
||||||
|
}
|
||||||
|
|
||||||
if v, err := g.SetView("status", 0, 0, leftSideWidth, vHeights["status"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
if v, err := g.SetView("status", 0, 0, leftSideWidth, vHeights["status"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
||||||
if err.Error() != "unknown view" {
|
if err.Error() != "unknown view" {
|
||||||
return err
|
return err
|
||||||
|
@ -678,14 +678,32 @@ func (gui *Gui) GetContextMap() map[string]map[string][]*Binding {
|
|||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Key: gocui.KeySpace,
|
Key: gocui.KeySpace,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleStageLine,
|
Handler: gui.handleStageSelection,
|
||||||
Description: gui.Tr.SLocalize("StageLine"),
|
Description: gui.Tr.SLocalize("StageSelection"),
|
||||||
|
}, {
|
||||||
|
ViewName: "main",
|
||||||
|
Key: 'd',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleResetSelection,
|
||||||
|
Description: gui.Tr.SLocalize("ResetSelection"),
|
||||||
|
}, {
|
||||||
|
ViewName: "main",
|
||||||
|
Key: 'c',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleToggleSelectRange,
|
||||||
|
Description: gui.Tr.SLocalize("ToggleDragSelect"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Key: 'a',
|
Key: 'a',
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleStageHunk,
|
Handler: gui.handleToggleSelectHunk,
|
||||||
Description: gui.Tr.SLocalize("StageHunk"),
|
Description: gui.Tr.SLocalize("ToggleSelectHunk"),
|
||||||
|
}, {
|
||||||
|
ViewName: "main",
|
||||||
|
Key: gocui.KeyTab,
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleTogglePanel,
|
||||||
|
Description: gui.Tr.SLocalize("TogglePanel"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"merging": {
|
"merging": {
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/git"
|
"github.com/jesseduffield/lazygit/pkg/git"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LINE = iota
|
||||||
|
RANGE
|
||||||
|
HUNK
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) refreshStagingPanel() error {
|
func (gui *Gui) refreshStagingPanel() error {
|
||||||
|
state := gui.State.Panels.Staging
|
||||||
|
|
||||||
file, err := gui.getSelectedFile(gui.g)
|
file, err := gui.getSelectedFile(gui.g)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != gui.Errors.ErrNoFiles {
|
if err != gui.Errors.ErrNoFiles {
|
||||||
@ -15,56 +24,187 @@ func (gui *Gui) refreshStagingPanel() error {
|
|||||||
return gui.handleStagingEscape(gui.g, nil)
|
return gui.handleStagingEscape(gui.g, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !file.HasUnstagedChanges {
|
gui.State.SplitMainPanel = true
|
||||||
|
|
||||||
|
indexFocused := false
|
||||||
|
if state != nil {
|
||||||
|
indexFocused = state.IndexFocused
|
||||||
|
}
|
||||||
|
|
||||||
|
if !file.HasUnstagedChanges && !file.HasStagedChanges {
|
||||||
return gui.handleStagingEscape(gui.g, nil)
|
return gui.handleStagingEscape(gui.g, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (indexFocused && !file.HasStagedChanges) || (!indexFocused && !file.HasUnstagedChanges) {
|
||||||
|
indexFocused = !indexFocused
|
||||||
|
}
|
||||||
|
|
||||||
|
getDiffs := func() (string, string) {
|
||||||
// note for custom diffs, we'll need to send a flag here saying not to use the custom diff
|
// note for custom diffs, we'll need to send a flag here saying not to use the custom diff
|
||||||
diff := gui.GitCommand.Diff(file, true)
|
diff := gui.GitCommand.Diff(file, true, indexFocused)
|
||||||
colorDiff := gui.GitCommand.Diff(file, false)
|
secondaryColorDiff := gui.GitCommand.Diff(file, false, !indexFocused)
|
||||||
|
return diff, secondaryColorDiff
|
||||||
|
}
|
||||||
|
|
||||||
if len(diff) < 2 {
|
diff, secondaryColorDiff := getDiffs()
|
||||||
|
|
||||||
|
// if we have e.g. a deleted file with nothing else to the diff will have only
|
||||||
|
// 4-5 lines in which case we'll swap panels
|
||||||
|
if len(strings.Split(diff, "\n")) < 5 {
|
||||||
|
if len(strings.Split(secondaryColorDiff, "\n")) < 5 {
|
||||||
|
return gui.handleStagingEscape(gui.g, nil)
|
||||||
|
}
|
||||||
|
indexFocused = !indexFocused
|
||||||
|
diff, secondaryColorDiff = getDiffs()
|
||||||
|
}
|
||||||
|
|
||||||
|
patchParser, err := git.NewPatchParser(gui.Log, diff)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(patchParser.StageableLines) == 0 {
|
||||||
return gui.handleStagingEscape(gui.g, nil)
|
return gui.handleStagingEscape(gui.g, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the diff and store the line numbers of hunks and stageable lines
|
var selectedLineIdx int
|
||||||
// TODO: maybe instantiate this at application start
|
var firstLineIdx int
|
||||||
p, err := git.NewPatchParser(gui.Log)
|
var lastLineIdx int
|
||||||
if err != nil {
|
selectMode := LINE
|
||||||
return nil
|
if state != nil {
|
||||||
}
|
if state.SelectMode == HUNK {
|
||||||
hunkStarts, stageableLines, err := p.ParsePatch(diff)
|
// this is tricky: we need to find out which hunk we just staged based on our old `state.PatchParser` (as opposed to the new `patchParser`)
|
||||||
if err != nil {
|
// we do this by getting the first line index of the original hunk, then
|
||||||
return nil
|
// finding the next stageable line, then getting its containing hunk
|
||||||
}
|
// in the new diff
|
||||||
|
selectMode = HUNK
|
||||||
var selectedLine int
|
prevNewHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0)
|
||||||
if gui.State.Panels.Staging != nil {
|
selectedLineIdx = patchParser.GetNextStageableLineIndex(prevNewHunk.FirstLineIdx)
|
||||||
end := len(stageableLines) - 1
|
newHunk := patchParser.GetHunkContainingLine(selectedLineIdx, 0)
|
||||||
if end < gui.State.Panels.Staging.SelectedLine {
|
firstLineIdx, lastLineIdx = newHunk.FirstLineIdx, newHunk.LastLineIdx
|
||||||
selectedLine = end
|
|
||||||
} else {
|
} else {
|
||||||
selectedLine = gui.State.Panels.Staging.SelectedLine
|
selectedLineIdx = patchParser.GetNextStageableLineIndex(state.SelectedLineIdx)
|
||||||
|
firstLineIdx, lastLineIdx = selectedLineIdx, selectedLineIdx
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedLine = 0
|
selectedLineIdx = patchParser.StageableLines[0]
|
||||||
|
firstLineIdx, lastLineIdx = selectedLineIdx, selectedLineIdx
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.Panels.Staging = &stagingPanelState{
|
gui.State.Panels.Staging = &stagingPanelState{
|
||||||
StageableLines: stageableLines,
|
PatchParser: patchParser,
|
||||||
HunkStarts: hunkStarts,
|
SelectedLineIdx: selectedLineIdx,
|
||||||
SelectedLine: selectedLine,
|
SelectMode: selectMode,
|
||||||
|
FirstLineIdx: firstLineIdx,
|
||||||
|
LastLineIdx: lastLineIdx,
|
||||||
Diff: diff,
|
Diff: diff,
|
||||||
|
IndexFocused: indexFocused,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(stageableLines) == 0 {
|
if err := gui.refreshView(); err != nil {
|
||||||
return gui.createErrorPanel(gui.g, "No lines to stage")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := gui.focusLineAndHunk(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := gui.focusSelection(selectMode == HUNK); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
secondaryView := gui.getSecondaryView()
|
||||||
|
secondaryView.Highlight = true
|
||||||
|
secondaryView.Wrap = false
|
||||||
|
|
||||||
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
|
return gui.setViewContent(gui.g, gui.getSecondaryView(), secondaryColorDiff)
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleTogglePanel(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
state := gui.State.Panels.Staging
|
||||||
|
|
||||||
|
state.IndexFocused = !state.IndexFocused
|
||||||
|
return gui.refreshStagingPanel()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
gui.State.Panels.Staging = nil
|
||||||
|
|
||||||
|
return gui.switchFocus(gui.g, nil, gui.getFilesView())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleStagingPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.handleCycleLine(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleStagingNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.handleCycleLine(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleStagingPrevHunk(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.handleCycleHunk(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleStagingNextHunk(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.handleCycleHunk(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCycleHunk(change int) error {
|
||||||
|
state := gui.State.Panels.Staging
|
||||||
|
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, change)
|
||||||
|
state.SelectedLineIdx = state.PatchParser.GetNextStageableLineIndex(newHunk.FirstLineIdx)
|
||||||
|
if state.SelectMode == HUNK {
|
||||||
|
state.FirstLineIdx, state.LastLineIdx = newHunk.FirstLineIdx, newHunk.LastLineIdx
|
||||||
|
} else {
|
||||||
|
state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.refreshView(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.focusSelection(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCycleLine(change int) error {
|
||||||
|
state := gui.State.Panels.Staging
|
||||||
|
|
||||||
|
if state.SelectMode == HUNK {
|
||||||
|
return gui.handleCycleHunk(change)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSelectedLineIdx := state.SelectedLineIdx + change
|
||||||
|
if newSelectedLineIdx < 0 {
|
||||||
|
newSelectedLineIdx = 0
|
||||||
|
} else if newSelectedLineIdx > len(state.PatchParser.PatchLines)-1 {
|
||||||
|
newSelectedLineIdx = len(state.PatchParser.PatchLines) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SelectedLineIdx = newSelectedLineIdx
|
||||||
|
|
||||||
|
if state.SelectMode == RANGE {
|
||||||
|
if state.SelectedLineIdx < state.FirstLineIdx {
|
||||||
|
state.FirstLineIdx = state.SelectedLineIdx
|
||||||
|
} else {
|
||||||
|
state.LastLineIdx = state.SelectedLineIdx
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.LastLineIdx = state.SelectedLineIdx
|
||||||
|
state.FirstLineIdx = state.SelectedLineIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.refreshView(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.focusSelection(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) refreshView() error {
|
||||||
|
state := gui.State.Panels.Staging
|
||||||
|
|
||||||
|
colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx)
|
||||||
|
|
||||||
mainView := gui.getMainView()
|
mainView := gui.getMainView()
|
||||||
mainView.Highlight = true
|
mainView.Highlight = true
|
||||||
mainView.Wrap = false
|
mainView.Wrap = false
|
||||||
@ -76,142 +216,84 @@ func (gui *Gui) refreshStagingPanel() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
|
// focusSelection works out the best focus for the staging panel given the
|
||||||
gui.State.Panels.Staging = nil
|
|
||||||
|
|
||||||
return gui.switchFocus(gui.g, nil, gui.getFilesView())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleStagingPrevLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
return gui.handleCycleLine(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleStagingNextLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
return gui.handleCycleLine(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleStagingPrevHunk(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
return gui.handleCycleHunk(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleStagingNextHunk(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
return gui.handleCycleHunk(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleCycleHunk(prev bool) error {
|
|
||||||
state := gui.State.Panels.Staging
|
|
||||||
lineNumbers := state.StageableLines
|
|
||||||
currentLine := lineNumbers[state.SelectedLine]
|
|
||||||
currentHunkIndex := utils.PrevIndex(state.HunkStarts, currentLine)
|
|
||||||
var newHunkIndex int
|
|
||||||
if prev {
|
|
||||||
if currentHunkIndex == 0 {
|
|
||||||
newHunkIndex = len(state.HunkStarts) - 1
|
|
||||||
} else {
|
|
||||||
newHunkIndex = currentHunkIndex - 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if currentHunkIndex == len(state.HunkStarts)-1 {
|
|
||||||
newHunkIndex = 0
|
|
||||||
} else {
|
|
||||||
newHunkIndex = currentHunkIndex + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.SelectedLine = utils.NextIndex(lineNumbers, state.HunkStarts[newHunkIndex])
|
|
||||||
|
|
||||||
return gui.focusLineAndHunk()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleCycleLine(prev bool) error {
|
|
||||||
state := gui.State.Panels.Staging
|
|
||||||
lineNumbers := state.StageableLines
|
|
||||||
currentLine := lineNumbers[state.SelectedLine]
|
|
||||||
var newIndex int
|
|
||||||
if prev {
|
|
||||||
newIndex = utils.PrevIndex(lineNumbers, currentLine)
|
|
||||||
} else {
|
|
||||||
newIndex = utils.NextIndex(lineNumbers, currentLine)
|
|
||||||
}
|
|
||||||
state.SelectedLine = newIndex
|
|
||||||
|
|
||||||
return gui.focusLineAndHunk()
|
|
||||||
}
|
|
||||||
|
|
||||||
// focusLineAndHunk works out the best focus for the staging panel given the
|
|
||||||
// selected line and size of the hunk
|
// selected line and size of the hunk
|
||||||
func (gui *Gui) focusLineAndHunk() error {
|
func (gui *Gui) focusSelection(includeCurrentHunk bool) error {
|
||||||
stagingView := gui.getMainView()
|
stagingView := gui.getMainView()
|
||||||
state := gui.State.Panels.Staging
|
state := gui.State.Panels.Staging
|
||||||
|
|
||||||
lineNumber := state.StageableLines[state.SelectedLine]
|
_, viewHeight := stagingView.Size()
|
||||||
|
bufferHeight := viewHeight - 1
|
||||||
|
_, origin := stagingView.Origin()
|
||||||
|
|
||||||
// we want the bottom line of the view buffer to ideally be the bottom line
|
firstLineIdx := state.SelectedLineIdx
|
||||||
// of the hunk, but if the hunk is too big we'll just go three lines beyond
|
lastLineIdx := state.SelectedLineIdx
|
||||||
// the currently selected line so that the user can see the context
|
|
||||||
var bottomLine int
|
if includeCurrentHunk {
|
||||||
nextHunkStartIndex := utils.NextIndex(state.HunkStarts, lineNumber)
|
hunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0)
|
||||||
if nextHunkStartIndex == 0 {
|
firstLineIdx = hunk.FirstLineIdx
|
||||||
// for now linesHeight is an efficient means of getting the number of lines
|
lastLineIdx = hunk.LastLineIdx
|
||||||
// in the patch. However if we introduce word wrap we'll need to update this
|
}
|
||||||
bottomLine = stagingView.LinesHeight() - 1
|
|
||||||
|
margin := 0 // we may want to have a margin in place to show context but right now I'm thinking we keep this at zero
|
||||||
|
|
||||||
|
var newOrigin int
|
||||||
|
if firstLineIdx-origin < margin {
|
||||||
|
newOrigin = firstLineIdx - margin
|
||||||
|
} else if lastLineIdx-origin > bufferHeight-margin {
|
||||||
|
newOrigin = lastLineIdx - bufferHeight + margin
|
||||||
} else {
|
} else {
|
||||||
bottomLine = state.HunkStarts[nextHunkStartIndex] - 1
|
newOrigin = origin
|
||||||
}
|
}
|
||||||
|
|
||||||
hunkStartIndex := utils.PrevIndex(state.HunkStarts, lineNumber)
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
hunkStart := state.HunkStarts[hunkStartIndex]
|
if err := stagingView.SetOrigin(0, newOrigin); err != nil {
|
||||||
// if it's the first hunk we'll also show the diff header
|
return err
|
||||||
if hunkStartIndex == 0 {
|
|
||||||
hunkStart = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, height := stagingView.Size()
|
return stagingView.SetCursor(0, state.SelectedLineIdx-newOrigin)
|
||||||
// if this hunk is too big, we will just ensure that the user can at least
|
})
|
||||||
// see three lines of context below the cursor
|
|
||||||
if bottomLine-hunkStart > height {
|
return nil
|
||||||
bottomLine = lineNumber + 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.generalFocusLine(lineNumber, bottomLine, stagingView)
|
func (gui *Gui) handleStageSelection(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.applySelection(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleStageHunk(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleResetSelection(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.handleStageLineOrHunk(true)
|
return gui.applySelection(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleStageLine(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) applySelection(reverse bool) error {
|
||||||
return gui.handleStageLineOrHunk(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleStageLineOrHunk(hunk bool) error {
|
|
||||||
state := gui.State.Panels.Staging
|
state := gui.State.Panels.Staging
|
||||||
p, err := git.NewPatchModifier(gui.Log)
|
|
||||||
|
if !reverse && state.IndexFocused {
|
||||||
|
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantStageStaged"))
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := gui.getSelectedFile(gui.g)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
currentLine := state.StageableLines[state.SelectedLine]
|
patch := git.ModifiedPatch(gui.Log, file.Name, state.Diff, state.FirstLineIdx, state.LastLineIdx, reverse)
|
||||||
var patch string
|
|
||||||
if hunk {
|
|
||||||
patch, err = p.ModifyPatchForHunk(state.Diff, state.HunkStarts, currentLine)
|
|
||||||
} else {
|
|
||||||
patch, err = p.ModifyPatchForLine(state.Diff, currentLine)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// for logging purposes
|
if patch == "" {
|
||||||
// ioutil.WriteFile("patch.diff", []byte(patch), 0600)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// apply the patch then refresh this panel
|
// apply the patch then refresh this panel
|
||||||
// create a new temp file with the patch, then call git apply with that patch
|
// create a new temp file with the patch, then call git apply with that patch
|
||||||
_, err = gui.GitCommand.ApplyPatch(patch)
|
_, err = gui.GitCommand.ApplyPatch(patch, false, !reverse || state.IndexFocused)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state.SelectMode == RANGE {
|
||||||
|
state.SelectMode = LINE
|
||||||
|
}
|
||||||
|
|
||||||
if err := gui.refreshFiles(); err != nil {
|
if err := gui.refreshFiles(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -220,3 +302,34 @@ func (gui *Gui) handleStageLineOrHunk(hunk bool) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleToggleSelectRange(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
state := gui.State.Panels.Staging
|
||||||
|
if state.SelectMode == RANGE {
|
||||||
|
state.SelectMode = LINE
|
||||||
|
} else {
|
||||||
|
state.SelectMode = RANGE
|
||||||
|
}
|
||||||
|
state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx
|
||||||
|
|
||||||
|
return gui.refreshView()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleToggleSelectHunk(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
state := gui.State.Panels.Staging
|
||||||
|
|
||||||
|
if state.SelectMode == HUNK {
|
||||||
|
state.SelectMode = LINE
|
||||||
|
state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx
|
||||||
|
} else {
|
||||||
|
state.SelectMode = HUNK
|
||||||
|
selectedHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0)
|
||||||
|
state.FirstLineIdx, state.LastLineIdx = selectedHunk.FirstLineIdx, selectedHunk.LastLineIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.refreshView(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.focusSelection(state.SelectMode == HUNK)
|
||||||
|
}
|
||||||
|
@ -300,6 +300,11 @@ func (gui *Gui) getMainView() *gocui.View {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) getSecondaryView() *gocui.View {
|
||||||
|
v, _ := gui.g.View("secondary")
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) getStashView() *gocui.View {
|
func (gui *Gui) getStashView() *gocui.View {
|
||||||
v, _ := gui.g.View("stash")
|
v, _ := gui.g.View("stash")
|
||||||
return v
|
return v
|
||||||
|
@ -482,13 +482,30 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
Other: `stage individual hunks/lines`,
|
Other: `stage individual hunks/lines`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "FileStagingRequirements",
|
ID: "FileStagingRequirements",
|
||||||
Other: `Can only stage individual lines for tracked files with unstaged changes`,
|
Other: `Can only stage individual lines for tracked files`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StageHunk",
|
ID: "SelectHunk",
|
||||||
Other: `stage hunk`,
|
Other: `select hunk`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StageLine",
|
ID: "StageSelection",
|
||||||
Other: `stage line`,
|
Other: `stage selection`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "ResetSelection",
|
||||||
|
Other: `reset selection`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "ToggleDragSelect",
|
||||||
|
Other: `toggle drag select`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "ToggleSelectHunk",
|
||||||
|
Other: `toggle select hunk`,
|
||||||
|
},
|
||||||
|
&i18n.Message{
|
||||||
|
ID: "TogglePanel",
|
||||||
|
Other: `toggle staged/unstaged panel focus`,
|
||||||
|
},
|
||||||
|
&i18n.Message{
|
||||||
|
ID: "CantStageStaged",
|
||||||
|
Other: `You can't stage an already staged change!`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "EscapeStaging",
|
ID: "EscapeStaging",
|
||||||
Other: `return to files panel`,
|
Other: `return to files panel`,
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
var (
|
var (
|
||||||
// DefaultTextColor is the default text color
|
// DefaultTextColor is the default text color
|
||||||
DefaultTextColor = color.FgWhite
|
DefaultTextColor = color.FgWhite
|
||||||
|
// DefaultHiTextColor is the default highlighted text color
|
||||||
|
DefaultHiTextColor = color.FgHiWhite
|
||||||
|
|
||||||
// GocuiDefaultTextColor does the same as DefaultTextColor but this one only colors gocui default text colors
|
// GocuiDefaultTextColor does the same as DefaultTextColor but this one only colors gocui default text colors
|
||||||
GocuiDefaultTextColor gocui.Attribute
|
GocuiDefaultTextColor gocui.Attribute
|
||||||
@ -28,9 +30,11 @@ func UpdateTheme(userConfig *viper.Viper) {
|
|||||||
isLightTheme := userConfig.GetBool("gui.theme.lightTheme")
|
isLightTheme := userConfig.GetBool("gui.theme.lightTheme")
|
||||||
if isLightTheme {
|
if isLightTheme {
|
||||||
DefaultTextColor = color.FgBlack
|
DefaultTextColor = color.FgBlack
|
||||||
|
DefaultHiTextColor = color.FgHiBlack
|
||||||
GocuiDefaultTextColor = gocui.ColorBlack
|
GocuiDefaultTextColor = gocui.ColorBlack
|
||||||
} else {
|
} else {
|
||||||
DefaultTextColor = color.FgWhite
|
DefaultTextColor = color.FgWhite
|
||||||
|
DefaultHiTextColor = color.FgHiWhite
|
||||||
GocuiDefaultTextColor = gocui.ColorWhite
|
GocuiDefaultTextColor = gocui.ColorWhite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,18 +233,18 @@ func NextIndex(numbers []int, currentNumber int) int {
|
|||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0
|
return len(numbers) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrevIndex returns the index that comes before the given number, cycling if we reach the end
|
// PrevIndex returns the index that comes before the given number, cycling if we reach the end
|
||||||
func PrevIndex(numbers []int, currentNumber int) int {
|
func PrevIndex(numbers []int, currentNumber int) int {
|
||||||
end := len(numbers) - 1
|
end := len(numbers) - 1
|
||||||
for i := end; i >= 0; i -= 1 {
|
for i := end; i >= 0; i-- {
|
||||||
if numbers[i] < currentNumber {
|
if numbers[i] < currentNumber {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return end
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func AsJson(i interface{}) string {
|
func AsJson(i interface{}) string {
|
||||||
|
@ -485,7 +485,7 @@ func TestNextIndex(t *testing.T) {
|
|||||||
"no elements",
|
"no elements",
|
||||||
[]int{},
|
[]int{},
|
||||||
1,
|
1,
|
||||||
0,
|
-1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"one element",
|
"one element",
|
||||||
@ -503,7 +503,7 @@ func TestNextIndex(t *testing.T) {
|
|||||||
"two elements, giving second one",
|
"two elements, giving second one",
|
||||||
[]int{1, 2},
|
[]int{1, 2},
|
||||||
2,
|
2,
|
||||||
0,
|
1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"three elements, giving second one",
|
"three elements, giving second one",
|
||||||
@ -534,7 +534,7 @@ func TestPrevIndex(t *testing.T) {
|
|||||||
"no elements",
|
"no elements",
|
||||||
[]int{},
|
[]int{},
|
||||||
1,
|
1,
|
||||||
-1,
|
0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"one element",
|
"one element",
|
||||||
@ -546,7 +546,7 @@ func TestPrevIndex(t *testing.T) {
|
|||||||
"two elements",
|
"two elements",
|
||||||
[]int{1, 2},
|
[]int{1, 2},
|
||||||
1,
|
1,
|
||||||
1,
|
0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"three elements, giving second one",
|
"three elements, giving second one",
|
||||||
|
Reference in New Issue
Block a user