From daecdd7c2b678e308b01d9e402c86c06a35b8021 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 21 Mar 2020 13:39:20 +1100 Subject: [PATCH] redoing --- docs/Config.md | 3 +- pkg/config/app_config.go | 3 +- pkg/gui/gui.go | 3 ++ pkg/gui/keybindings.go | 11 +++++- pkg/gui/merge_panel.go | 2 +- pkg/gui/reflog_panel.go | 83 +++++++++++++++++++++++++++++++++------- pkg/i18n/english.go | 6 +++ 7 files changed, 93 insertions(+), 18 deletions(-) diff --git a/docs/Config.md b/docs/Config.md index 7cd142f69..41fa68f27 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -83,6 +83,8 @@ Default path for the config file: prevTab: '[' nextScreenMode: '+' prevScreenMode: '_' + undo: 'z' + redo: '' status: checkForUpdate: 'u' recentRepos: '' @@ -138,7 +140,6 @@ Default path for the config file: toggleDragSelect-alt: 'V' toggleSelectHunk: 'a' pickBothHunks: 'b' - undo: 'z' ``` ## Platform Defaults diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index eed4e7da4..310ca9a01 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -314,6 +314,8 @@ keybinding: prevTab: '[' nextScreenMode: '+' prevScreenMode: '_' + undo: 'z' + redo: '' status: checkForUpdate: 'u' recentRepos: '' @@ -370,7 +372,6 @@ keybinding: toggleDragSelect-alt: 'V' toggleSelectHunk: 'a' pickBothHunks: 'b' - undo: 'z' `) } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index fd4e6bfd4..2805b8416 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -229,6 +229,9 @@ type guiState struct { type UndoState struct { ReflogKey string ReflogIdx int + // this is the index of the most recent reflog entry that the user initiated themselves + // (as opposed to being created by an undo or redo action) + UndoCount int } // for now the split view will always be on diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 43b2d8ae5..ab2273d94 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -315,11 +315,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { }, { ViewName: "", - Key: gui.getKey("main.undo"), + Key: gui.getKey("universal.undo"), Modifier: gocui.ModNone, Handler: gui.reflogUndo, Description: gui.Tr.SLocalize("undoReflog"), }, + { + ViewName: "", + Key: gui.getKey("universal.redo"), + Modifier: gocui.ModNone, + Handler: gui.reflogRedo, + Description: gui.Tr.SLocalize("redoReflog"), + }, { ViewName: "status", Key: gui.getKey("universal.edit"), @@ -1349,7 +1356,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { { ViewName: "main", Contexts: []string{"merging"}, - Key: gui.getKey("main.undo"), + Key: gui.getKey("universal.undo"), Modifier: gocui.ModNone, Handler: gui.handlePopFileSnapshot, Description: gui.Tr.SLocalize("undo"), diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index 89e83b2d9..fa0928f22 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -255,7 +255,7 @@ func (gui *Gui) renderMergeOptions() error { fmt.Sprintf("%s %s", gui.getKeyDisplay("universal.prevBlock"), gui.getKeyDisplay("universal.nextBlock")): gui.Tr.SLocalize("navigateConflicts"), gui.getKeyDisplay("universal.select"): gui.Tr.SLocalize("pickHunk"), gui.getKeyDisplay("main.pickBothHunks"): gui.Tr.SLocalize("pickBothHunks"), - gui.getKeyDisplay("main.undo"): gui.Tr.SLocalize("undo"), + gui.getKeyDisplay("universal.undo"): gui.Tr.SLocalize("undo"), }) } diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index 3da4caeb1..f0172d7f2 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -104,7 +104,7 @@ func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error { type reflogAction struct { regexStr string - action func(match []string, commitSha string, prevCommitSha string, onDone func()) (bool, error) + action func(match []string, commitSha string, onDone func()) (bool, error) } func (gui *Gui) reflogKey(reflogCommit *commands.Commit) string { @@ -130,10 +130,17 @@ func (gui *Gui) setUndoReflogKey(key string) { func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error { reflogCommits := gui.State.ReflogCommits + // if the index of the previous reflog entry has changed, we need to start from the beginning, because it means there's been user input. + startIndex := gui.State.Undo.ReflogIdx + if gui.idxOfUndoReflogKey(gui.State.Undo.ReflogKey) != gui.State.Undo.ReflogIdx { + gui.State.Undo.UndoCount = 0 + startIndex = 0 + } + reflogActions := []reflogAction{ { regexStr: `^checkout: moving from ([\S]+)`, - action: func(match []string, commitSha string, prevCommitSha string, onDone func()) (bool, error) { + action: func(match []string, commitSha string, onDone func()) (bool, error) { if len(match) <= 1 { return false, nil } @@ -141,19 +148,13 @@ func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error { }, }, { - regexStr: `^commit|^rebase -i \(start\)`, - action: func(match []string, commitSha string, prevCommitSha string, onDone func()) (bool, error) { - return true, gui.handleHardResetWithAutoStash(prevCommitSha, onDone) + regexStr: `^commit|^rebase -i \(start\)|^reset: moving to|^pull`, + action: func(match []string, commitSha string, onDone func()) (bool, error) { + return true, gui.handleHardResetWithAutoStash(commitSha, onDone) }, }, } - // if the index of the previous reflog entry has changed, we need to start from the beginning, because it means there's been user input. - startIndex := gui.State.Undo.ReflogIdx - if gui.idxOfUndoReflogKey(gui.State.Undo.ReflogKey) != gui.State.Undo.ReflogIdx { - startIndex = 0 - } - for offsetIdx, reflogCommit := range reflogCommits[startIndex:] { i := offsetIdx + startIndex for _, action := range reflogActions { @@ -167,12 +168,68 @@ func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error { prevCommitSha = reflogCommits[i+1].Sha } - nextKey := gui.reflogKey(gui.State.ReflogCommits[i+1]) + nextKey := gui.reflogKey(reflogCommits[i+1]) onDone := func() { gui.setUndoReflogKey(nextKey) + gui.State.Undo.UndoCount++ } - isMatchingAction, err := action.action(match, reflogCommit.Sha, prevCommitSha, onDone) + isMatchingAction, err := action.action(match, prevCommitSha, onDone) + if !isMatchingAction { + continue + } + + return err + } + } + + return nil +} + +func (gui *Gui) reflogRedo(g *gocui.Gui, v *gocui.View) error { + reflogCommits := gui.State.ReflogCommits + + // if the index of the previous reflog entry has changed there is nothing to redo because there's been a user action + startIndex := gui.State.Undo.ReflogIdx + if gui.idxOfUndoReflogKey(gui.State.Undo.ReflogKey) != gui.State.Undo.ReflogIdx || startIndex == 0 || gui.State.Undo.UndoCount == 0 { + return nil + } + + reflogActions := []reflogAction{ + { + regexStr: `^checkout: moving from [\S]+ to ([\S]+)`, + action: func(match []string, commitSha string, onDone func()) (bool, error) { + if len(match) <= 1 { + return false, nil + } + return true, gui.handleCheckoutRef(match[1], handleCheckoutRefOptions{onDone: onDone, waitingStatus: gui.Tr.SLocalize("RedoingStatus")}) + }, + }, + { + regexStr: `^commit|^rebase -i \(start\)|^reset: moving to|^pull`, + action: func(match []string, commitSha string, onDone func()) (bool, error) { + return true, gui.handleHardResetWithAutoStash(commitSha, onDone) + }, + }, + } + + for i := startIndex - 1; i > 0; i++ { + reflogCommit := reflogCommits[i] + + for _, action := range reflogActions { + re := regexp.MustCompile(action.regexStr) + match := re.FindStringSubmatch(reflogCommit.Name) + if len(match) == 0 { + continue + } + + prevKey := gui.reflogKey(reflogCommits[i-1]) + onDone := func() { + gui.setUndoReflogKey(prevKey) + gui.State.Undo.UndoCount-- + } + + isMatchingAction, err := action.action(match, reflogCommit.Sha, onDone) if !isMatchingAction { continue } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index d63d330bf..0d624534d 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -363,6 +363,9 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "undoReflog", Other: "undo (via reflog) (experimental)", + }, &i18n.Message{ + ID: "redoReflog", + Other: "redo (via reflog) (experimental)", }, &i18n.Message{ ID: "pop", Other: "pop", @@ -747,6 +750,9 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "UndoingStatus", Other: "undoing", + }, &i18n.Message{ + ID: "RedoingStatus", + Other: "redoing", }, &i18n.Message{ ID: "CheckingOutStatus", Other: "checking out",