summaryrefslogtreecommitdiffstats
path: root/pkg/gui
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2020-03-28 16:28:35 +1100
committerJesse Duffield <jessedduffield@gmail.com>2020-03-29 11:37:29 +1100
commit624ae45ebb3f54499a25c4eba0844fa971277c34 (patch)
treeca20f93742858b2b4231d083e5b6abdab61d69ba /pkg/gui
parent2756b82f5733c2099c43279ebb1a962101411142 (diff)
allow scoped mode where the commits/reflog/stash panels are scoped to a file
WIP restrict certain actions in scoped mode WIP
Diffstat (limited to 'pkg/gui')
-rw-r--r--pkg/gui/branches_panel.go12
-rw-r--r--pkg/gui/commits_panel.go68
-rw-r--r--pkg/gui/gui.go66
-rw-r--r--pkg/gui/keybindings.go9
-rw-r--r--pkg/gui/patch_options_panel.go3
-rw-r--r--pkg/gui/reflog_panel.go4
-rw-r--r--pkg/gui/scoping_menu_panel.go58
-rw-r--r--pkg/gui/stash_panel.go2
8 files changed, 203 insertions, 19 deletions
diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go
index 9f3bd0f97..3d1692844 100644
--- a/pkg/gui/branches_panel.go
+++ b/pkg/gui/branches_panel.go
@@ -263,6 +263,10 @@ func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *c
}
func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
if gui.GitCommand.IsHeadDetached() {
return gui.createErrorPanel("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
}
@@ -286,6 +290,10 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
}
func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
selectedBranchName := gui.getSelectedBranch().Name
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
}
@@ -296,6 +304,10 @@ func (gui *Gui) handleRebaseOntoLocalBranch(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
checkedOutBranch := gui.getCheckedOutBranch().Name
if selectedBranchName == checkedOutBranch {
return gui.createErrorPanel(gui.Tr.SLocalize("CantRebaseOntoSelf"))
diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go
index 3d96555f0..0bdc18fb0 100644
--- a/pkg/gui/commits_panel.go
+++ b/pkg/gui/commits_panel.go
@@ -62,7 +62,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
}
cmd := gui.OSCommand.ExecutableFromString(
- gui.GitCommand.ShowCmdStr(commit.Sha),
+ gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.LogScope),
)
if err := gui.newPtyTask("main", cmd); err != nil {
gui.Log.Error(err)
@@ -122,7 +122,7 @@ func (gui *Gui) refreshCommitsWithLimit() error {
return err
}
- commits, err := builder.GetCommits(gui.State.Panels.Commits.LimitCommits)
+ commits, err := builder.GetCommits(commands.GetCommitsOptions{Limit: gui.State.Panels.Commits.LimitCommits, LogScope: gui.State.LogScope})
if err != nil {
return err
}
@@ -140,6 +140,10 @@ func (gui *Gui) refreshCommitsWithLimit() error {
// specific functions
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
if len(gui.State.Commits) <= 1 {
return gui.createErrorPanel(gui.Tr.SLocalize("YouNoCommitsToSquash"))
}
@@ -161,6 +165,10 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
if len(gui.State.Commits) <= 1 {
return gui.createErrorPanel(gui.Tr.SLocalize("YouNoCommitsToSquash"))
}
@@ -182,6 +190,10 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
applied, err := gui.handleMidRebaseCommand("reword")
if err != nil {
return err
@@ -203,6 +215,10 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
applied, err := gui.handleMidRebaseCommand("reword")
if err != nil {
return err
@@ -249,6 +265,10 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
}
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
applied, err := gui.handleMidRebaseCommand("drop")
if err != nil {
return err
@@ -266,6 +286,10 @@ func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleCommitMoveDown(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
index := gui.State.Panels.Commits.SelectedLine
selectedCommit := gui.State.Commits[index]
if selectedCommit.Status == "rebasing" {
@@ -289,6 +313,10 @@ func (gui *Gui) handleCommitMoveDown(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleCommitMoveUp(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
index := gui.State.Panels.Commits.SelectedLine
if index == 0 {
return nil
@@ -312,6 +340,10 @@ func (gui *Gui) handleCommitMoveUp(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleCommitEdit(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
applied, err := gui.handleMidRebaseCommand("edit")
if err != nil {
return err
@@ -327,6 +359,10 @@ func (gui *Gui) handleCommitEdit(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleCommitAmendTo(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
return gui.createConfirmationPanel(gui.g, v, true, gui.Tr.SLocalize("AmendCommitTitle"), gui.Tr.SLocalize("AmendCommitPrompt"), func(*gocui.Gui, *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.SLocalize("AmendingStatus"), func() error {
err := gui.GitCommand.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha)
@@ -336,6 +372,10 @@ func (gui *Gui) handleCommitAmendTo(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleCommitPick(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
applied, err := gui.handleMidRebaseCommand("pick")
if err != nil {
return err
@@ -350,6 +390,10 @@ func (gui *Gui) handleCommitPick(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleCommitRevert(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
if err := gui.GitCommand.Revert(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha); err != nil {
return gui.surfaceError(err)
}
@@ -358,6 +402,10 @@ func (gui *Gui) handleCommitRevert(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleCopyCommit(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
// get currently selected commit, add the sha to state.
commit := gui.State.Commits[gui.State.Panels.Commits.SelectedLine]
@@ -397,6 +445,10 @@ func (gui *Gui) addCommitToCherryPickedCommits(index int) {
}
func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
commitShaMap := gui.cherryPickedCommitShaMap()
// find the last commit that is copied that's above our position
@@ -419,6 +471,10 @@ func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
func (gui *Gui) HandlePasteCommits(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("CherryPick"), gui.Tr.SLocalize("SureCherryPick"), func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.SLocalize("CherryPickingStatus"), func() error {
err := gui.GitCommand.CherryPickCommits(gui.State.CherryPickedCommits)
@@ -495,6 +551,10 @@ func (gui *Gui) unchooseCommit(commits []*commands.Commit, i int) []*commands.Co
}
func (gui *Gui) handleCreateFixupCommit(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
commit := gui.getSelectedCommit(g)
if commit == nil {
return nil
@@ -515,6 +575,10 @@ func (gui *Gui) handleCreateFixupCommit(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleSquashAllAboveFixupCommits(g *gocui.Gui, v *gocui.View) error {
+ if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
+ return err
+ }
+
commit := gui.getSelectedCommit(g)
if commit == nil {
return nil
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 969c8c942..a3f110427 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -50,6 +50,7 @@ type SentinelErrors struct {
ErrSubProcess error
ErrNoFiles error
ErrSwitchRepo error
+ ErrRestart error
}
// GenerateSentinelErrors makes the sentinel errors for the gui. We're defining it here
@@ -67,6 +68,7 @@ func (gui *Gui) GenerateSentinelErrors() {
ErrSubProcess: errors.New(gui.Tr.SLocalize("RunningSubprocess")),
ErrNoFiles: errors.New(gui.Tr.SLocalize("NoChangedFiles")),
ErrSwitchRepo: errors.New("switching repo"),
+ ErrRestart: errors.New("restarting"),
}
}
@@ -214,13 +216,13 @@ type guiState struct {
PrevMainWidth int
PrevMainHeight int
OldInformation string
- StartupStage int // one of INITIAL and COMPLETE. Allows us to not load everything at once
+ StartupStage int // one of INITIAL and COMPLETE. Allows us to not load everything at once
+ LogScope string // the filename that gets passed to git log
}
// for now the split view will always be on
-
// NewGui builds a new gui handler
-func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) {
+func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater, logScope string) (*Gui, error) {
initialState := &guiState{
Files: make([]*commands.File, 0),
@@ -248,9 +250,9 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
EditHistory: stack.New(),
},
},
- ScreenMode: SCREEN_NORMAL,
- SideView: nil,
- Ptmx: nil,
+ SideView: nil,
+ Ptmx: nil,
+ LogScope: logScope,
}
gui := &Gui{
@@ -509,7 +511,9 @@ func (gui *Gui) layout(g *gocui.Gui) error {
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.SLocalize("Donate"))
information = donate + " " + information
}
- if len(gui.State.CherryPickedCommits) > 0 {
+ if gui.inScopedMode() {
+ information = utils.ColoredString(fmt.Sprintf("%s '%s' %s", gui.Tr.SLocalize("scopingTo"), gui.State.LogScope, utils.ColoredString(gui.Tr.SLocalize("(reset)"), color.Underline)), color.FgRed, color.Bold)
+ } else if len(gui.State.CherryPickedCommits) > 0 {
information = utils.ColoredString(fmt.Sprintf("%d commits copied", len(gui.State.CherryPickedCommits)), color.FgCyan)
}
@@ -799,11 +803,15 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
if gui.g.CurrentView() == nil {
- if _, err := gui.g.SetCurrentView(gui.getFilesView().Name()); err != nil {
+ initialView := gui.getFilesView()
+ if gui.inScopedMode() {
+ initialView = gui.getCommitsView()
+ }
+ if _, err := gui.g.SetCurrentView(initialView.Name()); err != nil {
return err
}
- if err := gui.switchFocus(gui.g, nil, gui.getFilesView()); err != nil {
+ if err := gui.switchFocus(gui.g, nil, initialView); err != nil {
return err
}
}
@@ -985,6 +993,12 @@ func (gui *Gui) Run() error {
}
defer g.Close()
+ if gui.inScopedMode() {
+ gui.State.ScreenMode = SCREEN_HALF
+ } else {
+ gui.State.ScreenMode = SCREEN_NORMAL
+ }
+
g.OnSearchEscape = gui.onSearchEscape
g.SearchEscapeKey = gui.getKey("universal.return")
g.NextSearchMatchKey = gui.getKey("universal.nextMatch")
@@ -1061,6 +1075,8 @@ func (gui *Gui) RunWithSubprocesses() error {
break
} else if err == gui.Errors.ErrSwitchRepo {
continue
+ } else if err == gui.Errors.ErrRestart {
+ continue
} else if err == gui.Errors.ErrSubProcess {
if err := gui.runCommand(); err != nil {
return err
@@ -1097,16 +1113,29 @@ func (gui *Gui) runCommand() error {
return nil
}
-func (gui *Gui) handleDonate(g *gocui.Gui, v *gocui.View) error {
+func (gui *Gui) handleInfoClick(g *gocui.Gui, v *gocui.View) error {
if !gui.g.Mouse {
return nil
}
cx, _ := v.Cursor()
- if cx > len(gui.Tr.SLocalize("Donate")) {
- return nil
+ width, _ := v.Size()
+
+ // if we're in the normal context there will be a donate button here
+ // if we have ('reset') at the end then
+ if gui.inScopedMode() {
+ if width-cx <= len(gui.Tr.SLocalize("(reset)")) {
+ gui.State.LogScope = ""
+ return gui.Errors.ErrRestart
+ } else {
+ return nil
+ }
+ }
+
+ if cx <= len(gui.Tr.SLocalize("Donate")) {
+ return gui.OSCommand.OpenLink("https://github.com/sponsors/jesseduffield")
}
- return gui.OSCommand.OpenLink("https://github.com/sponsors/jesseduffield")
+ return nil
}
// setColorScheme sets the color scheme for the app based on the user config
@@ -1147,3 +1176,14 @@ func (gui *Gui) handleMouseDownSecondary(g *gocui.Gui, v *gocui.View) error {
return nil
}
+
+func (gui *Gui) inScopedMode() bool {
+ return gui.State.LogScope != ""
+}
+
+func (gui *Gui) validateNotInScopedMode() (bool, error) {
+ if gui.inScopedMode() {
+ return false, gui.createErrorPanel("command not available in scoped mode. Either exit scoped mode or restart lazygit")
+ }
+ return true, nil
+}
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index accb30ede..6d5a54bae 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -938,7 +938,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "information",
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
- Handler: gui.handleDonate,
+ Handler: gui.handleInfoClick,
},
{
ViewName: "commitFiles",
@@ -983,6 +983,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Description: gui.Tr.SLocalize("enterFile"),
},
{
+ ViewName: "",
+ Key: gui.getKey("universal.scopingMenu"),
+ Modifier: gocui.ModNone,
+ Handler: gui.handleCreateScopingMenuPanel,
+ Description: gui.Tr.SLocalize("openScopingMenu"),
+ },
+ {
ViewName: "secondary",
Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone,
diff --git a/pkg/gui/patch_options_panel.go b/pkg/gui/patch_options_panel.go
index e51860b43..074b4f38e 100644
--- a/pkg/gui/patch_options_panel.go
+++ b/pkg/gui/patch_options_panel.go
@@ -62,6 +62,9 @@ func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
if gui.GitCommand.WorkingTreeState() != "normal" {
return false, gui.createErrorPanel(gui.Tr.SLocalize("CantPatchWhileRebasingError"))
}
+ if gui.GitCommand.WorkingTreeState() != "normal" {
+ return false, gui.createErrorPanel(gui.Tr.SLocalize("CantPatchWhileRebasingError"))
+ }
return true, nil
}
diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go
index 34491bc29..fa7f994ee 100644
--- a/pkg/gui/reflog_panel.go
+++ b/pkg/gui/reflog_panel.go
@@ -37,7 +37,7 @@ func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) error {
v.FocusPoint(0, gui.State.Panels.ReflogCommits.SelectedLine)
cmd := gui.OSCommand.ExecutableFromString(
- gui.GitCommand.ShowCmdStr(commit.Sha),
+ gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.LogScope),
)
if err := gui.newPtyTask("main", cmd); err != nil {
gui.Log.Error(err)
@@ -52,7 +52,7 @@ func (gui *Gui) refreshReflogCommits() error {
lastReflogCommit = gui.State.ReflogCommits[0]
}
- commits, onlyObtainedNewReflogCommits, err := gui.GitCommand.GetReflogCommits(lastReflogCommit)
+ commits, onlyObtainedNewReflogCommits, err := gui.GitCommand.GetReflogCommits(lastReflogCommit, gui.State.LogScope)
if err != nil {
return gui.surfaceError(err)
}
diff --git a/pkg/gui/scoping_menu_panel.go b/pkg/gui/scoping_menu_panel.go
new file mode 100644
index 000000000..d1c2f85e3
--- /dev/null
+++ b/pkg/gui/scoping_menu_panel.go
@@ -0,0 +1,58 @@
+package gui
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/jesseduffield/gocui"
+)
+
+func (gui *Gui) handleCreateScopingMenuPanel(g *gocui.Gui, v *gocui.View) error {
+ fileName := ""
+ switch v.Name() {
+ case "files":
+ file, err := gui.getSelectedFile(gui.g)
+ if err == nil {
+ fileName = file.Name
+ }
+ case "commitFiles":
+ file := gui.getSelectedCommitFile(gui.g)
+ if file != nil {
+ fileName = file.Name
+ }
+ }
+
+ menuItems := []*menuItem{}
+
+ if fileName != "" {
+ menuItems = append(menuItems, &menuItem{
+ displayString: fmt.Sprintf("%s '%s'", gui.Tr.SLocalize("scopeTo"), fileName),
+ onPress: func() error {
+ gui.State.LogScope = fileName
+ return gui.Errors.ErrRestart
+ },
+ })
+ }
+
+ menuItems = append(menuItems, &menuItem{
+ displayString: gui.Tr.SLocalize("fileToScopeToOption"),
+ onPress: func() error {
+ return gui.createPromptPanel(gui.g, v, gui.Tr.SLocalize("enterFileName"), "", func(g *gocui.Gui, promptView *gocui.View) error {
+ gui.State.LogScope = strings.TrimSpace(promptView.Buffer())
+ return gui.Errors.ErrRestart
+ })
+ },
+ })
+
+ if gui.inScopedMode() {
+ menuItems = append(menuItems, &menuItem{
+ displayString: gui.Tr.SLocalize("exitOutOfScopedMode"),
+ onPress: func() error {
+ gui.State.LogScope = ""
+ return gui.Errors.ErrRestart
+ },
+ })
+ }
+
+ return gui.createMenu(gui.Tr.SLocalize("scopingMenuTitle"), menuItems, createMenuOptions{showCancel: true})
+}
diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go
index 4fae6085c..797a9a624 100644
--- a/pkg/gui/stash_panel.go
+++ b/pkg/gui/stash_panel.go
@@ -47,7 +47,7 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
- gui.State.StashEntries = gui.GitCommand.GetStashEntries()
+ gui.State.StashEntries = gui.GitCommand.GetStashEntries(gui.State.LogScope)
gui.refreshSelectedLine(&gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries))